Migrate UI components from AdminLTE to LucentUI Theme using Tailwind CSS

- Updated tab-panel component to use Tailwind for styling and transitions.
- Refactored table-row component to implement Tailwind classes for hover effects and borders.
- Converted table-td component to utilize Tailwind for alignment and text styles.
- Changed table-th component to apply Tailwind for header styling and sorting indicators.
- Revised table component to adopt Tailwind for layout, borders, and responsive design.
- Enhanced tabs component with Tailwind for navigation styles and transitions.
- Updated toggle component to use Tailwind for switch styles and accessibility.
- Refactored tooltip component to implement Tailwind for positioning and styling.
- Added visual enhancements to app.css for improved UI interactions and animations.
This commit is contained in:
2026-02-01 14:16:57 -05:00
parent fa0bc93c83
commit abff5a381f
42 changed files with 1061 additions and 2427 deletions

View File

@@ -11,15 +11,29 @@
])
@php
// AdminLTE variant classes
// LucentUI Tailwind variant classes
$variantClasses = match($variant) {
'success' => $callout ? 'lte-callout lte-callout-success' : 'lte-alert lte-alert-success',
'danger', 'error' => $callout ? 'lte-callout lte-callout-danger' : 'lte-alert lte-alert-danger',
'warning' => $callout ? 'lte-callout lte-callout-warning' : 'lte-alert lte-alert-warning',
'info' => $callout ? 'lte-callout lte-callout-info' : 'lte-alert lte-alert-info',
'primary' => $callout ? 'lte-callout lte-callout-primary' : 'lte-alert lte-alert-primary',
'neutral' => $callout ? 'lte-callout' : 'lte-alert',
default => $callout ? 'lte-callout lte-callout-info' : 'lte-alert lte-alert-info',
'success' => $callout
? 'border-l-4 border-l-success bg-success/5 text-success'
: 'bg-success/10 border border-success/30 text-success',
'danger', 'error' => $callout
? 'border-l-4 border-l-error bg-error/5 text-error'
: 'bg-error/10 border border-error/30 text-error',
'warning' => $callout
? 'border-l-4 border-l-warning bg-warning/5 text-warning'
: 'bg-warning/10 border border-warning/30 text-warning',
'info' => $callout
? 'border-l-4 border-l-info bg-info/5 text-info'
: 'bg-info/10 border border-info/30 text-info',
'primary' => $callout
? 'border-l-4 border-l-primary bg-primary/5 text-primary'
: 'bg-primary/10 border border-primary/30 text-primary',
'neutral' => $callout
? 'border-l-4 border-l-neutral bg-background-secondary text-base/80'
: 'bg-background-secondary border border-neutral text-base/80',
default => $callout
? 'border-l-4 border-l-info bg-info/5 text-info'
: 'bg-info/10 border border-info/30 text-info',
};
// Default icons per variant
@@ -34,7 +48,7 @@
};
$alertIcon = $icon ?? $defaultIcon;
$baseClasses = "$variantClasses lte-fade-in";
$baseClasses = "relative p-4 rounded-lg transition-all duration-300 $variantClasses";
@endphp
<div

View File

@@ -11,16 +11,16 @@
])
@php
// AdminLTE badge base class
$baseClasses = 'lte-badge';
// LucentUI badge base classes
$baseClasses = 'inline-flex items-center gap-1.5 font-semibold transition-all duration-200';
// Size classes - inline style adjustments
$sizeStyles = match($size) {
'xs' => 'padding: 0.2em 0.5em; font-size: 0.65em;',
'sm' => 'padding: 0.25em 0.55em; font-size: 0.7em;',
'md' => '',
'lg' => 'padding: 0.4em 0.75em; font-size: 0.85em;',
default => '',
// Size classes
$sizeClasses = match($size) {
'xs' => 'px-1.5 py-0.5 text-[10px]',
'sm' => 'px-2 py-0.5 text-xs',
'md' => 'px-2.5 py-1 text-xs',
'lg' => 'px-3 py-1.5 text-sm',
default => 'px-2.5 py-1 text-xs',
};
// Icon size classes
@@ -41,47 +41,61 @@
default => 'size-2',
};
// AdminLTE variant classes
// Variant classes - LucentUI Tailwind
$variantClasses = match($variant) {
'success' => 'lte-badge-success',
'danger' => 'lte-badge-danger',
'warning' => 'lte-badge-warning',
'info' => 'lte-badge-info',
'primary' => 'lte-badge-primary',
'secondary' => 'lte-badge-secondary',
'neutral' => 'lte-badge-light',
default => 'lte-badge-secondary',
'success' => $outline
? 'border border-success text-success bg-transparent'
: 'bg-success/20 text-success',
'danger' => $outline
? 'border border-error text-error bg-transparent'
: 'bg-error/20 text-error',
'warning' => $outline
? 'border border-warning text-warning bg-transparent'
: 'bg-warning/20 text-warning',
'info' => $outline
? 'border border-info text-info bg-transparent'
: 'bg-info/20 text-info',
'primary' => $outline
? 'border border-primary text-primary bg-transparent'
: 'bg-primary/20 text-primary',
'secondary' => $outline
? 'border border-neutral text-base/70 bg-transparent'
: 'bg-neutral/20 text-base/70',
'neutral' => $outline
? 'border border-neutral/50 text-base/60 bg-transparent'
: 'bg-neutral/10 text-base/60',
default => $outline
? 'border border-neutral text-base/70 bg-transparent'
: 'bg-neutral/20 text-base/70',
};
// Dot color classes - AdminLTE colors
// Dot color classes
$dotColorClasses = match($variant) {
'success' => 'lte-bg-success',
'danger' => 'lte-bg-danger',
'warning' => 'lte-bg-warning',
'info' => 'lte-bg-info',
'primary' => 'lte-bg-primary',
'secondary' => 'lte-bg-secondary',
'neutral' => 'lte-bg-light',
default => 'lte-bg-secondary',
'success' => 'bg-success',
'danger' => 'bg-error',
'warning' => 'bg-warning',
'info' => 'bg-info',
'primary' => 'bg-primary',
'secondary' => 'bg-neutral',
'neutral' => 'bg-neutral/50',
default => 'bg-neutral',
};
// Border radius
$roundedClasses = match($rounded) {
'none' => 'rounded-none',
'sm' => 'rounded-sm',
'md' => 'rounded-md',
'lg' => 'rounded-lg',
'full' => 'rounded-full',
default => 'rounded-full',
};
// Combined classes
$classes = "$baseClasses $variantClasses";
// Border radius style
$borderRadius = match($rounded) {
'none' => 'border-radius: 0;',
'sm' => 'border-radius: 0.125rem;',
'md' => 'border-radius: 0.25rem;',
'lg' => 'border-radius: 0.375rem;',
'full' => 'border-radius: 9999px;',
default => 'border-radius: 9999px;',
};
$styles = trim("$sizeStyles $borderRadius");
$classes = "$baseClasses $sizeClasses $variantClasses $roundedClasses";
@endphp
<span {{ $attributes->merge(['class' => $classes]) }} @if($styles) style="{{ $styles }}" @endif>
<span {{ $attributes->merge(['class' => $classes]) }}>
{{-- Dot indicator --}}
@if($dot)
<span class="{{ $dotSizeClasses }} {{ $dotColorClasses }} rounded-full {{ $pulse ? 'animate-pulse' : '' }}"></span>

View File

@@ -14,17 +14,17 @@
])
@php
// AdminLTE button base class
$baseClasses = 'lte-btn';
// LucentUI button base classes
$baseClasses = 'inline-flex items-center justify-center gap-2 font-semibold transition-all duration-200 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 active:scale-95';
// Size classes - AdminLTE style
// Size classes
$sizeClasses = match($size) {
'xs' => 'lte-btn-sm',
'sm' => 'lte-btn-sm',
'md' => '',
'lg' => 'lte-btn-lg',
'xl' => 'lte-btn-lg',
default => '',
'xs' => 'px-2 py-1 text-xs',
'sm' => 'px-3 py-1.5 text-sm',
'md' => 'px-4 py-2 text-sm',
'lg' => 'px-5 py-2.5 text-base',
'xl' => 'px-6 py-3 text-base',
default => 'px-4 py-2 text-sm',
};
// Icon size classes
@@ -37,43 +37,66 @@
default => 'size-4',
};
// Variant classes - AdminLTE style
// Variant classes - LucentUI Tailwind
$variantClasses = match($variant) {
'primary' => $outline ? 'lte-btn-outline-primary' : 'lte-btn-primary',
'secondary' => $outline ? 'lte-btn-outline-secondary' : 'lte-btn-secondary',
'success' => $outline ? 'lte-btn-outline-success' : 'lte-btn-success',
'danger' => $outline ? 'lte-btn-outline-danger' : 'lte-btn-danger',
'warning' => 'lte-btn-warning',
'info' => 'lte-btn-info',
'ghost' => 'lte-btn-outline-secondary',
'link' => 'lte-text-primary',
'neutral' => $outline ? 'lte-btn-outline-secondary' : 'lte-btn-secondary',
default => $outline ? 'lte-btn-outline-primary' : 'lte-btn-primary',
'primary' => $outline
? 'border border-primary text-primary bg-transparent hover:bg-primary/10 focus:ring-2 focus:ring-primary/50'
: 'bg-primary text-white hover:bg-primary/80 focus:ring-2 focus:ring-primary/50',
'secondary' => $outline
? 'border border-neutral text-base/80 bg-transparent hover:bg-neutral/10 focus:ring-2 focus:ring-neutral/50'
: 'bg-background-secondary text-base/80 border border-neutral hover:bg-background-secondary/80 focus:ring-2 focus:ring-neutral/50',
'success' => $outline
? 'border border-success text-success bg-transparent hover:bg-success/10 focus:ring-2 focus:ring-success/50'
: 'bg-success text-white hover:bg-success/80 focus:ring-2 focus:ring-success/50',
'danger' => $outline
? 'border border-error text-error bg-transparent hover:bg-error/10 focus:ring-2 focus:ring-error/50'
: 'bg-error text-white hover:bg-error/80 focus:ring-2 focus:ring-error/50',
'warning' => $outline
? 'border border-warning text-warning bg-transparent hover:bg-warning/10 focus:ring-2 focus:ring-warning/50'
: 'bg-warning text-white hover:bg-warning/80 focus:ring-2 focus:ring-warning/50',
'info' => $outline
? 'border border-info text-info bg-transparent hover:bg-info/10 focus:ring-2 focus:ring-info/50'
: 'bg-info text-white hover:bg-info/80 focus:ring-2 focus:ring-info/50',
'ghost' => 'text-base/80 bg-transparent hover:bg-neutral/10 focus:ring-2 focus:ring-neutral/50',
'link' => 'text-primary bg-transparent hover:underline p-0',
'neutral' => $outline
? 'border border-neutral text-base/70 bg-transparent hover:bg-neutral/10 focus:ring-2 focus:ring-neutral/50'
: 'bg-neutral/20 text-base/80 hover:bg-neutral/30 focus:ring-2 focus:ring-neutral/50',
default => $outline
? 'border border-primary text-primary bg-transparent hover:bg-primary/10 focus:ring-2 focus:ring-primary/50'
: 'bg-primary text-white hover:bg-primary/80 focus:ring-2 focus:ring-primary/50',
};
// Full width
$widthClasses = $fullWidth ? 'w-full' : '';
// Combined classes
$classes = trim("$baseClasses $sizeClasses $variantClasses $widthClasses");
// Border radius
$roundedClasses = match($rounded) {
'none' => 'rounded-none',
'sm' => 'rounded-sm',
'md' => 'rounded-md',
'lg' => 'rounded-lg',
'xl' => 'rounded-xl',
'full' => 'rounded-full',
default => 'rounded-lg',
};
// Determine tag
$tag = $href ? 'a' : 'button';
// Combined classes
$classes = trim("$baseClasses $sizeClasses $variantClasses $widthClasses $roundedClasses");
@endphp
@if($href)
<a href="{{ $href }}" {{ $attributes->merge(['class' => $classes]) }} style="border-radius: 0.375rem;">
<a href="{{ $href }}" {{ $attributes->merge(['class' => $classes]) }}>
@else
<button
type="{{ $type }}"
{{ $disabled || $loading ? 'disabled' : '' }}
{{ $attributes->merge(['class' => $classes]) }}
style="border-radius: 0.375rem;"
>
@endif
@if($loading)
<span class="lte-spinner lte-spinner-sm" style="margin-right: 0.5rem;"></span>
<x-ri-loader-5-fill class="{{ $iconSizeClasses }} animate-spin" />
@elseif($icon && $iconPosition === 'left')
<x-dynamic-component :component="$icon" class="{{ $iconSizeClasses }}" />
@endif

View File

@@ -3,7 +3,7 @@
'subtitle' => null,
'icon' => null,
'iconClass' => 'size-5',
'iconColor' => 'lte-text-primary',
'iconColor' => 'text-primary',
'actions' => null,
'padding' => true,
'flush' => false,
@@ -15,17 +15,17 @@
])
@php
// AdminLTE Card variant classes
// LucentUI Card variant classes
$variantClasses = match($variant) {
'primary' => $outline ? 'lte-card-outline-primary' : 'lte-card-primary',
'success' => $outline ? 'lte-card-outline-success' : 'lte-card-success',
'warning' => $outline ? 'lte-card-outline-warning' : 'lte-card-warning',
'danger' => $outline ? 'lte-card-outline-danger' : 'lte-card-danger',
'info' => $outline ? 'lte-card-outline-info' : 'lte-card-info',
default => '',
'primary' => $outline ? 'border-primary' : 'border-primary bg-primary/5',
'success' => $outline ? 'border-success' : 'border-success bg-success/5',
'warning' => $outline ? 'border-warning' : 'border-warning bg-warning/5',
'danger' => $outline ? 'border-error' : 'border-error bg-error/5',
'info' => $outline ? 'border-info' : 'border-info bg-info/5',
default => 'border-neutral',
};
$cardClasses = "lte-card lte-slide-in-up $variantClasses";
$cardClasses = "bg-background border rounded-lg shadow-sm transition-all duration-300 $variantClasses";
@endphp
<div {{ $attributes->merge(['class' => $cardClasses]) }}
@@ -47,22 +47,22 @@
@endif
>
@if($title || $icon || $actions || isset($header))
<div class="lte-card-header">
<h3 class="lte-card-title">
<div class="flex items-center justify-between px-4 py-3 border-b border-neutral">
<h3 class="flex items-center gap-3 text-base font-semibold text-base">
@if($icon)
<span class="lte-info-box-icon lte-bg-light rounded" style="width: 36px; height: 36px; font-size: 1rem;">
<span class="flex items-center justify-center size-9 bg-background-secondary rounded">
<x-dynamic-component :component="$icon" class="{{ $iconClass }} {{ $iconColor }}" />
</span>
@endif
<span>
{{ $title }}
@if($subtitle)
<small class="lte-text-muted d-block" style="font-size: 0.8rem; font-weight: normal;">{{ $subtitle }}</small>
<small class="block text-xs font-normal text-base/60">{{ $subtitle }}</small>
@endif
</span>
</h3>
@if($actions || $collapsible)
<div class="lte-card-tools">
<div class="flex items-center gap-2">
@if($actions)
{{ $actions }}
@endif
@@ -70,8 +70,7 @@
<button
type="button"
@click="toggle()"
class="lte-btn lte-btn-sm lte-btn-outline-secondary"
style="padding: 0.25rem 0.5rem;"
class="inline-flex items-center justify-center px-2 py-1 text-sm border border-neutral rounded bg-transparent text-base/80 hover:bg-neutral/10 transition-colors"
>
<x-ri-arrow-down-s-line
class="size-4 transition-transform duration-200"
@@ -89,7 +88,7 @@
@if($collapsible)
<div
class="lte-card-body"
class="p-4"
x-show="!isCollapsed"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
@@ -101,13 +100,13 @@
{{ $slot }}
</div>
@else
<div class="lte-card-body">
<div class="p-4">
{{ $slot }}
</div>
@endif
@if(isset($footer))
<div class="lte-card-footer">
<div class="px-4 py-3 border-t border-neutral bg-background-secondary/50 rounded-b-lg">
{{ $footer }}
</div>
@endif

View File

@@ -19,12 +19,12 @@
@php
$inputId = $id ?? $name ?? Str::random(8);
// AdminLTE size classes
// LucentUI size classes
$sizeClasses = match($size) {
'sm' => 'lte-form-control-sm',
'md' => '',
'lg' => 'lte-form-control-lg',
default => '',
'sm' => 'px-2.5 py-1.5 text-sm',
'md' => 'px-3 py-2 text-sm',
'lg' => 'px-4 py-2.5 text-base',
default => 'px-3 py-2 text-sm',
};
$iconSizeClasses = match($size) {
@@ -34,35 +34,38 @@
default => 'size-4',
};
// AdminLTE input classes
$inputClasses = "lte-form-control $sizeClasses";
// LucentUI input classes
$inputClasses = "w-full bg-background border border-neutral rounded-lg text-base placeholder:text-base/50 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-200 $sizeClasses";
if ($error) {
$inputClasses .= ' is-invalid';
$inputClasses .= ' border-error focus:ring-error/50 focus:border-error';
}
if ($disabled) {
$inputClasses .= ' opacity-50 cursor-not-allowed';
}
@endphp
<div {{ $attributes->only('class')->merge(['class' => 'mb-3']) }}>
{{-- Label --}}
@if($label)
<label for="{{ $inputId }}" class="form-label" style="display: block; margin-bottom: 0.5rem; font-weight: 500;">
<label for="{{ $inputId }}" class="block mb-2 font-medium text-sm text-base">
{{ $label }}
@if($required)
<span class="lte-text-danger">*</span>
<span class="text-error">*</span>
@endif
</label>
@endif
{{-- Input wrapper --}}
@if($icon || $prefix || $suffix)
<div class="lte-input-group">
<div class="flex items-stretch">
{{-- Left icon/prefix --}}
@if($icon && $iconPosition === 'left')
<span class="lte-input-group-text">
<span class="inline-flex items-center px-3 bg-background-secondary border border-r-0 border-neutral rounded-l-lg text-base/60">
<x-dynamic-component :component="$icon" class="{{ $iconSizeClasses }}" />
</span>
@endif
@if($prefix)
<span class="lte-input-group-text">{{ $prefix }}</span>
<span class="inline-flex items-center px-3 bg-background-secondary border border-r-0 border-neutral {{ ($icon && $iconPosition === 'left') ? '' : 'rounded-l-lg' }} text-base/60 text-sm">{{ $prefix }}</span>
@endif
{{-- Input --}}
@@ -74,17 +77,17 @@
@if($required) required @endif
@if($disabled) disabled @endif
@if($readonly) readonly @endif
{{ $attributes->except('class')->merge(['class' => $inputClasses]) }}
{{ $attributes->except('class')->merge(['class' => $inputClasses . ' rounded-none ' . ((!$icon || $iconPosition !== 'left') && !$prefix ? 'rounded-l-lg' : '') . ' ' . ((!$icon || $iconPosition !== 'right') && !$suffix ? 'rounded-r-lg' : '')]) }}
>
{{-- Right icon/suffix --}}
@if($icon && $iconPosition === 'right')
<span class="lte-input-group-text">
<span class="inline-flex items-center px-3 bg-background-secondary border border-l-0 border-neutral rounded-r-lg text-base/60">
<x-dynamic-component :component="$icon" class="{{ $iconSizeClasses }}" />
</span>
@endif
@if($suffix)
<span class="lte-input-group-text">{{ $suffix }}</span>
<span class="inline-flex items-center px-3 bg-background-secondary border border-l-0 border-neutral {{ ($icon && $iconPosition === 'right') ? '' : 'rounded-r-lg' }} text-base/60 text-sm">{{ $suffix }}</span>
@endif
</div>
@else
@@ -103,12 +106,12 @@
{{-- Hint text --}}
@if($hint && !$error)
<small class="lte-text-muted" style="display: block; margin-top: 0.25rem;">{{ $hint }}</small>
<small class="block mt-1 text-xs text-base/60">{{ $hint }}</small>
@endif
{{-- Error message --}}
@if($error)
<div class="invalid-feedback" style="display: flex; align-items: center; gap: 0.25rem; margin-top: 0.25rem; color: var(--lte-danger);">
<div class="flex items-center gap-1 mt-1 text-sm text-error">
<x-ri-error-warning-line class="size-3.5" />
{{ $error }}
</div>

View File

@@ -12,49 +12,42 @@
@php
$percentage = $max > 0 ? min(100, ($value / $max) * 100) : 0;
// AdminLTE size classes
// LucentUI size classes
$sizeClasses = match($size) {
'xs' => 'lte-progress-xs',
'sm' => 'lte-progress-sm',
'md' => '',
'lg' => '',
'xl' => '',
default => '',
'xs' => 'h-1',
'sm' => 'h-1.5',
'md' => 'h-2',
'lg' => 'h-3',
'xl' => 'h-4',
default => 'h-2',
};
// Height style for non-standard sizes
$heightStyle = match($size) {
'lg' => 'height: 1.25rem;',
'xl' => 'height: 1.5rem;',
default => '',
};
// AdminLTE variant classes for the fill
// LucentUI variant classes for the fill
$variantClasses = match($variant) {
'primary' => 'lte-bg-primary',
'secondary' => 'lte-bg-secondary',
'success' => 'lte-bg-success',
'danger' => 'lte-bg-danger',
'warning' => 'lte-bg-warning',
'info' => 'lte-bg-info',
'gradient' => 'lte-bg-gradient-primary',
default => 'lte-bg-primary',
'primary' => 'bg-primary',
'secondary' => 'bg-base/50',
'success' => 'bg-success',
'danger' => 'bg-error',
'warning' => 'bg-warning',
'info' => 'bg-info',
'gradient' => 'bg-gradient-to-r from-primary to-primary/60',
default => 'bg-primary',
};
// Dynamic color based on percentage
if ($variant === 'auto') {
if ($percentage >= 90) {
$variantClasses = 'lte-bg-danger';
$variantClasses = 'bg-error';
} elseif ($percentage >= 70) {
$variantClasses = 'lte-bg-warning';
$variantClasses = 'bg-warning';
} else {
$variantClasses = 'lte-bg-success';
$variantClasses = 'bg-success';
}
}
// Animation classes - AdminLTE style
$animatedClasses = $animated ? 'lte-progress-bar-animated' : '';
$stripedClasses = $striped ? 'lte-progress-bar-striped' : '';
// Animation classes
$animatedClasses = $animated ? 'progress-striped' : '';
$stripedClasses = $striped ? 'progress-striped' : '';
@endphp
<div {{ $attributes->merge(['class' => 'flex flex-col gap-1.5']) }}>
@@ -62,19 +55,19 @@
@if($label || $showValue)
<div class="flex items-center justify-between text-sm">
@if($label)
<span class="lte-text-muted" style="font-weight: 500;">{{ $label }}</span>
<span class="text-base/60 font-medium">{{ $label }}</span>
@endif
@if($showValue)
<span style="font-weight: 600;">{{ number_format($percentage, 1) }}%</span>
<span class="font-semibold text-base">{{ number_format($percentage, 1) }}%</span>
@endif
</div>
@endif
{{-- Progress bar container - AdminLTE style --}}
<div class="lte-progress {{ $sizeClasses }}" @if($heightStyle) style="{{ $heightStyle }}" @endif>
{{-- Progress bar container --}}
<div class="w-full bg-background-secondary rounded-full overflow-hidden {{ $sizeClasses }}">
{{-- Progress bar fill --}}
<div
class="lte-progress-bar {{ $variantClasses }} {{ $animatedClasses }} {{ $stripedClasses }}"
class="h-full rounded-full transition-all duration-300 ease-out {{ $variantClasses }} {{ $animatedClasses }} {{ $stripedClasses }}"
style="width: {{ $percentage }}%"
role="progressbar"
aria-valuenow="{{ $value }}"

View File

@@ -10,16 +10,21 @@
])
@php
// AdminLTE table classes
$tableClasses = 'lte-table';
// LucentUI table classes
$tableClasses = 'w-full text-sm text-left';
if ($striped) {
$tableClasses .= ' lte-table-striped';
$tableClasses .= ' [&_tbody_tr:nth-child(even)]:bg-background-secondary/50';
}
if ($hoverable) {
$tableClasses .= ' lte-table-hover';
$tableClasses .= ' [&_tbody_tr]:hover:bg-background-secondary/70 [&_tbody_tr]:transition-colors';
}
if ($compact) {
$tableClasses .= ' [&_th]:py-2 [&_td]:py-2';
} else {
$tableClasses .= ' [&_th]:py-3 [&_td]:py-3';
}
$wrapperClasses = 'lte-card';
$wrapperClasses = 'bg-background border border-neutral rounded-lg shadow-sm';
if ($responsive) {
$wrapperClasses .= ' overflow-x-auto';
@@ -28,14 +33,14 @@
<div {{ $attributes->merge(['class' => $wrapperClasses]) }}>
@if($loading)
<div style="padding: 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem;">
<div class="lte-spinner"></div>
<p class="lte-text-muted" style="font-size: 0.875rem;">Loading data...</p>
<div class="flex flex-col items-center justify-center gap-3 p-8">
<x-ri-loader-5-fill class="size-8 animate-spin text-primary" />
<p class="text-sm text-base/60">Loading data...</p>
</div>
@else
<table class="{{ $tableClasses }}" style="width: 100%;">
<table class="{{ $tableClasses }}">
@if(isset($head))
<thead @if($stickyHeader) style="position: sticky; top: 0; z-index: 10;" @endif>
<thead class="bg-background-secondary/50 border-b border-neutral text-xs uppercase text-base/60" @if($stickyHeader) style="position: sticky; top: 0; z-index: 10;" @endif>
<tr>
{{ $head }}
</tr>
@@ -43,22 +48,22 @@
@endif
@if(isset($body) && $body->isNotEmpty())
<tbody>
<tbody class="divide-y divide-neutral/50">
{{ $body }}
</tbody>
@elseif($slot->isNotEmpty())
<tbody>
<tbody class="divide-y divide-neutral/50">
{{ $slot }}
</tbody>
@else
<tbody>
<tr>
<td colspan="100" style="padding: 3rem 1.25rem;">
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.75rem; text-align: center;">
<div class="lte-info-box-icon lte-bg-light rounded" style="width: 56px; height: 56px;">
<x-dynamic-component :component="$emptyIcon" class="size-7 lte-text-muted" />
<td colspan="100" class="px-5 py-12">
<div class="flex flex-col items-center justify-center gap-3 text-center">
<div class="flex items-center justify-center size-14 bg-background-secondary rounded-lg">
<x-dynamic-component :component="$emptyIcon" class="size-7 text-base/50" />
</div>
<p class="lte-text-muted" style="font-size: 0.875rem;">{{ $emptyMessage }}</p>
<p class="text-sm text-base/60">{{ $emptyMessage }}</p>
</div>
</td>
</tr>
@@ -66,7 +71,7 @@
@endif
@if(isset($foot))
<tfoot class="bg-neutral/5 border-t border-neutral/10">
<tfoot class="bg-background-secondary/50 border-t border-neutral">
{{ $foot }}
</tfoot>
@endif

View File

@@ -1,5 +1,6 @@
{{--
Action Toolbar Button Component - AdminLTE style
Action Toolbar Button Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Individual button for VM control actions
Usage:
@@ -23,152 +24,46 @@
])
@php
$sizeStyles = [
'sm' => 'height: 2.25rem; padding: 0 0.75rem; font-size: 0.8125rem;',
'md' => 'height: 2.5rem; padding: 0 1rem; font-size: 0.875rem;',
'lg' => 'height: 2.75rem; padding: 0 1.25rem; font-size: 0.875rem;',
$sizeClasses = [
'sm' => 'h-9 px-3 text-xs',
'md' => 'h-10 px-4 text-sm',
'lg' => 'h-11 px-5 text-sm',
];
$iconSizes = [
'sm' => 'width: 0.875rem; height: 0.875rem;',
'md' => 'width: 1rem; height: 1rem;',
'lg' => 'width: 1.125rem; height: 1.125rem;',
'sm' => 'size-3.5',
'md' => 'size-4',
'lg' => 'size-4.5',
];
$variantClasses = [
'default' => 'bg-background border border-neutral text-base/80 shadow-sm hover:bg-background-secondary hover:border-neutral/80',
'primary' => 'bg-primary/10 border border-primary/20 text-primary hover:bg-primary/15 hover:border-primary/30',
'success' => 'bg-success/10 border border-success/20 text-success hover:bg-success/15 hover:border-success/30',
'warning' => 'bg-warning/15 border border-warning/30 text-warning hover:bg-warning/25 hover:border-warning/40',
'danger' => 'bg-error/10 border border-error/20 text-error hover:bg-error/15 hover:border-error/30',
];
@endphp
<style>
.action-btn-lte {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
white-space: nowrap;
font-weight: 500;
border-radius: var(--lte-border-radius, 0.375rem);
border: 1px solid transparent;
transition: all var(--lte-transition-speed, 0.3s) ease;
cursor: pointer;
}
.action-btn-lte:focus {
outline: none;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.action-btn-lte:disabled {
opacity: 0.5;
pointer-events: none;
}
.action-btn-lte-default {
background-color: #fff;
border-color: var(--lte-border-color, #dee2e6);
color: #495057;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.action-btn-lte-default:hover {
background-color: #f8f9fa;
border-color: #adb5bd;
}
.action-btn-lte-primary {
background-color: rgba(13, 110, 253, 0.1);
border-color: rgba(13, 110, 253, 0.2);
color: var(--lte-primary, #0d6efd);
}
.action-btn-lte-primary:hover {
background-color: rgba(13, 110, 253, 0.15);
border-color: rgba(13, 110, 253, 0.3);
}
.action-btn-lte-success {
background-color: rgba(25, 135, 84, 0.1);
border-color: rgba(25, 135, 84, 0.2);
color: var(--lte-success, #198754);
}
.action-btn-lte-success:hover {
background-color: rgba(25, 135, 84, 0.15);
border-color: rgba(25, 135, 84, 0.3);
}
.action-btn-lte-warning {
background-color: rgba(255, 193, 7, 0.15);
border-color: rgba(255, 193, 7, 0.3);
color: #997404;
}
.action-btn-lte-warning:hover {
background-color: rgba(255, 193, 7, 0.25);
border-color: rgba(255, 193, 7, 0.4);
}
.action-btn-lte-danger {
background-color: rgba(220, 53, 69, 0.1);
border-color: rgba(220, 53, 69, 0.2);
color: var(--lte-danger, #dc3545);
}
.action-btn-lte-danger:hover {
background-color: rgba(220, 53, 69, 0.15);
border-color: rgba(220, 53, 69, 0.3);
}
.action-btn-lte-loading {
cursor: wait;
}
/* Spinner */
.action-btn-spinner-lte {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Dark mode */
.dark .action-btn-lte-default {
background-color: #1f2937;
border-color: #374151;
color: #d1d5db;
}
.dark .action-btn-lte-default:hover {
background-color: #374151;
border-color: #4b5563;
}
.dark .action-btn-lte-primary {
background-color: rgba(13, 110, 253, 0.15);
border-color: rgba(13, 110, 253, 0.25);
}
.dark .action-btn-lte-success {
background-color: rgba(25, 135, 84, 0.15);
border-color: rgba(25, 135, 84, 0.25);
}
.dark .action-btn-lte-warning {
background-color: rgba(255, 193, 7, 0.15);
border-color: rgba(255, 193, 7, 0.25);
color: #ffc107;
}
.dark .action-btn-lte-danger {
background-color: rgba(220, 53, 69, 0.15);
border-color: rgba(220, 53, 69, 0.25);
}
</style>
<button
type="button"
@if($confirm)
x-data=""
@click="if(confirm('{{ $confirm }}')) { $wire.call('{{ $action }}') }"
@endif
style="{{ $sizeStyles[$size] ?? $sizeStyles['md'] }}"
{{ $attributes->merge([
'disabled' => $disabled || $loading,
])->class([
'action-btn-lte',
'action-btn-lte-default' => $variant === 'default',
'action-btn-lte-primary' => $variant === 'primary',
'action-btn-lte-success' => $variant === 'success',
'action-btn-lte-warning' => $variant === 'warning',
'action-btn-lte-danger' => $variant === 'danger',
'action-btn-lte-loading' => $loading,
'inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium rounded-lg transition-all duration-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50 disabled:pointer-events-none',
$sizeClasses[$size] ?? $sizeClasses['md'],
$variantClasses[$variant] ?? $variantClasses['default'],
'cursor-wait' => $loading,
]) }}
>
@if($loading)
<svg class="action-btn-spinner-lte" style="{{ $iconSizes[$size] ?? $iconSizes['md'] }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<x-ri-loader-5-fill class="{{ $iconSizes[$size] ?? $iconSizes['md'] }} animate-spin" />
@elseif($icon)
<x-dynamic-component :component="$icon" style="{{ $iconSizes[$size] ?? $iconSizes['md'] }}" />
<x-dynamic-component :component="$icon" class="{{ $iconSizes[$size] ?? $iconSizes['md'] }}" />
@endif
<span>{{ $label }}</span>
</button>

View File

@@ -1,5 +1,6 @@
{{--
Action Toolbar Component - AdminLTE style
Action Toolbar Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
For VM control buttons (Start, Stop, Restart, etc.)
Usage:
@@ -16,20 +17,9 @@
'centered' => false,
])
<style>
.action-toolbar-lte {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.action-toolbar-lte-centered {
justify-content: center;
}
</style>
<div {{ $attributes->class([
'action-toolbar-lte',
'action-toolbar-lte-centered' => $centered,
'flex flex-wrap gap-3',
'justify-center' => $centered,
]) }}>
{{ $slot }}
</div>

View File

@@ -1,6 +1,6 @@
{{--
Alert Component - AdminLTE Theme
Based on AdminLTE v4 Alert & Callout styling patterns
Alert Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.alert>Default alert</x-proxmox::ui.alert>
@@ -22,34 +22,40 @@
@php
$variantConfig = [
'default' => [
'alertClass' => 'lte-alert',
'calloutClass' => 'lte-callout',
'alertClass' => 'bg-background-secondary border border-neutral text-base/80',
'calloutClass' => 'border-l-4 border-l-neutral bg-background-secondary/50 text-base/80',
'icon' => $icon ?? 'ri-information-line',
'iconColor' => 'text-base/60',
],
'info' => [
'alertClass' => 'lte-alert lte-alert-info',
'calloutClass' => 'lte-callout lte-callout-info',
'alertClass' => 'bg-info/10 border border-info/30 text-info',
'calloutClass' => 'border-l-4 border-l-info bg-info/5 text-info',
'icon' => $icon ?? 'ri-information-line',
'iconColor' => 'text-info',
],
'success' => [
'alertClass' => 'lte-alert lte-alert-success',
'calloutClass' => 'lte-callout lte-callout-success',
'alertClass' => 'bg-success/10 border border-success/30 text-success',
'calloutClass' => 'border-l-4 border-l-success bg-success/5 text-success',
'icon' => $icon ?? 'ri-checkbox-circle-line',
'iconColor' => 'text-success',
],
'warning' => [
'alertClass' => 'lte-alert lte-alert-warning',
'calloutClass' => 'lte-callout lte-callout-warning',
'alertClass' => 'bg-warning/10 border border-warning/30 text-warning',
'calloutClass' => 'border-l-4 border-l-warning bg-warning/5 text-warning',
'icon' => $icon ?? 'ri-error-warning-line',
'iconColor' => 'text-warning',
],
'danger' => [
'alertClass' => 'lte-alert lte-alert-danger',
'calloutClass' => 'lte-callout lte-callout-danger',
'alertClass' => 'bg-error/10 border border-error/30 text-error',
'calloutClass' => 'border-l-4 border-l-error bg-error/5 text-error',
'icon' => $icon ?? 'ri-error-warning-fill',
'iconColor' => 'text-error',
],
'primary' => [
'alertClass' => 'lte-alert lte-alert-primary',
'calloutClass' => 'lte-callout lte-callout-primary',
'alertClass' => 'bg-primary/10 border border-primary/30 text-primary',
'calloutClass' => 'border-l-4 border-l-primary bg-primary/5 text-primary',
'icon' => $icon ?? 'ri-information-line',
'iconColor' => 'text-primary',
],
];
@@ -60,9 +66,8 @@
<div
role="alert"
{{ $attributes->class([
'relative p-4 rounded-lg transition-all duration-300 hover:shadow-sm',
$componentClass,
'lte-fade-in transition-all duration-300 hover:shadow-sm',
$dismissible ? 'lte-alert-dismissible' : '',
]) }}
@if($dismissible || $collapsible)
x-data="{
@@ -79,10 +84,10 @@
@endif
>
<div class="flex gap-3 items-start">
<x-dynamic-component :component="$config['icon']" class="size-5 mt-0.5 shrink-0 transition-transform duration-200 hover:scale-110" />
<x-dynamic-component :component="$config['icon']" class="size-5 mt-0.5 shrink-0 transition-transform duration-200 hover:scale-110 {{ $config['iconColor'] }}" />
<div class="flex-1 min-w-0">
@if($title)
<h5 style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;">{{ $title }}</h5>
<h5 class="text-lg font-semibold mb-2">{{ $title }}</h5>
@endif
<div
@if($collapsible)
@@ -100,13 +105,12 @@
</div>
</div>
@if($dismissible || $collapsible)
<div class="flex items-center gap-1 shrink-0" style="position: absolute; top: 0.5rem; right: 0.5rem;">
<div class="absolute top-2 right-2 flex items-center gap-1">
@if($collapsible)
<button
type="button"
@click="collapsed = !collapsed"
class="lte-btn lte-btn-sm lte-btn-outline-secondary transition-all duration-200 hover:scale-105"
style="padding: 0.25rem;"
class="p-1 rounded border border-neutral bg-transparent text-base/60 hover:bg-neutral/10 hover:scale-105 transition-all duration-200"
>
<x-ri-arrow-down-s-line
class="size-4 transition-transform duration-300 ease-out"
@@ -118,9 +122,11 @@
<button
type="button"
@click="show = false"
class="lte-btn-close transition-opacity duration-200 hover:opacity-70"
class="p-1 rounded text-base/60 hover:text-base hover:opacity-70 transition-opacity duration-200"
aria-label="Close"
></button>
>
<x-ri-close-line class="size-4" />
</button>
@endif
</div>
@endif

View File

@@ -1,5 +1,6 @@
{{--
Resource Allocation Card Component - AdminLTE style
Resource Allocation Card Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
For displaying CPU, Memory, and other resource allocation vs entitlement
Usage:
@@ -10,7 +11,7 @@
entitlement-label="4 vCPU entitlement"
description="Live hypervisor usage sampled directly from the cluster."
gradient="linear-gradient(90deg, #38bdf8 0%, #6366f1 100%)"
:badge="['label' => 'Healthy', 'class' => 'text-success-500 bg-success-50 dark:bg-success-500/15']"
:badge="['label' => 'Healthy', 'class' => 'text-success bg-success/10']"
/>
--}}
@@ -27,178 +28,53 @@
])
@php
$iconBgColors = [
'primary' => 'background-color: rgba(13, 110, 253, 0.1);',
'success' => 'background-color: rgba(25, 135, 84, 0.1);',
'warning' => 'background-color: rgba(255, 193, 7, 0.1);',
'danger' => 'background-color: rgba(220, 53, 69, 0.1);',
'info' => 'background-color: rgba(13, 202, 240, 0.1);',
$iconBgClasses = [
'primary' => 'bg-primary/10',
'success' => 'bg-success/10',
'warning' => 'bg-warning/10',
'danger' => 'bg-error/10',
'info' => 'bg-info/10',
];
$iconTextColors = [
'primary' => 'color: var(--lte-primary, #0d6efd);',
'success' => 'color: var(--lte-success, #198754);',
'warning' => 'color: var(--lte-warning, #ffc107);',
'danger' => 'color: var(--lte-danger, #dc3545);',
'info' => 'color: var(--lte-info, #0dcaf0);',
$iconTextClasses = [
'primary' => 'text-primary',
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-error',
'info' => 'text-info',
];
@endphp
<style>
.allocation-card-lte {
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
padding: 1.25rem;
box-shadow: var(--lte-card-shadow);
transition: background-color var(--lte-transition-speed, 0.3s) ease;
}
.allocation-card-lte:hover {
background-color: #f8f9fa;
}
.allocation-card-lte-content {
display: flex;
flex-direction: column;
gap: 1rem;
}
.allocation-card-lte-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
}
.allocation-card-lte-main {
display: flex;
align-items: flex-start;
gap: 0.875rem;
}
.allocation-card-lte-icon {
display: flex;
align-items: center;
justify-content: center;
width: 2.75rem;
height: 2.75rem;
flex-shrink: 0;
border-radius: calc(var(--lte-border-radius, 0.375rem) + 2px);
}
.allocation-card-lte-icon svg {
width: 1.25rem;
height: 1.25rem;
}
.allocation-card-lte-info {
min-width: 0;
}
.allocation-card-lte-label {
font-size: 0.8125rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
margin-bottom: 0.25rem;
}
.allocation-card-lte-value {
font-size: 1.25rem;
font-weight: 700;
color: #212529;
line-height: 1.3;
}
.allocation-card-lte-entitlement {
font-size: 0.8125rem;
color: var(--lte-secondary, #6c757d);
margin-top: 0.25rem;
}
.allocation-card-lte-badge {
flex-shrink: 0;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
background-color: rgba(25, 135, 84, 0.1);
color: var(--lte-success, #198754);
}
.allocation-card-lte-progress-section {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.allocation-card-lte-progress-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.8125rem;
}
.allocation-card-lte-progress-label {
color: var(--lte-secondary, #6c757d);
}
.allocation-card-lte-progress-value {
font-weight: 600;
color: #212529;
}
.allocation-card-lte-progress-track {
height: 0.5rem;
background-color: #e9ecef;
border-radius: 9999px;
overflow: hidden;
}
.allocation-card-lte-progress-bar {
height: 100%;
border-radius: 9999px;
transition: width var(--lte-transition-speed, 0.3s) ease;
}
.allocation-card-lte-description {
font-size: 0.8125rem;
color: #adb5bd;
}
/* Dark mode */
.dark .allocation-card-lte {
background-color: #1f2937;
border-color: #374151;
}
.dark .allocation-card-lte:hover {
background-color: #374151;
}
.dark .allocation-card-lte-value {
color: #f3f4f6;
}
.dark .allocation-card-lte-progress-value {
color: #f3f4f6;
}
.dark .allocation-card-lte-progress-track {
background-color: #374151;
}
.dark .allocation-card-lte-description {
color: #6b7280;
}
</style>
<div {{ $attributes->class(['allocation-card-lte']) }}>
<div class="allocation-card-lte-content">
<div class="allocation-card-lte-header">
<div class="allocation-card-lte-main">
<div {{ $attributes->class(['bg-background border border-neutral rounded-lg p-5 shadow-sm transition-colors duration-200 hover:bg-background-secondary/30']) }}>
<div class="flex flex-col gap-4">
<div class="flex items-start justify-between gap-3">
<div class="flex items-start gap-3.5">
@if($icon)
<div class="allocation-card-lte-icon" style="{{ $iconBgColors[$iconColor] ?? $iconBgColors['primary'] }}">
<x-dynamic-component :component="$icon" style="{{ $iconTextColors[$iconColor] ?? $iconTextColors['primary'] }}" />
<div class="flex items-center justify-center size-11 shrink-0 rounded-lg {{ $iconBgClasses[$iconColor] ?? $iconBgClasses['primary'] }}">
<x-dynamic-component :component="$icon" class="size-5 {{ $iconTextClasses[$iconColor] ?? $iconTextClasses['primary'] }}" />
</div>
@endif
<div class="allocation-card-lte-info">
<p class="allocation-card-lte-label">{{ $label }}</p>
<p class="allocation-card-lte-value">{{ $usageSummary }}</p>
<p class="allocation-card-lte-entitlement">{{ $entitlementLabel }}</p>
<div class="min-w-0">
<p class="text-xs font-medium text-base/60 mb-1">{{ $label }}</p>
<p class="text-xl font-bold text-base leading-tight">{{ $usageSummary }}</p>
<p class="text-xs text-base/60 mt-1">{{ $entitlementLabel }}</p>
</div>
</div>
@if($badge)
<span class="allocation-card-lte-badge" @if(isset($badge['class'])) style="{{ $badge['class'] }}" @endif>
<span class="shrink-0 px-2.5 py-1 text-xs font-medium rounded-full {{ $badge['class'] ?? 'bg-success/10 text-success' }}">
{{ $badge['label'] ?? '' }}
</span>
@endif
</div>
@if(!is_null($percent))
<div class="allocation-card-lte-progress-section">
<div class="allocation-card-lte-progress-header">
<span class="allocation-card-lte-progress-label">Usage</span>
<span class="allocation-card-lte-progress-value">{{ $percent }}%</span>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between text-xs">
<span class="text-base/60">Usage</span>
<span class="font-semibold text-base">{{ $percent }}%</span>
</div>
<div class="allocation-card-lte-progress-track">
<div class="h-2 bg-background-secondary rounded-full overflow-hidden">
<div
class="allocation-card-lte-progress-bar"
class="h-full rounded-full transition-all duration-300"
style="width: {{ min($percent, 130) }}%; background-image: {{ $gradient }};"
></div>
</div>
@@ -206,7 +82,7 @@
@endif
@if($description)
<p class="allocation-card-lte-description">{{ $description }}</p>
<p class="text-xs text-base/50">{{ $description }}</p>
@endif
</div>
</div>

View File

@@ -1,6 +1,6 @@
{{--
Badge Component - AdminLTE Theme
Based on AdminLTE v4 Badge styling patterns
Badge Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.badge>Default</x-proxmox::ui.badge>
@@ -19,16 +19,23 @@
@php
$variantClasses = [
'default' => 'lte-badge-primary',
'primary' => 'lte-badge-primary',
'secondary' => 'lte-badge-secondary',
'success' => 'lte-badge-success',
'warning' => 'lte-badge-warning',
'danger' => 'lte-badge-danger',
'info' => 'lte-badge-info',
'light' => 'lte-badge-light',
'dark' => 'lte-badge-dark',
'muted' => 'lte-badge-secondary',
'default' => 'bg-primary/20 text-primary',
'primary' => 'bg-primary/20 text-primary',
'secondary' => 'bg-neutral/20 text-base/70',
'success' => 'bg-success/20 text-success',
'warning' => 'bg-warning/20 text-warning',
'danger' => 'bg-error/20 text-error',
'info' => 'bg-info/20 text-info',
'light' => 'bg-neutral/10 text-base/60',
'dark' => 'bg-neutral/80 text-white',
'muted' => 'bg-neutral/20 text-base/50',
];
$sizeClasses = [
'xs' => 'px-1.5 py-0.5 text-[10px]',
'sm' => 'px-2 py-0.5 text-xs',
'md' => 'px-2.5 py-1 text-xs',
'lg' => 'px-3 py-1.5 text-sm',
];
$iconSizes = [
@@ -47,13 +54,15 @@
$iconSize = $iconSizes[$size] ?? $iconSizes['md'];
$dotSize = $dotSizes[$size] ?? $dotSizes['md'];
$badgeSize = $sizeClasses[$size] ?? $sizeClasses['md'];
@endphp
<span {{ $attributes->class([
'lte-badge',
'inline-flex items-center font-semibold rounded-full transition-all duration-200',
$badgeSize,
$variantClasses[$variant] ?? $variantClasses['default'],
'gap-1' => $icon || $dot,
]) }} style="{{ $size === 'lg' ? 'font-size: 0.9em; padding: 0.5em 0.8em;' : '' }}">
'gap-1.5' => $icon || $dot,
]) }}>
@if($dot)
<span class="relative flex {{ $dotSize }}">
@if($pulse)

View File

@@ -1,6 +1,6 @@
{{--
Button Component - AdminLTE Theme
Based on AdminLTE v4 Button styling patterns
Button Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.button>Click me</x-proxmox::ui.button>
@@ -26,25 +26,37 @@
@php
$tag = $href ? 'a' : 'button';
// AdminLTE button classes
// LucentUI button classes
$variantClasses = [
'primary' => $outline ? 'lte-btn-outline-primary' : 'lte-btn-primary',
'secondary' => $outline ? 'lte-btn-outline-secondary' : 'lte-btn-secondary',
'danger' => $outline ? 'lte-btn-outline-danger' : 'lte-btn-danger',
'success' => $outline ? 'lte-btn-outline-success' : 'lte-btn-success',
'warning' => 'lte-btn-warning',
'info' => 'lte-btn-info',
'ghost' => 'lte-btn-outline-secondary border-0',
'link' => 'lte-text-primary',
'primary' => $outline
? 'border border-primary text-primary bg-transparent hover:bg-primary/10 focus:ring-2 focus:ring-primary/50'
: 'bg-primary text-white hover:bg-primary/80 focus:ring-2 focus:ring-primary/50',
'secondary' => $outline
? 'border border-neutral text-base/80 bg-transparent hover:bg-neutral/10 focus:ring-2 focus:ring-neutral/50'
: 'bg-background-secondary text-base/80 border border-neutral hover:bg-background-secondary/80 focus:ring-2 focus:ring-neutral/50',
'danger' => $outline
? 'border border-error text-error bg-transparent hover:bg-error/10 focus:ring-2 focus:ring-error/50'
: 'bg-error text-white hover:bg-error/80 focus:ring-2 focus:ring-error/50',
'success' => $outline
? 'border border-success text-success bg-transparent hover:bg-success/10 focus:ring-2 focus:ring-success/50'
: 'bg-success text-white hover:bg-success/80 focus:ring-2 focus:ring-success/50',
'warning' => $outline
? 'border border-warning text-warning bg-transparent hover:bg-warning/10 focus:ring-2 focus:ring-warning/50'
: 'bg-warning text-white hover:bg-warning/80 focus:ring-2 focus:ring-warning/50',
'info' => $outline
? 'border border-info text-info bg-transparent hover:bg-info/10 focus:ring-2 focus:ring-info/50'
: 'bg-info text-white hover:bg-info/80 focus:ring-2 focus:ring-info/50',
'ghost' => 'text-base/80 bg-transparent hover:bg-neutral/10 focus:ring-2 focus:ring-neutral/50',
'link' => 'text-primary bg-transparent hover:underline p-0',
];
$sizeClasses = [
'xs' => 'lte-btn-sm',
'sm' => 'lte-btn-sm',
'md' => '',
'lg' => 'lte-btn-lg',
'xl' => 'lte-btn-lg',
'icon' => 'lte-btn-sm',
'xs' => 'px-2 py-1 text-xs',
'sm' => 'px-3 py-1.5 text-sm',
'md' => 'px-4 py-2 text-sm',
'lg' => 'px-5 py-2.5 text-base',
'xl' => 'px-6 py-3 text-base',
'icon' => 'p-2',
];
$iconSizes = [
@@ -64,14 +76,14 @@
@if($tag === 'button') type="{{ $type }}" @endif
@if($disabled || $loading) disabled @endif
{{ $attributes->class([
'lte-btn',
'inline-flex items-center justify-center gap-2 font-semibold rounded-lg transition-all duration-200 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50 active:scale-95',
$variantClasses[$variant] ?? $variantClasses['secondary'],
$sizeClasses[$size] ?? '',
$sizeClasses[$size] ?? $sizeClasses['md'],
$full ? 'w-full' : '',
]) }}
>
@if($loading)
<span class="lte-spinner lte-spinner-sm me-2" role="status" aria-hidden="true"></span>
<x-ri-loader-5-fill class="{{ $iconSize }} animate-spin" />
<span>{{ $loadingText ?? $slot }}</span>
@else
@if($icon)

View File

@@ -1,6 +1,6 @@
{{--
Card Component - AdminLTE Theme
Based on AdminLTE v4 Card styling patterns
Card Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.card>Content</x-proxmox::ui.card>
@@ -13,7 +13,7 @@
'title' => null,
'subtitle' => null,
'icon' => null,
'iconColor' => 'lte-text-primary',
'iconColor' => 'text-primary',
'actions' => null,
'footer' => null,
'footerClass' => '',
@@ -28,43 +28,43 @@
@php
$variantClasses = match($variant) {
'primary' => $outline ? 'lte-card-outline-primary' : 'lte-card-primary',
'success' => $outline ? 'lte-card-outline-success' : 'lte-card-success',
'warning' => $outline ? 'lte-card-outline-warning' : 'lte-card-warning',
'danger' => $outline ? 'lte-card-outline-danger' : 'lte-card-danger',
'info' => $outline ? 'lte-card-outline-info' : 'lte-card-info',
default => '',
'primary' => $outline ? 'border-primary' : 'border-primary bg-primary/5',
'success' => $outline ? 'border-success' : 'border-success bg-success/5',
'warning' => $outline ? 'border-warning' : 'border-warning bg-warning/5',
'danger' => $outline ? 'border-error' : 'border-error bg-error/5',
'info' => $outline ? 'border-info' : 'border-info bg-info/5',
default => 'border-neutral',
};
@endphp
<div
{{ $attributes->class([
'lte-card lte-slide-in-up transition-all duration-300 ease-out hover:shadow-md',
'bg-background border rounded-lg shadow-sm transition-all duration-300 ease-out hover:shadow-md',
$variantClasses,
'cursor-pointer hover:-translate-y-0.5 hover:scale-[1.002]' => $clickable,
'hover:border-gray-300 dark:hover:border-gray-600' => $hover,
'hover:border-neutral/80' => $hover,
]) }}
@if($collapsible)
x-data="{ isCollapsed: {{ $collapsed ? 'true' : 'false' }} }"
@endif
>
@if($title || $actions)
<div class="lte-card-header transition-colors duration-200 hover:bg-gray-50/50 dark:hover:bg-gray-800/30">
<h3 class="lte-card-title">
<div class="flex items-center justify-between px-4 py-3 border-b border-neutral transition-colors duration-200 hover:bg-background-secondary/30">
<h3 class="flex items-center gap-3 text-base font-semibold text-base">
@if($icon)
<span class="lte-info-box-icon lte-bg-light rounded transition-transform duration-200 hover:scale-110" style="width: 36px; height: 36px; font-size: 1rem;">
<span class="flex items-center justify-center size-9 bg-background-secondary rounded transition-transform duration-200 hover:scale-110">
<x-dynamic-component :component="$icon" class="size-5 {{ $iconColor }}" />
</span>
@endif
<span>
{{ $title }}
@if($subtitle)
<small class="lte-text-muted d-block transition-opacity duration-200" style="font-size: 0.8rem; font-weight: normal;">{{ $subtitle }}</small>
<small class="block text-xs font-normal text-base/60 transition-opacity duration-200">{{ $subtitle }}</small>
@endif
</span>
</h3>
@if($actions || $collapsible)
<div class="lte-card-tools">
<div class="flex items-center gap-2">
@if($actions)
{{ $actions }}
@endif
@@ -72,8 +72,7 @@
<button
type="button"
@click="isCollapsed = !isCollapsed"
class="lte-btn lte-btn-sm lte-btn-outline-secondary transition-all duration-200 hover:scale-105"
style="padding: 0.25rem 0.5rem;"
class="inline-flex items-center justify-center px-2 py-1 text-sm border border-neutral rounded bg-transparent text-base/80 hover:bg-neutral/10 hover:scale-105 transition-all duration-200"
>
<x-ri-arrow-down-s-line
class="size-4 transition-transform duration-300 ease-out"
@@ -87,7 +86,7 @@
@endif
<div
class="lte-card-body"
class="p-4"
@if($collapsible)
x-show="!isCollapsed"
x-transition:enter="transition ease-out duration-300"
@@ -102,7 +101,7 @@
</div>
@if($footer)
<div class="lte-card-footer {{ $footerClass }}">
<div class="px-4 py-3 border-t border-neutral bg-background-secondary/50 rounded-b-lg {{ $footerClass }}">
{{ $footer }}
</div>
@endif

View File

@@ -1,5 +1,6 @@
{{--
Confirm Dialog Component - AdminLTE style
Confirm Dialog Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
For dangerous actions like delete, stop, etc.
Usage:
@@ -26,142 +27,27 @@
$variants = [
'danger' => [
'icon' => 'ri-error-warning-line',
'iconBg' => 'background-color: rgba(220, 53, 69, 0.1);',
'iconColor' => 'color: var(--lte-danger, #dc3545);',
'buttonStyle' => 'background-color: var(--lte-danger, #dc3545); color: #fff;',
'buttonHover' => 'background-color: #bb2d3b;',
'iconBg' => 'bg-error/10',
'iconColor' => 'text-error',
'buttonClass' => 'bg-error text-white hover:bg-error/80',
],
'warning' => [
'icon' => 'ri-alert-line',
'iconBg' => 'background-color: rgba(255, 193, 7, 0.15);',
'iconColor' => 'color: #997404;',
'buttonStyle' => 'background-color: var(--lte-warning, #ffc107); color: #000;',
'buttonHover' => 'background-color: #ffca2c;',
'iconBg' => 'bg-warning/15',
'iconColor' => 'text-warning',
'buttonClass' => 'bg-warning text-black hover:bg-warning/80',
],
'primary' => [
'icon' => 'ri-question-line',
'iconBg' => 'background-color: rgba(13, 110, 253, 0.1);',
'iconColor' => 'color: var(--lte-primary, #0d6efd);',
'buttonStyle' => 'background-color: var(--lte-primary, #0d6efd); color: #fff;',
'buttonHover' => 'background-color: #0b5ed7;',
'iconBg' => 'bg-primary/10',
'iconColor' => 'text-primary',
'buttonClass' => 'bg-primary text-white hover:bg-primary/80',
],
];
$config = $variants[$variant] ?? $variants['danger'];
@endphp
<style>
.confirm-dialog-lte-backdrop {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
z-index: 99998;
}
.confirm-dialog-lte-container {
position: fixed;
inset: 0;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.confirm-dialog-lte {
position: relative;
width: 100%;
max-width: 28rem;
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
padding: 1.5rem;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175);
}
.confirm-dialog-lte-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.confirm-dialog-lte-icon {
display: flex;
align-items: center;
justify-content: center;
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
margin-bottom: 1.25rem;
}
.confirm-dialog-lte-icon svg {
width: 1.75rem;
height: 1.75rem;
}
.confirm-dialog-lte-title {
font-size: 1.125rem;
font-weight: 600;
color: #212529;
margin-bottom: 0.5rem;
}
.confirm-dialog-lte-message {
font-size: 0.875rem;
color: var(--lte-secondary, #6c757d);
margin-bottom: 1.5rem;
}
.confirm-dialog-lte-actions {
display: flex;
width: 100%;
gap: 0.75rem;
}
.confirm-dialog-lte-btn {
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.625rem 1rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--lte-border-radius, 0.375rem);
border: none;
cursor: pointer;
transition: all var(--lte-transition-speed, 0.3s) ease;
}
.confirm-dialog-lte-btn-cancel {
background-color: #fff;
color: #495057;
border: 1px solid var(--lte-border-color, #dee2e6);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.confirm-dialog-lte-btn-cancel:hover {
background-color: #f8f9fa;
border-color: #adb5bd;
}
.confirm-dialog-lte-btn-confirm {
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.confirm-dialog-lte-btn-confirm:hover {
opacity: 0.9;
}
/* Dark mode */
.dark .confirm-dialog-lte {
background-color: #1f2937;
border-color: #374151;
}
.dark .confirm-dialog-lte-title {
color: #f3f4f6;
}
.dark .confirm-dialog-lte-message {
color: #9ca3af;
}
.dark .confirm-dialog-lte-btn-cancel {
background-color: #374151;
color: #d1d5db;
border-color: #4b5563;
}
.dark .confirm-dialog-lte-btn-cancel:hover {
background-color: #4b5563;
}
</style>
<div
x-data="{ open: @js($open) }"
x-show="open"
@@ -179,11 +65,11 @@
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
class="confirm-dialog-lte-backdrop"
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-[99998]"
></div>
<!-- Dialog Container -->
<div class="confirm-dialog-lte-container">
<div class="fixed inset-0 z-[99999] flex items-center justify-center p-4">
<div
x-show="open"
x-transition:enter="transition ease-out duration-200"
@@ -192,26 +78,26 @@
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="confirm-dialog-lte"
class="relative w-full max-w-md bg-background border border-neutral rounded-lg p-6 shadow-xl"
>
<div class="confirm-dialog-lte-content">
<div class="flex flex-col items-center text-center">
<!-- Icon -->
<div class="confirm-dialog-lte-icon" style="{{ $config['iconBg'] }}">
<x-dynamic-component :component="$config['icon']" style="{{ $config['iconColor'] }}" />
<div class="flex items-center justify-center size-14 rounded-full mb-5 {{ $config['iconBg'] }}">
<x-dynamic-component :component="$config['icon']" class="size-7 {{ $config['iconColor'] }}" />
</div>
<!-- Title -->
<h3 class="confirm-dialog-lte-title">{{ $title }}</h3>
<h3 class="text-lg font-semibold text-base mb-2">{{ $title }}</h3>
<!-- Message -->
<p class="confirm-dialog-lte-message">{{ $message }}</p>
<p class="text-sm text-base/60 mb-6">{{ $message }}</p>
<!-- Actions -->
<div class="confirm-dialog-lte-actions">
<div class="flex w-full gap-3">
<button
type="button"
@click="open = false"
class="confirm-dialog-lte-btn confirm-dialog-lte-btn-cancel"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium rounded-lg border border-neutral bg-background-secondary text-base/80 hover:bg-neutral/20 transition-all duration-200 shadow-sm"
>
{{ $cancelLabel }}
</button>
@@ -219,8 +105,7 @@
type="button"
@click="open = false"
{{ $attributes->whereStartsWith('wire:click') }}
class="confirm-dialog-lte-btn confirm-dialog-lte-btn-confirm"
style="{{ $config['buttonStyle'] }}"
class="flex-1 inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium rounded-lg transition-all duration-200 shadow-sm {{ $config['buttonClass'] }}"
>
{{ $confirmLabel }}
</button>

View File

@@ -1,5 +1,6 @@
{{--
Copy Button Component - AdminLTE style
Copy Button Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Small inline button to copy text to clipboard
Usage:
@@ -10,40 +11,6 @@
'value',
])
<style>
.copy-btn-lte {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
width: 1.25rem;
height: 1.25rem;
padding: 0;
background: transparent;
border: none;
color: #adb5bd;
cursor: pointer;
transition: color var(--lte-transition-speed, 0.3s) ease;
}
.copy-btn-lte:hover {
color: var(--lte-primary, #0d6efd);
}
.copy-btn-lte svg {
width: 1rem;
height: 1rem;
position: absolute;
}
.copy-btn-lte-success {
color: var(--lte-success, #198754);
}
.dark .copy-btn-lte {
color: #6b7280;
}
.dark .copy-btn-lte:hover {
color: var(--lte-primary, #0d6efd);
}
</style>
<div
x-data="{
copied: false,
@@ -54,17 +21,17 @@
setTimeout(() => this.copied = false, 2000);
}
}"
style="display: inline-flex; align-items: center;"
class="inline-flex items-center"
{{ $attributes }}
>
<button
@click="copyToClipboard()"
type="button"
class="copy-btn-lte"
:class="copied ? 'copy-btn-lte-success' : ''"
class="relative inline-flex items-center justify-center size-5 p-0 bg-transparent border-none text-base/50 cursor-pointer transition-colors duration-200 hover:text-primary"
:class="copied ? 'text-success' : ''"
title="Copy to clipboard"
>
<x-ri-check-fill x-show="copied" x-transition x-cloak />
<x-ri-file-copy-line x-show="!copied" x-transition />
<x-ri-check-fill class="size-4 absolute" x-show="copied" x-transition x-cloak />
<x-ri-file-copy-line class="size-4 absolute" x-show="!copied" x-transition />
</button>
</div>

View File

@@ -1,5 +1,6 @@
{{--
Copyable Text Component - AdminLTE style
Copyable Text Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Text field with copy to clipboard functionality
Usage:
@@ -12,84 +13,9 @@
'masked' => false,
])
<style>
.copyable-lte-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.copyable-lte-label {
font-size: 0.875rem;
font-weight: 500;
color: #495057;
}
.copyable-lte-field {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.625rem 1rem;
background-color: #f8f9fa;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
font-size: 0.875rem;
transition: border-color var(--lte-transition-speed, 0.3s) ease;
}
.copyable-lte-value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
color: #212529;
}
.copyable-lte-btn {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
width: 1.25rem;
height: 1.25rem;
flex-shrink: 0;
padding: 0;
background: transparent;
border: none;
color: #adb5bd;
cursor: pointer;
transition: color var(--lte-transition-speed, 0.3s) ease;
}
.copyable-lte-btn:hover {
color: var(--lte-primary, #0d6efd);
}
.copyable-lte-btn svg {
width: 1rem;
height: 1rem;
position: absolute;
}
.copyable-lte-btn-success {
color: var(--lte-success, #198754);
}
/* Dark mode */
.dark .copyable-lte-label {
color: #d1d5db;
}
.dark .copyable-lte-field {
background-color: #1f2937;
border-color: #374151;
}
.dark .copyable-lte-value {
color: #e5e7eb;
}
.dark .copyable-lte-btn {
color: #6b7280;
}
.dark .copyable-lte-btn:hover {
color: var(--lte-primary, #0d6efd);
}
</style>
<div class="copyable-lte-group">
<div class="flex flex-col gap-2">
@if($label)
<label class="copyable-lte-label">{{ $label }}</label>
<label class="text-sm font-medium text-base/80">{{ $label }}</label>
@endif
<div
x-data="{
@@ -102,31 +28,31 @@
setTimeout(() => this.copied = false, 2000);
}
}"
class="copyable-lte-field"
class="flex items-center gap-2.5 px-4 py-2.5 bg-background-secondary border border-neutral rounded-lg text-sm transition-colors duration-200"
>
@if($masked)
<span class="copyable-lte-value" x-text="showValue ? copyText : '••••••••••••'"></span>
<span class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-base" x-text="showValue ? copyText : '••••••••••••'"></span>
<button
@click="showValue = !showValue"
type="button"
class="copyable-lte-btn"
class="relative inline-flex items-center justify-center size-5 shrink-0 p-0 bg-transparent border-none text-base/50 cursor-pointer transition-colors duration-200 hover:text-primary"
:aria-label="showValue ? 'Hide value' : 'Show value'"
>
<x-ri-eye-line x-show="!showValue" />
<x-ri-eye-off-line x-show="showValue" x-cloak />
<x-ri-eye-line class="size-4 absolute" x-show="!showValue" />
<x-ri-eye-off-line class="size-4 absolute" x-show="showValue" x-cloak />
</button>
@else
<span class="copyable-lte-value">{{ $value }}</span>
<span class="flex-1 overflow-hidden text-ellipsis whitespace-nowrap font-mono text-base">{{ $value }}</span>
@endif
<button
@click="copyToClipboard()"
type="button"
class="copyable-lte-btn"
:class="copied ? 'copyable-lte-btn-success' : ''"
class="relative inline-flex items-center justify-center size-5 shrink-0 p-0 bg-transparent border-none text-base/50 cursor-pointer transition-colors duration-200 hover:text-primary"
:class="copied ? 'text-success' : ''"
aria-label="Copy to clipboard"
>
<x-ri-check-fill x-show="copied" x-transition x-cloak />
<x-ri-file-copy-line x-show="!copied" x-transition />
<x-ri-check-fill class="size-4 absolute" x-show="copied" x-transition x-cloak />
<x-ri-file-copy-line class="size-4 absolute" x-show="!copied" x-transition />
</button>
</div>
</div>

View File

@@ -1,17 +1,6 @@
{{--
Dropdown Divider Component - AdminLTE style
Dropdown Divider Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
<style>
.dropdown-divider-lte {
height: 0;
margin: 0.375rem 0;
overflow: hidden;
border-top: 1px solid var(--lte-border-color, #dee2e6);
}
.dark .dropdown-divider-lte {
border-top-color: #374151;
}
</style>
<hr class="dropdown-divider-lte">
<hr class="h-0 my-1.5 overflow-hidden border-t border-neutral">

View File

@@ -1,5 +1,6 @@
{{--
Dropdown Item Component - AdminLTE style
Dropdown Item Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
@props([
@@ -11,82 +12,30 @@
@php
$tag = $href ? 'a' : 'button';
$baseClasses = 'flex items-center gap-2.5 w-full px-3 py-2 text-sm text-left no-underline bg-transparent border-0 rounded-md cursor-pointer transition-colors duration-200';
$variantClasses = match($variant) {
'danger' => 'text-error hover:bg-error/10 focus:bg-error/10',
'success' => 'text-success hover:bg-success/10 focus:bg-success/10',
default => 'text-primary hover:bg-primary/5 hover:text-primary-hover focus:bg-primary/5 focus:text-primary-hover dark:text-gray-200 dark:hover:bg-white/5',
};
$disabledClasses = $disabled ? 'pointer-events-none opacity-50' : '';
@endphp
<style>
.dropdown-item-lte {
display: flex;
align-items: center;
gap: 0.625rem;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
color: #212529;
text-align: left;
text-decoration: none;
background-color: transparent;
border: 0;
border-radius: calc(var(--lte-border-radius, 0.375rem) - 2px);
cursor: pointer;
transition: background-color var(--lte-transition-speed, 0.3s) ease, color var(--lte-transition-speed, 0.3s) ease;
}
.dropdown-item-lte:hover,
.dropdown-item-lte:focus {
background-color: rgba(0, 0, 0, 0.04);
color: var(--lte-primary, #0d6efd);
outline: none;
}
.dropdown-item-lte-danger {
color: var(--lte-danger, #dc3545);
}
.dropdown-item-lte-danger:hover,
.dropdown-item-lte-danger:focus {
background-color: rgba(220, 53, 69, 0.08);
color: var(--lte-danger, #dc3545);
}
.dropdown-item-lte-success {
color: var(--lte-success, #198754);
}
.dropdown-item-lte-success:hover,
.dropdown-item-lte-success:focus {
background-color: rgba(25, 135, 84, 0.08);
color: var(--lte-success, #198754);
}
.dropdown-item-lte-disabled {
pointer-events: none;
opacity: 0.5;
}
.dropdown-item-lte-icon {
flex-shrink: 0;
width: 1.125rem;
height: 1.125rem;
}
.dropdown-item-lte-text {
flex: 1;
}
/* Dark mode */
.dark .dropdown-item-lte {
color: #e5e7eb;
}
.dark .dropdown-item-lte:hover,
.dark .dropdown-item-lte:focus {
background-color: rgba(255, 255, 255, 0.04);
}
</style>
<{{ $tag }}
@if($href) href="{{ $href }}" @endif
@if($tag === 'button') type="button" @endif
@if($disabled) disabled @endif
{{ $attributes->class([
'dropdown-item-lte',
'dropdown-item-lte-danger' => $variant === 'danger',
'dropdown-item-lte-success' => $variant === 'success',
'dropdown-item-lte-disabled' => $disabled,
$baseClasses,
$variantClasses,
$disabledClasses,
]) }}
>
@if($icon)
<x-dynamic-component :component="$icon" class="dropdown-item-lte-icon" />
<x-dynamic-component :component="$icon" class="shrink-0 w-4.5 h-4.5" />
@endif
<span class="dropdown-item-lte-text">{{ $slot }}</span>
<span class="flex-1">{{ $slot }}</span>
</{{ $tag }}>

View File

@@ -1,5 +1,6 @@
{{--
Dropdown Component - AdminLTE style
Dropdown Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.dropdown>
@@ -21,50 +22,23 @@
])
@php
$alignStyles = [
'left' => 'left: 0; transform-origin: top left;',
'right' => 'right: 0; transform-origin: top right;',
'center' => 'left: 50%; transform: translateX(-50%); transform-origin: top center;',
$alignClasses = [
'left' => 'left-0 origin-top-left',
'right' => 'right-0 origin-top-right',
'center' => 'left-1/2 -translate-x-1/2 origin-top',
];
$widthStyles = [
'32' => 'width: 8rem;',
'40' => 'width: 10rem;',
'48' => 'width: 12rem;',
'56' => 'width: 14rem;',
'64' => 'width: 16rem;',
'80' => 'width: 20rem;',
$widthClasses = [
'32' => 'w-32',
'40' => 'w-40',
'48' => 'w-48',
'56' => 'w-56',
'64' => 'w-64',
'80' => 'w-80',
];
@endphp
<style>
.dropdown-lte {
position: relative;
display: inline-block;
}
.dropdown-menu-lte {
position: absolute;
z-index: 1000;
margin-top: 0.5rem;
min-width: 10rem;
padding: 0.5rem;
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
}
.dropdown-menu-lte[x-cloak] {
display: none !important;
}
/* Dark mode */
.dark .dropdown-menu-lte {
background-color: #1f2937;
border-color: #374151;
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.3);
}
</style>
<div class="dropdown-lte" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
<div class="relative inline-block" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
<div @click="open = !open">
{{ $trigger }}
</div>
@@ -77,8 +51,7 @@
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="dropdown-menu-lte {{ $contentClasses }}"
style="{{ $widthStyles[$width] ?? 'width: 12rem;' }} {{ $alignStyles[$align] ?? $alignStyles['right'] }}"
class="absolute z-50 mt-2 min-w-40 p-2 bg-background border border-neutral rounded-lg shadow-lg {{ $widthClasses[$width] ?? 'w-48' }} {{ $alignClasses[$align] ?? $alignClasses['right'] }} {{ $contentClasses }}"
x-cloak
@click="open = false"
>

View File

@@ -1,5 +1,6 @@
{{--
Editable Field Component - AdminLTE style
Editable Field Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
A field with display value and edit button that opens a modal
Usage:
@@ -31,260 +32,22 @@
'error' => null,
])
<style>
.editable-field-lte {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.editable-field-lte-label {
font-size: 0.875rem;
font-weight: 500;
color: #495057;
}
.editable-field-lte-display {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.625rem 1rem;
background-color: #f8f9fa;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
font-size: 0.875rem;
}
.editable-field-lte-value {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #212529;
}
.editable-field-lte-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
background: transparent;
border: none;
color: #adb5bd;
cursor: pointer;
transition: color var(--lte-transition-speed, 0.3s) ease;
}
.editable-field-lte-btn:hover {
color: var(--lte-primary, #0d6efd);
}
.editable-field-lte-btn svg {
width: 1rem;
height: 1rem;
}
.editable-field-lte-backdrop {
position: fixed;
inset: 0;
z-index: 99998;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.editable-field-lte-modal-container {
position: fixed;
inset: 0;
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.editable-field-lte-modal {
position: relative;
width: 100%;
max-width: 36rem;
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
padding: 1.5rem;
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175);
}
.editable-field-lte-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.25rem;
}
.editable-field-lte-modal-title {
font-size: 1.125rem;
font-weight: 600;
color: #212529;
}
.editable-field-lte-modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 2.25rem;
height: 2.25rem;
padding: 0;
background: transparent;
border: none;
border-radius: var(--lte-border-radius, 0.375rem);
color: var(--lte-secondary, #6c757d);
cursor: pointer;
transition: all var(--lte-transition-speed, 0.3s) ease;
}
.editable-field-lte-modal-close:hover {
background-color: #f8f9fa;
color: #212529;
}
.editable-field-lte-modal-close svg {
width: 1.25rem;
height: 1.25rem;
}
.editable-field-lte-modal-body {
display: flex;
flex-direction: column;
gap: 1rem;
}
.editable-field-lte-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.editable-field-lte-input-label {
font-size: 0.875rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
}
.editable-field-lte-input {
width: 100%;
height: 2.75rem;
padding: 0 1rem;
font-size: 0.875rem;
color: #212529;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: var(--lte-border-radius, 0.375rem);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.editable-field-lte-input:focus {
border-color: #86b7fe;
outline: 0;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.editable-field-lte-input::placeholder {
color: #adb5bd;
}
.editable-field-lte-error {
font-size: 0.875rem;
color: var(--lte-danger, #dc3545);
}
.editable-field-lte-hint {
font-size: 0.8125rem;
color: var(--lte-secondary, #6c757d);
}
.editable-field-lte-modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding-top: 1rem;
margin-top: 0.5rem;
border-top: 1px solid var(--lte-border-color, #dee2e6);
}
.editable-field-lte-modal-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.625rem 1rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--lte-border-radius, 0.375rem);
border: none;
cursor: pointer;
transition: all var(--lte-transition-speed, 0.3s) ease;
}
.editable-field-lte-modal-btn-cancel {
background-color: #fff;
color: #495057;
border: 1px solid var(--lte-border-color, #dee2e6);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.editable-field-lte-modal-btn-cancel:hover {
background-color: #f8f9fa;
border-color: #adb5bd;
}
.editable-field-lte-modal-btn-save {
background-color: var(--lte-primary, #0d6efd);
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.editable-field-lte-modal-btn-save:hover {
background-color: #0b5ed7;
}
/* Dark mode */
.dark .editable-field-lte-label {
color: #d1d5db;
}
.dark .editable-field-lte-display {
background-color: #1f2937;
border-color: #374151;
}
.dark .editable-field-lte-value {
color: #e5e7eb;
}
.dark .editable-field-lte-btn {
color: #6b7280;
}
.dark .editable-field-lte-modal {
background-color: #1f2937;
border-color: #374151;
}
.dark .editable-field-lte-modal-title {
color: #f3f4f6;
}
.dark .editable-field-lte-modal-close {
color: #9ca3af;
}
.dark .editable-field-lte-modal-close:hover {
background-color: #374151;
color: #f3f4f6;
}
.dark .editable-field-lte-input {
background-color: #111827;
border-color: #374151;
color: #f3f4f6;
}
.dark .editable-field-lte-input:focus {
border-color: var(--lte-primary, #0d6efd);
}
.dark .editable-field-lte-input::placeholder {
color: #6b7280;
}
.dark .editable-field-lte-modal-footer {
border-top-color: #374151;
}
.dark .editable-field-lte-modal-btn-cancel {
background-color: #374151;
color: #d1d5db;
border-color: #4b5563;
}
.dark .editable-field-lte-modal-btn-cancel:hover {
background-color: #4b5563;
}
</style>
@php
$entangleProperty = $attributes->wire('model')->value() ? ('isEditing' . ucfirst($label)) : 'editing';
@endphp
<div class="editable-field-lte" x-data="{ showEditModal: @entangle($entangleProperty) }">
<label class="editable-field-lte-label">{{ $label }}</label>
<div class="editable-field-lte-display">
<span class="editable-field-lte-value">{{ $value ?? 'Not set' }}</span>
<div class="flex flex-col gap-2" x-data="{ showEditModal: @entangle($entangleProperty) }">
<label class="text-sm font-medium text-secondary dark:text-gray-300">{{ $label }}</label>
<div class="flex items-center gap-2.5 px-4 py-2.5 bg-background-secondary dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-lg text-sm">
<span class="flex-1 truncate text-primary dark:text-gray-200">{{ $value ?? 'Not set' }}</span>
@if($editClick)
<button
wire:click="{{ $editClick }}"
type="button"
class="editable-field-lte-btn"
class="inline-flex items-center justify-center p-0 bg-transparent border-none text-secondary/60 dark:text-gray-500 hover:text-primary dark:hover:text-primary transition-colors duration-200 cursor-pointer"
aria-label="Edit {{ $label }}"
>
<x-ri-edit-line />
<x-ri-edit-line class="w-4 h-4" />
</button>
@endif
</div>
@@ -302,7 +65,7 @@
x-transition:leave-end="opacity-0"
@click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
@keydown.escape.window="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
class="editable-field-lte-backdrop"
class="fixed inset-0 z-[99998] bg-black/50 backdrop-blur-sm"
></div>
<!-- Edit Modal -->
@@ -316,47 +79,47 @@
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.away="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
class="editable-field-lte-modal-container"
class="fixed inset-0 z-[99999] flex items-center justify-center p-4"
>
<div class="editable-field-lte-modal">
<div class="editable-field-lte-modal-header">
<h4 class="editable-field-lte-modal-title">Edit {{ $label }}</h4>
<div class="relative w-full max-w-xl bg-background dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-lg p-6 shadow-xl">
<div class="flex items-center justify-between mb-5">
<h4 class="text-lg font-semibold text-primary dark:text-gray-100">Edit {{ $label }}</h4>
<button
type="button"
class="editable-field-lte-modal-close"
class="flex items-center justify-center w-9 h-9 p-0 bg-transparent border-none rounded-lg text-secondary dark:text-gray-400 hover:bg-background-secondary dark:hover:bg-gray-700 hover:text-primary dark:hover:text-gray-100 transition-all duration-200 cursor-pointer"
@click="showEditModal = false; @if($cancelClick) $wire.{{ $cancelClick }}() @endif"
aria-label="Close"
>
<x-ri-close-line />
<x-ri-close-line class="w-5 h-5" />
</button>
</div>
<div class="editable-field-lte-modal-body">
<div class="editable-field-lte-input-group">
<label class="editable-field-lte-input-label">{{ $label }}</label>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<label class="text-sm font-medium text-secondary dark:text-gray-400">{{ $label }}</label>
<input
type="text"
{{ $attributes->whereStartsWith('wire:model') }}
class="editable-field-lte-input"
class="w-full h-11 px-4 text-sm text-primary dark:text-gray-100 bg-background dark:bg-gray-900 border border-neutral dark:border-gray-600 rounded-lg shadow-sm focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/25 placeholder:text-secondary/50 dark:placeholder:text-gray-500 transition-all duration-150"
placeholder="{{ $placeholder ?? 'Enter ' . strtolower($label) }}"
/>
</div>
@if($error)
<div class="editable-field-lte-error">{{ $error }}</div>
<div class="text-sm text-error">{{ $error }}</div>
@endif
@if(isset($hint))
<div class="editable-field-lte-hint">{{ $hint }}</div>
<div class="text-xs text-secondary dark:text-gray-400">{{ $hint }}</div>
@endif
</div>
<div class="editable-field-lte-modal-footer">
<div class="flex justify-end gap-3 pt-4 mt-2 border-t border-neutral dark:border-gray-700">
@if($cancelClick)
<button
type="button"
@click="showEditModal = false; $wire.{{ $cancelClick }}()"
class="editable-field-lte-modal-btn editable-field-lte-modal-btn-cancel"
class="inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium bg-background dark:bg-gray-700 text-secondary dark:text-gray-300 border border-neutral dark:border-gray-600 rounded-lg shadow-sm hover:bg-background-secondary dark:hover:bg-gray-600 transition-all duration-200 cursor-pointer"
>
Cancel
</button>
@@ -365,7 +128,7 @@
<button
type="button"
wire:click="{{ $saveClick }}"
class="editable-field-lte-modal-btn editable-field-lte-modal-btn-save"
class="inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium bg-primary text-white rounded-lg shadow-sm hover:bg-primary-hover transition-all duration-200 cursor-pointer"
>
Save
</button>
@@ -374,7 +137,7 @@
<button
type="button"
wire:click="{{ $applyClick }}"
class="editable-field-lte-modal-btn editable-field-lte-modal-btn-save"
class="inline-flex items-center justify-center px-4 py-2.5 text-sm font-medium bg-primary text-white rounded-lg shadow-sm hover:bg-primary-hover transition-all duration-200 cursor-pointer"
>
{{ $applyLabel }}
</button>

View File

@@ -1,5 +1,6 @@
{{--
Empty State Component - AdminLTE style
Empty State Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.empty-state title="No servers found" />
@@ -20,121 +21,36 @@
'compact' => false,
])
<style>
.empty-state-lte {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.empty-state-lte-regular {
padding: 3rem 1.5rem;
}
.empty-state-lte-compact {
padding: 1.5rem 1rem;
}
.empty-state-lte-icon {
display: flex;
align-items: center;
justify-content: center;
background-color: #f8f9fa;
border-radius: 50%;
}
.empty-state-lte-icon-regular {
width: 4rem;
height: 4rem;
margin-bottom: 1.25rem;
}
.empty-state-lte-icon-compact {
width: 2.75rem;
height: 2.75rem;
margin-bottom: 0.75rem;
}
.empty-state-lte-icon svg {
color: #adb5bd;
}
.empty-state-lte-icon-regular svg {
width: 2rem;
height: 2rem;
}
.empty-state-lte-icon-compact svg {
width: 1.25rem;
height: 1.25rem;
}
.empty-state-lte-title {
font-weight: 600;
color: #212529;
}
.empty-state-lte-title-regular {
font-size: 1.125rem;
}
.empty-state-lte-title-compact {
font-size: 0.875rem;
}
.empty-state-lte-description {
max-width: 24rem;
color: var(--lte-secondary, #6c757d);
}
.empty-state-lte-description-regular {
font-size: 0.875rem;
margin-top: 0.5rem;
}
.empty-state-lte-description-compact {
font-size: 0.8125rem;
margin-top: 0.25rem;
}
.empty-state-lte-actions-regular {
margin-top: 1.25rem;
}
.empty-state-lte-actions-compact {
margin-top: 0.75rem;
}
/* Dark mode */
.dark .empty-state-lte-icon {
background-color: #374151;
}
.dark .empty-state-lte-icon svg {
color: #6b7280;
}
.dark .empty-state-lte-title {
color: #f3f4f6;
}
.dark .empty-state-lte-description {
color: #9ca3af;
}
</style>
<div
{{ $attributes->class([
'empty-state-lte',
'empty-state-lte-regular' => !$compact,
'empty-state-lte-compact' => $compact,
'flex flex-col items-center justify-center text-center',
'py-12 px-6' => !$compact,
'py-6 px-4' => $compact,
]) }}
x-data="{ loaded: false }"
x-init="setTimeout(() => loaded = true, 100)"
:class="loaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'"
style="transition: all 0.5s ease-out;"
class="transition-all duration-500 ease-out"
>
@if($icon)
<div class="empty-state-lte-icon {{ $compact ? 'empty-state-lte-icon-compact' : 'empty-state-lte-icon-regular' }} transition-transform duration-300 hover:scale-110"
<div class="flex items-center justify-center bg-background-secondary rounded-full transition-transform duration-300 hover:scale-110 {{ $compact ? 'size-11 mb-3' : 'size-16 mb-5' }}"
:class="loaded ? 'scale-100' : 'scale-90'"
style="transition: transform 0.5s ease-out 0.1s;">
<x-dynamic-component
:component="$icon"
class="{{ $iconClass ?? '' }}"
class="text-base/50 {{ $iconClass ?? ($compact ? 'size-5' : 'size-8') }}"
/>
</div>
@endif
<h3 class="empty-state-lte-title {{ $compact ? 'empty-state-lte-title-compact' : 'empty-state-lte-title-regular' }}"
<h3 class="font-semibold text-base {{ $compact ? 'text-sm' : 'text-lg' }}"
:class="loaded ? 'opacity-100' : 'opacity-0'"
style="transition: opacity 0.4s ease-out 0.15s;">
{{ $title }}
</h3>
@if($description)
<p class="empty-state-lte-description {{ $compact ? 'empty-state-lte-description-compact' : 'empty-state-lte-description-regular' }}"
<p class="max-w-sm text-base/60 {{ $compact ? 'text-xs mt-1' : 'text-sm mt-2' }}"
:class="loaded ? 'opacity-100' : 'opacity-0'"
style="transition: opacity 0.4s ease-out 0.25s;">
{{ $description }}
@@ -142,7 +58,7 @@
@endif
@if($slot->isNotEmpty())
<div class="{{ $compact ? 'empty-state-lte-actions-compact' : 'empty-state-lte-actions-regular' }}"
<div class="{{ $compact ? 'mt-3' : 'mt-5' }}"
:class="loaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'"
style="transition: all 0.4s ease-out 0.35s;">
{{ $slot }}

View File

@@ -1,5 +1,6 @@
{{--
Icon Picker Component - AdminLTE style
Icon Picker Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Modal-based icon picker with search and categories
Usage:
@@ -40,18 +41,15 @@
<button
type="button"
@click="open = true"
class="flex items-center gap-2"
style="padding: .5rem .75rem; border-radius: .375rem; border: 1px solid #ced4da; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.05); transition: background .2s ease, border-color .2s ease;"
onmouseover="this.style.background='#f8f9fa'; this.style.borderColor='#adb5bd'"
onmouseout="this.style.background='#fff'; this.style.borderColor='#ced4da'"
class="flex items-center gap-2 px-3 py-2 bg-background dark:bg-gray-800 border border-neutral dark:border-gray-600 rounded-lg shadow-sm hover:bg-background-secondary dark:hover:bg-gray-700 hover:border-secondary/40 transition-all duration-200"
>
<template x-if="selected">
<x-dynamic-component x-bind:component="selected" class="size-5" style="color: #0d6efd;" />
<x-dynamic-component x-bind:component="selected" class="size-5 text-primary" />
</template>
<template x-if="!selected">
<x-ri-image-add-line class="size-5" style="color: #6c757d;" />
<x-ri-image-add-line class="size-5 text-secondary dark:text-gray-400" />
</template>
<span style="font-size: .875rem; color: #495057;">Select Icon</span>
<span class="text-sm text-secondary dark:text-gray-300">Select Icon</span>
</button>
<!-- Modal -->
@@ -59,8 +57,7 @@
x-show="open"
x-cloak
@keydown.escape.window="open = false"
class="fixed inset-0 overflow-y-auto"
style="z-index: 1050;"
class="fixed inset-0 z-[1050] overflow-y-auto"
>
<div class="flex items-center justify-center min-h-screen p-4">
<!-- Backdrop -->
@@ -73,8 +70,7 @@
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
class="fixed inset-0"
style="background: rgba(0, 0, 0, 0.5);"
class="fixed inset-0 bg-black/50 backdrop-blur-sm"
></div>
<!-- Modal Content -->
@@ -86,48 +82,47 @@
x-transition:leave="ease-in duration-100"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="modal-lte relative w-full max-w-2xl"
style="background: #fff; border-radius: .375rem; box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15); max-height: 80vh; overflow: hidden;"
class="relative w-full max-w-2xl bg-background dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-lg shadow-xl max-h-[80vh] overflow-hidden"
>
<!-- Header -->
<div style="padding: 1rem; border-bottom: 1px solid #dee2e6;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: .75rem;">
<h3 style="font-size: 1.125rem; font-weight: 600; color: #212529; margin: 0;">Select Icon</h3>
<button @click="open = false" style="padding: .375rem; border-radius: .25rem; color: #6c757d; background: transparent; border: none; cursor: pointer; transition: background .2s ease, color .2s ease;" onmouseover="this.style.background='#f8f9fa'; this.style.color='#495057'" onmouseout="this.style.background='transparent'; this.style.color='#6c757d'">
<div class="p-4 border-b border-neutral dark:border-gray-700">
<div class="flex items-center justify-between mb-3">
<h3 class="text-lg font-semibold text-primary dark:text-gray-100">Select Icon</h3>
<button
@click="open = false"
class="p-1.5 rounded-md text-secondary dark:text-gray-400 bg-transparent border-none hover:bg-background-secondary dark:hover:bg-gray-700 hover:text-primary dark:hover:text-gray-100 transition-all duration-200 cursor-pointer"
>
<x-ri-close-line class="size-5" />
</button>
</div>
<!-- Search -->
<div class="relative">
<x-ri-search-line class="absolute left-3 top-1/2 -translate-y-1/2 size-4" style="color: #6c757d;" />
<x-ri-search-line class="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-secondary dark:text-gray-400" />
<input
type="text"
x-model="search"
placeholder="Search icons..."
class="form-control-lte w-full"
style="padding-left: 2.5rem; padding: .625rem .75rem .625rem 2.5rem; border: 1px solid #ced4da; border-radius: .375rem; font-size: .875rem; color: #212529; background: #fff; transition: border-color .15s ease, box-shadow .15s ease;"
onfocus="this.style.borderColor='#86b7fe'; this.style.boxShadow='0 0 0 .25rem rgba(13, 110, 253, .25)'"
onblur="this.style.borderColor='#ced4da'; this.style.boxShadow='none'"
class="w-full pl-10 pr-3 py-2.5 text-sm text-primary dark:text-gray-100 bg-background dark:bg-gray-900 border border-neutral dark:border-gray-600 rounded-lg placeholder:text-secondary/50 dark:placeholder:text-gray-500 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/25 transition-all duration-150"
/>
</div>
</div>
<!-- Categories -->
@if(count($categories) > 0)
<div style="display: flex; gap: .5rem; padding: 1rem; border-bottom: 1px solid #dee2e6; overflow-x: auto;">
<div class="flex gap-2 p-4 border-b border-neutral dark:border-gray-700 overflow-x-auto">
<button
@click="activeCategory = 'all'"
:style="activeCategory === 'all' ? 'background: #0d6efd; color: #fff;' : 'background: #e9ecef; color: #495057;'"
style="padding: .375rem .75rem; border-radius: 1rem; font-size: .75rem; font-weight: 500; border: none; cursor: pointer; white-space: nowrap; transition: background .2s ease, color .2s ease;"
:class="activeCategory === 'all' ? 'bg-primary text-white' : 'bg-background-secondary dark:bg-gray-700 text-secondary dark:text-gray-300'"
class="px-3 py-1.5 rounded-full text-xs font-medium border-none cursor-pointer whitespace-nowrap transition-all duration-200"
>
All
</button>
@foreach($categories as $category)
<button
@click="activeCategory = '{{ $category['id'] }}'"
:style="activeCategory === '{{ $category['id'] }}' ? 'background: #0d6efd; color: #fff;' : 'background: #e9ecef; color: #495057;'"
style="padding: .375rem .75rem; border-radius: 1rem; font-size: .75rem; font-weight: 500; border: none; cursor: pointer; white-space: nowrap; transition: background .2s ease, color .2s ease;"
:class="activeCategory === '{{ $category['id'] }}' ? 'bg-primary text-white' : 'bg-background-secondary dark:bg-gray-700 text-secondary dark:text-gray-300'"
class="px-3 py-1.5 rounded-full text-xs font-medium border-none cursor-pointer whitespace-nowrap transition-all duration-200"
>
{{ $category['name'] }}
</button>
@@ -136,15 +131,13 @@
@endif
<!-- Icons Grid -->
<div style="padding: 1rem; overflow-y: auto; max-height: 20rem;">
<div class="p-4 overflow-y-auto max-h-80">
<div class="grid grid-cols-8 gap-2">
<template x-for="icon in filteredIcons" :key="icon.name">
<button
@click="selected = icon.component; open = false;"
:style="selected === icon.component ? 'box-shadow: 0 0 0 2px #0d6efd; background: rgba(13, 110, 253, 0.1);' : ''"
style="display: flex; align-items: center; justify-content: center; padding: .75rem; border-radius: .375rem; border: none; background: transparent; cursor: pointer; transition: background .15s ease;"
onmouseover="if(!this.style.boxShadow.includes('#0d6efd')) this.style.background='#f8f9fa'"
onmouseout="if(!this.style.boxShadow.includes('#0d6efd')) this.style.background='transparent'"
:class="selected === icon.component ? 'ring-2 ring-primary bg-primary/10' : 'hover:bg-background-secondary dark:hover:bg-gray-700'"
class="flex items-center justify-center p-3 rounded-lg border-none bg-transparent cursor-pointer transition-all duration-150"
:title="icon.name"
>
<x-dynamic-component x-bind:component="icon.component" class="size-6" />
@@ -153,28 +146,23 @@
</div>
<!-- Empty State -->
<div x-show="filteredIcons.length === 0" style="padding: 2rem; text-align: center;">
<x-ri-search-line style="width: 3rem; height: 3rem; margin: 0 auto .75rem; color: #ced4da;" />
<p style="font-size: .875rem; color: #6c757d; margin: 0;">No icons found matching your search.</p>
<div x-show="filteredIcons.length === 0" class="py-8 text-center">
<x-ri-search-line class="w-12 h-12 mx-auto mb-3 text-neutral" />
<p class="text-sm text-secondary dark:text-gray-400">No icons found matching your search.</p>
</div>
</div>
<!-- Footer -->
<div style="padding: 1rem; border-top: 1px solid #dee2e6; display: flex; align-items: center; justify-content: space-between;">
<div class="p-4 border-t border-neutral dark:border-gray-700 flex items-center justify-between">
<button
@click="selected = null"
style="padding: .375rem .75rem; font-size: .875rem; color: #6c757d; background: transparent; border: none; cursor: pointer; transition: color .2s ease;"
onmouseover="this.style.color='#495057'"
onmouseout="this.style.color='#6c757d'"
class="px-3 py-1.5 text-sm text-secondary dark:text-gray-400 bg-transparent border-none cursor-pointer hover:text-primary dark:hover:text-gray-200 transition-colors duration-200"
>
Clear Selection
</button>
<button
@click="open = false"
class="btn-lte-primary"
style="padding: .625rem 1rem; background: #0d6efd; color: #fff; border: none; border-radius: .375rem; font-size: .875rem; font-weight: 500; cursor: pointer; box-shadow: 0 1px 2px rgba(0,0,0,.05); transition: background .2s ease;"
onmouseover="this.style.background='#0b5ed7'"
onmouseout="this.style.background='#0d6efd'"
class="px-4 py-2.5 bg-primary text-white border-none rounded-lg text-sm font-medium cursor-pointer shadow-sm hover:bg-primary-hover transition-all duration-200"
>
Done
</button>

View File

@@ -3,38 +3,37 @@
===================================
This file documents all available UI components for the Proxmox server views.
All components styled using AdminLTE CSS Framework patterns from:
https://github.com/ColorlibHQ/AdminLTE/releases (v4 - Bootstrap 5)
All components styled using LucentUI Theme with Tailwind CSS.
Migrated from AdminLTE to pure Tailwind utility classes.
AdminLTE Design Patterns:
- Card: card-lte with box-shadow: 0 0 1px rgba(0,0,0,.125), 0 1px 3px rgba(0,0,0,.2)
- Typography: font-size .875rem (14px), font-family "Source Sans Pro"
- Text Colors: #212529 (headings), #6c757d (muted), #495057 (body)
- Layout: Bootstrap grid with gap utilities
- Transitions: .3s for all interactions (--lte-transition-speed)
- Shadows: 0 .5rem 1rem rgba(0,0,0,.15) for modals
- Borders: border-radius .375rem (--lte-border-radius)
LucentUI Design Patterns:
- Card: bg-background border border-neutral rounded-xl shadow-sm
- Typography: text-sm (14px), font-sans
- Text Colors: text-primary (headings), text-secondary (muted)
- Layout: Tailwind flex/grid with gap utilities
- Transitions: duration-200 for interactions
- Shadows: shadow-xl for modals
- Borders: rounded-lg (0.5rem)
AdminLTE Color System (Bootstrap 5):
- Primary: #0d6efd (blue)
- Secondary: #6c757d (gray)
- Success: #198754 (green)
- Danger: #dc3545 (red)
- Warning: #ffc107 (yellow)
- Info: #0dcaf0 (cyan)
LucentUI Color System (Semantic):
- Primary: bg-primary, text-primary, border-primary
- Success: bg-success, text-success
- Error: bg-error, text-error
- Warning: bg-warning, text-warning
- Info: bg-info, text-info
- Neutral: border-neutral, bg-neutral
- Background: bg-background, bg-background-secondary
CSS Classes:
- .card-lte: Card container
- .btn-lte-primary, .btn-lte-secondary, etc.: Buttons
- .alert-lte-info, .alert-lte-success, etc.: Alerts
- .badge-lte-primary, .badge-lte-success, etc.: Badges
- .form-control-lte: Form inputs
- .form-select-lte: Select dropdowns
- .toggle-lte: Toggle switches
- .table-lte: Tables
- .tabs-lte: Tab navigation
- .dropdown-lte: Dropdowns
- .modal-lte: Modals
Tailwind Utility Patterns:
- Cards: bg-background border border-neutral rounded-xl shadow-sm
- Buttons: px-4 py-2 rounded-lg font-medium transition-all duration-200
- Alerts: p-4 rounded-lg border
- Badges: px-2 py-0.5 text-xs font-medium rounded-full
- Inputs: w-full px-4 py-2 border border-neutral rounded-lg focus:ring-2 focus:ring-primary/25
- Tables: w-full divide-y divide-neutral
- Tabs: flex gap-1 border-b border-neutral
- Dropdowns: absolute z-50 bg-background border border-neutral rounded-lg shadow-lg
- Modals: fixed inset-0 z-50 flex items-center justify-center
=== LAYOUT COMPONENTS ===

View File

@@ -1,5 +1,6 @@
{{--
Info Row / Key-Value Pair Component - AdminLTE style
Info Row / Key-Value Pair Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.info-row label="IP Address" value="192.168.1.1" />
@@ -16,93 +17,15 @@
'muted' => false,
])
<style>
.info-row-lte {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px solid var(--lte-border-color, #dee2e6);
}
.info-row-lte:last-child {
border-bottom: none;
}
.info-row-lte-label {
display: flex;
align-items: center;
gap: 0.625rem;
font-size: 0.875rem;
color: var(--lte-secondary, #6c757d);
}
.info-row-lte-label svg {
width: 1rem;
height: 1rem;
}
.info-row-lte-value {
display: flex;
align-items: center;
gap: 0.625rem;
}
.info-row-lte-value-text {
font-size: 0.875rem;
font-weight: 500;
color: #212529;
}
.info-row-lte-value-text-muted {
color: var(--lte-secondary, #6c757d);
}
.info-row-lte-copy-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.75rem;
height: 1.75rem;
padding: 0;
background: transparent;
border: none;
border-radius: var(--lte-border-radius, 0.375rem);
color: #adb5bd;
cursor: pointer;
transition: all var(--lte-transition-speed, 0.3s) ease;
}
.info-row-lte-copy-btn:hover {
background-color: #f8f9fa;
color: var(--lte-primary, #0d6efd);
}
.info-row-lte-copy-btn svg {
width: 1rem;
height: 1rem;
}
/* Dark mode */
.dark .info-row-lte {
border-bottom-color: #374151;
}
.dark .info-row-lte-label {
color: #9ca3af;
}
.dark .info-row-lte-value-text {
color: #f3f4f6;
}
.dark .info-row-lte-value-text-muted {
color: #9ca3af;
}
.dark .info-row-lte-copy-btn {
color: #6b7280;
}
.dark .info-row-lte-copy-btn:hover {
background-color: #374151;
}
</style>
<div {{ $attributes->class(['info-row-lte']) }}>
<div class="info-row-lte-label">
<div {{ $attributes->class(['flex items-center justify-between py-3 border-b border-neutral last:border-b-0']) }}>
<div class="flex items-center gap-2.5 text-sm text-base/60">
@if($icon)
<x-dynamic-component :component="$icon" />
<x-dynamic-component :component="$icon" class="size-4" />
@endif
<span>{{ $label }}</span>
</div>
<div class="info-row-lte-value">
<span class="info-row-lte-value-text {{ $muted ? 'info-row-lte-value-text-muted' : '' }}">
<div class="flex items-center gap-2.5">
<span class="text-sm font-medium {{ $muted ? 'text-base/60' : 'text-base' }}">
{{ $value ?? $slot }}
</span>
@if($copyable && $value)
@@ -114,11 +37,11 @@
copied = true;
setTimeout(() => copied = false, 2000);
"
class="info-row-lte-copy-btn"
class="inline-flex items-center justify-center size-7 p-0 bg-transparent border-none rounded-lg text-base/50 cursor-pointer transition-all duration-200 hover:bg-background-secondary hover:text-primary"
title="Copy to clipboard"
>
<x-ri-file-copy-line x-show="!copied" />
<x-ri-check-line x-show="copied" x-cloak style="color: var(--lte-success, #198754);" />
<x-ri-file-copy-line class="size-4" x-show="!copied" />
<x-ri-check-line class="size-4 text-success" x-show="copied" x-cloak />
</button>
@endif
</div>

View File

@@ -1,6 +1,6 @@
{{--
Input Component - AdminLTE Theme
Based on AdminLTE v4 Form Control styling patterns
Input Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.input name="email" label="Email" />
@@ -25,27 +25,31 @@
@php
$sizeClasses = [
'sm' => 'lte-form-control-sm',
'md' => '',
'lg' => 'lte-form-control-lg',
'sm' => 'h-8 text-xs',
'md' => 'h-10',
'lg' => 'h-12 text-base',
];
$sizeClass = $sizeClasses[$size] ?? '';
$sizeClass = $sizeClasses[$size] ?? $sizeClasses['md'];
$inputClasses = 'w-full px-4 py-2 text-sm text-primary dark:text-gray-100 bg-background dark:bg-gray-900 border border-neutral dark:border-gray-600 rounded-lg placeholder:text-secondary/50 dark:placeholder:text-gray-500 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/25 transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed';
$errorClasses = $error ? 'border-error focus:border-error focus:ring-error/25' : '';
@endphp
<div {{ $attributes->only('class')->class(['mb-3']) }}>
@if($label)
<label @if($name) for="{{ $name }}" @endif class="form-label" style="margin-bottom: 0.5rem; display: block; font-weight: 500;">
<label @if($name) for="{{ $name }}" @endif class="block mb-2 font-medium text-sm text-secondary dark:text-gray-300">
{{ $label }}
@if($required)
<span class="lte-text-danger">*</span>
<span class="text-error">*</span>
@endif
</label>
@endif
@if($icon || $iconRight)
<div class="lte-input-group">
<div class="relative flex items-stretch">
@if($icon)
<span class="lte-input-group-text">
<span class="flex items-center px-3 bg-background-secondary dark:bg-gray-800 border border-r-0 border-neutral dark:border-gray-600 rounded-l-lg text-secondary dark:text-gray-400">
<x-dynamic-component :component="$icon" class="size-4" />
</span>
@endif
@@ -57,11 +61,12 @@
@if($disabled) disabled @endif
@if($readonly) readonly @endif
@if($required) required @endif
{{ $attributes->except('class')->class(['lte-form-control', $sizeClass, 'is-invalid' => $error]) }}
class="{{ $inputClasses }} {{ $sizeClass }} {{ $errorClasses }} {{ $icon ? 'rounded-l-none' : '' }} {{ $iconRight ? 'rounded-r-none' : '' }}"
{{ $attributes->except('class') }}
/>
@if($iconRight)
<span class="lte-input-group-text">
<span class="flex items-center px-3 bg-background-secondary dark:bg-gray-800 border border-l-0 border-neutral dark:border-gray-600 rounded-r-lg text-secondary dark:text-gray-400">
<x-dynamic-component :component="$iconRight" class="size-4" />
</span>
@endif
@@ -74,13 +79,14 @@
@if($disabled) disabled @endif
@if($readonly) readonly @endif
@if($required) required @endif
{{ $attributes->except('class')->class(['lte-form-control', $sizeClass, 'is-invalid' => $error]) }}
class="{{ $inputClasses }} {{ $sizeClass }} {{ $errorClasses }}"
{{ $attributes->except('class') }}
/>
@endif
@if($error)
<div class="invalid-feedback" style="display: block; color: var(--lte-danger); font-size: 0.875rem; margin-top: 0.25rem;">{{ $error }}</div>
<div class="mt-1 text-sm text-error">{{ $error }}</div>
@elseif($hint)
<div class="form-text lte-text-muted" style="font-size: 0.875rem; margin-top: 0.25rem;">{{ $hint }}</div>
<div class="mt-1 text-sm text-secondary dark:text-gray-400">{{ $hint }}</div>
@endif
</div>

View File

@@ -1,6 +1,6 @@
{{--
Modal Component - AdminLTE Theme
Based on AdminLTE v4 Modal styling patterns
Modal Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.modal name="my-modal" title="Modal Title">Content</x-proxmox::ui.modal>
@@ -14,7 +14,7 @@
'title' => null,
'subtitle' => null,
'icon' => null,
'iconColor' => 'lte-text-primary',
'iconColor' => 'text-primary',
'maxWidth' => 'md',
'closeable' => true,
'footer' => null,
@@ -55,7 +55,7 @@
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@if($closeable) @click="show = false" @endif
class="lte-modal-backdrop backdrop-blur-sm"
class="fixed inset-0 bg-black/50 backdrop-blur-sm"
></div>
{{-- Modal Panel --}}
@@ -69,44 +69,46 @@
x-transition:leave-start="opacity-100 scale-100 translate-y-0"
x-transition:leave-end="opacity-0 scale-95 translate-y-4"
@click.stop
class="lte-modal-content lte-slide-in-up w-full {{ $maxWidthClass }} max-h-[90vh] overflow-y-auto shadow-2xl"
class="w-full {{ $maxWidthClass }} max-h-[90vh] overflow-y-auto bg-background dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-xl shadow-2xl"
>
{{-- Header --}}
@if($title || $closeable)
<div class="lte-modal-header">
<div class="flex items-center justify-between p-5 border-b border-neutral dark:border-gray-700">
<div class="flex items-center gap-3">
@if($icon)
<span class="lte-info-box-icon lte-bg-light rounded" style="width: 40px; height: 40px; font-size: 1rem;">
<span class="flex items-center justify-center w-10 h-10 bg-background-secondary dark:bg-gray-700 rounded-lg">
<x-dynamic-component :component="$icon" class="size-5 {{ $iconColor }}" />
</span>
@endif
<div>
@if($title)
<h5 class="lte-modal-title">{{ $title }}</h5>
<h5 class="text-lg font-semibold text-primary dark:text-gray-100">{{ $title }}</h5>
@endif
@if($subtitle)
<p class="lte-text-muted" style="font-size: 0.875rem; margin: 0;">{{ $subtitle }}</p>
<p class="text-sm text-secondary dark:text-gray-400 mt-0.5">{{ $subtitle }}</p>
@endif
</div>
</div>
@if($closeable)
<button
@click="show = false"
class="lte-btn-close transition-all duration-200 hover:scale-110 hover:opacity-70"
class="flex items-center justify-center w-8 h-8 rounded-lg text-secondary dark:text-gray-400 hover:bg-background-secondary dark:hover:bg-gray-700 hover:text-primary dark:hover:text-gray-100 transition-all duration-200"
aria-label="Close"
></button>
>
<x-ri-close-line class="size-5" />
</button>
@endif
</div>
@endif
{{-- Content --}}
<div class="lte-modal-body">
<div class="p-5">
{{ $slot }}
</div>
{{-- Footer --}}
@if($footer)
<div class="lte-modal-footer">
<div class="flex items-center justify-end gap-3 p-5 border-t border-neutral dark:border-gray-700">
{{ $footer }}
</div>
@endif

View File

@@ -1,6 +1,6 @@
{{--
Progress Bar Component - AdminLTE Theme
Based on AdminLTE v4 Progress Bar styling patterns
Progress Bar Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.progress :value="75" />
@@ -24,24 +24,24 @@
$percent = $max > 0 ? min(100, ($value / $max) * 100) : 0;
$colorClasses = [
'primary' => 'lte-bg-primary',
'secondary' => 'lte-bg-secondary',
'success' => 'lte-bg-success',
'warning' => 'lte-bg-warning',
'danger' => 'lte-bg-danger',
'info' => 'lte-bg-info',
'primary' => 'bg-primary',
'secondary' => 'bg-secondary',
'success' => 'bg-success',
'warning' => 'bg-warning',
'danger' => 'bg-error',
'info' => 'bg-info',
];
$sizeClasses = [
'xs' => 'lte-progress-xs',
'sm' => 'lte-progress-sm',
'md' => '',
'lg' => '',
'xl' => '',
'xs' => 'h-1',
'sm' => 'h-2',
'md' => 'h-3',
'lg' => 'h-4',
'xl' => 'h-5',
];
$colorClass = $colorClasses[$color] ?? $colorClasses['primary'];
$sizeClass = $sizeClasses[$size] ?? '';
$sizeClass = $sizeClasses[$size] ?? $sizeClasses['md'];
// Auto-select color based on value if specified
if ($color === 'auto') {
@@ -57,14 +57,14 @@
<div {{ $attributes->class(['w-full']) }}>
@if($showLabel || $label)
<div class="mb-2 flex items-center justify-between" style="font-size: 0.875rem;">
<span style="font-weight: 500;">{{ $label ?? 'Usage' }}</span>
<span style="font-weight: 600;">{{ number_format($percent, 1) }}%</span>
<div class="mb-2 flex items-center justify-between text-sm">
<span class="font-medium text-secondary dark:text-gray-300">{{ $label ?? 'Usage' }}</span>
<span class="font-semibold text-primary dark:text-gray-100">{{ number_format($percent, 1) }}%</span>
</div>
@endif
<div class="lte-progress {{ $sizeClass }}">
<div class="{{ $sizeClass }} w-full bg-neutral/30 dark:bg-gray-700 rounded-full overflow-hidden">
<div
class="lte-progress-bar {{ $colorClass }} {{ $striped ? 'lte-progress-bar-striped' : '' }} {{ $animated ? 'lte-progress-bar-animated' : '' }}"
class="{{ $colorClass }} h-full rounded-full transition-all duration-500 {{ $striped ? 'progress-striped' : '' }} {{ $animated ? 'progress-animated' : '' }}"
role="progressbar"
style="width: {{ $percent }}%; {{ $gradient ? 'background-image: ' . $gradient . ';' : '' }}"
aria-valuenow="{{ $percent }}"

View File

@@ -1,5 +1,6 @@
{{--
Resource Gauge Component - AdminLTE style
Resource Gauge Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Shows a circular or linear gauge for resource usage
Usage:
@@ -33,15 +34,15 @@
}
}
$bgColors = [
'primary' => 'var(--lte-primary, #0d6efd)',
'success' => 'var(--lte-success, #198754)',
'warning' => 'var(--lte-warning, #ffc107)',
'danger' => 'var(--lte-danger, #dc3545)',
'info' => 'var(--lte-info, #0dcaf0)',
$barColorClasses = [
'primary' => 'bg-primary',
'success' => 'bg-success',
'warning' => 'bg-warning',
'danger' => 'bg-error',
'info' => 'bg-info',
];
$barColor = $bgColors[$color] ?? $bgColors['primary'];
$barColorClass = $barColorClasses[$color] ?? $barColorClasses['primary'];
$badgeLabel = match(true) {
$percent >= 100 => 'Over entitlement',
@@ -49,130 +50,41 @@
default => 'Healthy',
};
$badgeStyle = match(true) {
$percent >= 100 => 'background-color: rgba(220, 53, 69, 0.1); color: var(--lte-danger, #dc3545);',
$percent >= 85 => 'background-color: rgba(255, 193, 7, 0.15); color: #997404;',
default => 'background-color: rgba(25, 135, 84, 0.1); color: var(--lte-success, #198754);',
$badgeClasses = match(true) {
$percent >= 100 => 'bg-error/10 text-error',
$percent >= 85 => 'bg-warning/15 text-yellow-700 dark:text-yellow-400',
default => 'bg-success/10 text-success',
};
@endphp
<style>
.resource-gauge-lte {
display: flex;
flex-direction: column;
gap: 1rem;
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
padding: 1.25rem;
box-shadow: var(--lte-card-shadow);
}
.resource-gauge-lte-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.75rem;
}
.resource-gauge-lte-info {
min-width: 0;
}
.resource-gauge-lte-label {
font-size: 0.8125rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
margin-bottom: 0.25rem;
}
.resource-gauge-lte-value {
font-size: 1.25rem;
font-weight: 700;
color: #212529;
line-height: 1.3;
}
.resource-gauge-lte-sublabel {
font-size: 0.8125rem;
color: var(--lte-secondary, #6c757d);
margin-top: 0.25rem;
}
.resource-gauge-lte-badge {
flex-shrink: 0;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: 9999px;
}
.resource-gauge-lte-progress {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.resource-gauge-lte-progress-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.8125rem;
}
.resource-gauge-lte-progress-label {
color: var(--lte-secondary, #6c757d);
}
.resource-gauge-lte-progress-value {
font-weight: 600;
color: #212529;
}
.resource-gauge-lte-track {
height: 0.5rem;
background-color: #e9ecef;
border-radius: 9999px;
overflow: hidden;
}
.resource-gauge-lte-bar {
height: 100%;
border-radius: 9999px;
transition: width var(--lte-transition-speed, 0.3s) ease;
}
/* Dark mode */
.dark .resource-gauge-lte {
background-color: #1f2937;
border-color: #374151;
}
.dark .resource-gauge-lte-value {
color: #f3f4f6;
}
.dark .resource-gauge-lte-progress-value {
color: #f3f4f6;
}
.dark .resource-gauge-lte-track {
background-color: #374151;
}
</style>
<div {{ $attributes->class(['resource-gauge-lte']) }}>
<div class="resource-gauge-lte-header">
<div class="resource-gauge-lte-info">
<p class="resource-gauge-lte-label">{{ $label }}</p>
<p class="resource-gauge-lte-value">
<div {{ $attributes->class(['flex flex-col gap-4 bg-background dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-lg p-5 shadow-sm']) }}>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="text-xs font-medium text-secondary dark:text-gray-400 mb-1">{{ $label }}</p>
<p class="text-xl font-bold text-primary dark:text-gray-100 leading-tight">
@if($prefix){{ $prefix }}@endif{{ is_numeric($value) ? number_format($value, 1) : $value }}@if($suffix){{ $suffix }}@endif
</p>
@if($sublabel)
<p class="resource-gauge-lte-sublabel">{{ $sublabel }}</p>
<p class="text-xs text-secondary dark:text-gray-400 mt-1">{{ $sublabel }}</p>
@endif
</div>
<span class="resource-gauge-lte-badge" style="{{ $badgeStyle }}">
<span class="shrink-0 px-2.5 py-1 text-xs font-medium rounded-full {{ $badgeClasses }}">
{{ $badgeLabel }}
</span>
</div>
<div class="resource-gauge-lte-progress">
<div class="resource-gauge-lte-progress-header">
<span class="resource-gauge-lte-progress-label">Usage</span>
<span class="resource-gauge-lte-progress-value">{{ number_format($percent, 1) }}%</span>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between text-xs">
<span class="text-secondary dark:text-gray-400">Usage</span>
<span class="font-semibold text-primary dark:text-gray-100">{{ number_format($percent, 1) }}%</span>
</div>
<div class="resource-gauge-lte-track">
<div class="h-2 bg-neutral/30 dark:bg-gray-700 rounded-full overflow-hidden">
<div
class="resource-gauge-lte-bar"
class="{{ $barColorClass }} h-full rounded-full transition-all duration-500"
@if($gradient)
style="width: {{ min($percent, 130) }}%; background-image: {{ $gradient }};"
@else
style="width: {{ min($percent, 130) }}%; background-color: {{ $barColor }};"
style="width: {{ min($percent, 130) }}%;"
@endif
></div>
</div>

View File

@@ -1,5 +1,6 @@
{{--
Select Component - AdminLTE style
Select Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.select name="country" label="Country">
@@ -19,126 +20,46 @@
])
@php
$sizeStyles = [
'sm' => 'height: 2.25rem; padding: 0.375rem 2.25rem 0.375rem 0.75rem; font-size: 0.8125rem;',
'md' => 'height: 2.625rem; padding: 0.5rem 2.5rem 0.5rem 1rem; font-size: 0.875rem;',
'lg' => 'height: 3rem; padding: 0.625rem 2.75rem 0.625rem 1rem; font-size: 1rem;',
$sizeClasses = [
'sm' => 'h-9 py-1.5 px-3 pr-9 text-xs',
'md' => 'h-10 py-2 px-4 pr-10 text-sm',
'lg' => 'h-12 py-2.5 px-4 pr-11 text-base',
];
$sizeStyle = $sizeStyles[$size] ?? $sizeStyles['md'];
$sizeClass = $sizeClasses[$size] ?? $sizeClasses['md'];
$baseClasses = 'block w-full appearance-none bg-background dark:bg-gray-900 border border-neutral dark:border-gray-600 rounded-lg shadow-sm text-primary dark:text-gray-100 transition-all duration-150 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/25 disabled:bg-background-secondary dark:disabled:bg-gray-800 disabled:cursor-not-allowed disabled:opacity-65';
$errorClasses = $error ? 'border-error focus:border-error focus:ring-error/25' : '';
@endphp
<style>
.form-group-lte {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label-lte {
font-size: 0.875rem;
font-weight: 500;
color: #495057;
}
.form-label-lte-required::after {
content: " *";
color: var(--lte-danger, #dc3545);
}
.form-select-wrapper-lte {
position: relative;
}
.form-select-lte {
display: block;
width: 100%;
appearance: none;
background-color: #fff;
border: 1px solid #ced4da;
border-radius: var(--lte-border-radius, 0.375rem);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
color: #212529;
}
.form-select-lte:focus {
border-color: #86b7fe;
outline: 0;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.form-select-lte:disabled {
background-color: #e9ecef;
cursor: not-allowed;
opacity: 0.65;
}
.form-select-lte-error {
border-color: var(--lte-danger, #dc3545);
}
.form-select-lte-error:focus {
border-color: var(--lte-danger, #dc3545);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
.form-select-icon-lte {
position: absolute;
top: 50%;
right: 1rem;
transform: translateY(-50%);
pointer-events: none;
color: #6c757d;
}
.form-hint-lte {
font-size: 0.8125rem;
color: var(--lte-secondary, #6c757d);
}
.form-error-lte {
font-size: 0.8125rem;
font-weight: 500;
color: var(--lte-danger, #dc3545);
}
/* Dark mode */
.dark .form-label-lte {
color: #d1d5db;
}
.dark .form-select-lte {
background-color: #1f2937;
border-color: #374151;
color: #f3f4f6;
}
.dark .form-select-lte:focus {
border-color: var(--lte-primary, #0d6efd);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.dark .form-select-lte:disabled {
background-color: #374151;
}
.dark .form-select-icon-lte {
color: #9ca3af;
}
.dark .form-hint-lte {
color: #9ca3af;
}
</style>
<div {{ $attributes->only('class')->class(['form-group-lte']) }}>
<div {{ $attributes->only('class')->class(['flex flex-col gap-2']) }}>
@if($label)
<label @if($name) for="{{ $name }}" @endif class="form-label-lte @if($required) form-label-lte-required @endif">
<label @if($name) for="{{ $name }}" @endif class="text-sm font-medium text-secondary dark:text-gray-300">
{{ $label }}
@if($required)
<span class="text-error">*</span>
@endif
</label>
@endif
<div class="form-select-wrapper-lte">
<div class="relative">
<select
@if($name) name="{{ $name }}" id="{{ $name }}" @endif
@if($disabled) disabled @endif
@if($required) required @endif
style="{{ $sizeStyle }}"
{{ $attributes->except('class')->class(['form-select-lte', 'form-select-lte-error' => $error]) }}
class="{{ $baseClasses }} {{ $sizeClass }} {{ $errorClasses }}"
{{ $attributes->except('class') }}
>
{{ $slot }}
</select>
<div class="form-select-icon-lte">
<div class="absolute top-1/2 right-4 -translate-y-1/2 pointer-events-none text-secondary dark:text-gray-400">
<x-ri-arrow-down-s-line class="size-5" />
</div>
</div>
@if($error)
<p class="form-error-lte">{{ $error }}</p>
<p class="text-xs font-medium text-error">{{ $error }}</p>
@elseif($hint)
<p class="form-hint-lte">{{ $hint }}</p>
<p class="text-xs text-secondary dark:text-gray-400">{{ $hint }}</p>
@endif
</div>

View File

@@ -1,5 +1,6 @@
{{--
Server Status Indicator Component - AdminLTE style
Server Status Indicator Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.server-status status="running" />
@@ -60,117 +61,73 @@
$config = $statusConfig[strtolower($status)] ?? $statusConfig['unknown'];
$colorStyles = [
'success' => 'color: var(--lte-success, #198754);',
'warning' => 'color: #997404;',
'danger' => 'color: var(--lte-danger, #dc3545);',
'info' => 'color: var(--lte-info, #0dcaf0);',
'muted' => 'color: var(--lte-secondary, #6c757d);',
$colorClasses = [
'success' => 'text-success',
'warning' => 'text-yellow-600 dark:text-yellow-400',
'danger' => 'text-error',
'info' => 'text-info',
'muted' => 'text-secondary dark:text-gray-400',
];
$bgStyles = [
'success' => 'background-color: rgba(25, 135, 84, 0.1);',
'warning' => 'background-color: rgba(255, 193, 7, 0.15);',
'danger' => 'background-color: rgba(220, 53, 69, 0.1);',
'info' => 'background-color: rgba(13, 202, 240, 0.1);',
'muted' => 'background-color: #f8f9fa;',
$bgClasses = [
'success' => 'bg-success/10',
'warning' => 'bg-warning/15',
'danger' => 'bg-error/10',
'info' => 'bg-info/10',
'muted' => 'bg-neutral/20 dark:bg-gray-700',
];
$dotStyles = [
'success' => 'background-color: var(--lte-success, #198754);',
'warning' => 'background-color: var(--lte-warning, #ffc107);',
'danger' => 'background-color: var(--lte-danger, #dc3545);',
'info' => 'background-color: var(--lte-info, #0dcaf0);',
'muted' => 'background-color: #adb5bd;',
$dotClasses = [
'success' => 'bg-success',
'warning' => 'bg-warning',
'danger' => 'bg-error',
'info' => 'bg-info',
'muted' => 'bg-neutral',
];
$sizeStyles = [
'sm' => 'font-size: 0.75rem; padding: 0.25rem 0.5rem;',
'md' => 'font-size: 0.8125rem; padding: 0.375rem 0.625rem;',
'lg' => 'font-size: 0.875rem; padding: 0.5rem 0.75rem;',
$sizeClasses = [
'sm' => 'text-xs px-2 py-1',
'md' => 'text-xs px-2.5 py-1.5',
'lg' => 'text-sm px-3 py-2',
];
$dotSizeStyles = [
'sm' => 'width: 0.375rem; height: 0.375rem;',
'md' => 'width: 0.5rem; height: 0.5rem;',
'lg' => 'width: 0.625rem; height: 0.625rem;',
$dotSizeClasses = [
'sm' => 'w-1.5 h-1.5',
'md' => 'w-2 h-2',
'lg' => 'w-2.5 h-2.5',
];
@endphp
<style>
.server-status-lte {
display: inline-flex;
align-items: center;
font-weight: 500;
border-radius: 9999px;
}
.server-status-lte-minimal {
display: inline-flex;
align-items: center;
}
.server-status-lte-dot-wrapper {
position: relative;
display: flex;
}
.server-status-lte-dot {
position: relative;
display: inline-flex;
border-radius: 50%;
}
.server-status-lte-pulse {
position: absolute;
display: inline-flex;
width: 100%;
height: 100%;
border-radius: 50%;
opacity: 0.6;
animation: pulse-lte 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse-lte {
0%, 100% {
opacity: 0.6;
transform: scale(1);
}
50% {
opacity: 0;
transform: scale(1.5);
}
}
/* Dark mode */
.dark .server-status-lte-muted {
background-color: #374151 !important;
}
</style>
@if($minimal)
{{-- Minimal dot-only variant --}}
<div
{{ $attributes }}
class="server-status-lte-minimal"
style="gap: {{ $showLabel ? '0.375rem' : '0' }};"
{{ $attributes->class(['inline-flex items-center', $showLabel ? 'gap-1.5' : '']) }}
>
<span class="server-status-lte-dot-wrapper" style="{{ $dotSizeStyles[$size] ?? $dotSizeStyles['md'] }}">
<span class="relative flex {{ $dotSizeClasses[$size] ?? $dotSizeClasses['md'] }}">
@if($config['pulse'])
<span class="server-status-lte-pulse" style="{{ $dotStyles[$config['color']] ?? $dotStyles['muted'] }}"></span>
<span class="absolute inline-flex w-full h-full rounded-full {{ $dotClasses[$config['color']] ?? $dotClasses['muted'] }} opacity-60 animate-ping"></span>
@endif
<span class="server-status-lte-dot" style="{{ $dotSizeStyles[$size] ?? $dotSizeStyles['md'] }} {{ $dotStyles[$config['color']] ?? $dotStyles['muted'] }}"></span>
<span class="relative inline-flex rounded-full {{ $dotSizeClasses[$size] ?? $dotSizeClasses['md'] }} {{ $dotClasses[$config['color']] ?? $dotClasses['muted'] }}"></span>
</span>
@if($showLabel)
<span style="font-weight: 500; {{ $colorStyles[$config['color']] ?? $colorStyles['muted'] }}">{{ $config['label'] }}</span>
<span class="font-medium {{ $colorClasses[$config['color']] ?? $colorClasses['muted'] }}">{{ $config['label'] }}</span>
@endif
</div>
@else
{{-- Full badge variant --}}
<div
{{ $attributes }}
class="server-status-lte {{ $config['color'] === 'muted' ? 'server-status-lte-muted' : '' }}"
style="{{ $bgStyles[$config['color']] ?? $bgStyles['muted'] }} {{ $colorStyles[$config['color']] ?? $colorStyles['muted'] }} {{ $sizeStyles[$size] ?? $sizeStyles['md'] }} gap: 0.5rem;"
{{ $attributes->class([
'inline-flex items-center gap-2 font-medium rounded-full',
$bgClasses[$config['color']] ?? $bgClasses['muted'],
$colorClasses[$config['color']] ?? $colorClasses['muted'],
$sizeClasses[$size] ?? $sizeClasses['md'],
]) }}
>
<span class="server-status-lte-dot-wrapper" style="{{ $dotSizeStyles[$size] ?? $dotSizeStyles['md'] }}">
<span class="relative flex {{ $dotSizeClasses[$size] ?? $dotSizeClasses['md'] }}">
@if($config['pulse'])
<span class="server-status-lte-pulse" style="background-color: currentColor;"></span>
<span class="absolute inline-flex w-full h-full rounded-full bg-current opacity-60 animate-ping"></span>
@endif
<span class="server-status-lte-dot" style="{{ $dotSizeStyles[$size] ?? $dotSizeStyles['md'] }} background-color: currentColor;"></span>
<span class="relative inline-flex rounded-full {{ $dotSizeClasses[$size] ?? $dotSizeClasses['md'] }} bg-current"></span>
</span>
@if($showLabel)
<span>{{ $config['label'] }}</span>

View File

@@ -1,5 +1,6 @@
{{--
Spinner Component - AdminLTE style
Spinner Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.spinner />
@@ -12,43 +13,31 @@
])
@php
$sizeStyles = [
'xs' => 'width: 0.75rem; height: 0.75rem;',
'sm' => 'width: 1rem; height: 1rem;',
'md' => 'width: 1.25rem; height: 1.25rem;',
'lg' => 'width: 1.5rem; height: 1.5rem;',
'xl' => 'width: 2rem; height: 2rem;',
$sizeClasses = [
'xs' => 'w-3 h-3',
'sm' => 'w-4 h-4',
'md' => 'w-5 h-5',
'lg' => 'w-6 h-6',
'xl' => 'w-8 h-8',
];
$colorStyles = [
'current' => 'color: currentColor;',
'primary' => 'color: var(--lte-primary, #0d6efd);',
'secondary' => 'color: var(--lte-secondary, #6c757d);',
'success' => 'color: var(--lte-success, #198754);',
'warning' => 'color: var(--lte-warning, #ffc107);',
'danger' => 'color: var(--lte-danger, #dc3545);',
'info' => 'color: var(--lte-info, #0dcaf0);',
'muted' => 'color: #adb5bd;',
$colorClasses = [
'current' => 'text-current',
'primary' => 'text-primary',
'secondary' => 'text-secondary',
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-error',
'info' => 'text-info',
'muted' => 'text-neutral',
];
$sizeStyle = $sizeStyles[$size] ?? $sizeStyles['md'];
$colorStyle = $colorStyles[$color] ?? $colorStyles['current'];
$sizeClass = $sizeClasses[$size] ?? $sizeClasses['md'];
$colorClass = $colorClasses[$color] ?? $colorClasses['current'];
@endphp
<style>
.spinner-lte {
animation: spin-lte 1s linear infinite;
}
@keyframes spin-lte {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
</style>
<svg
{{ $attributes }}
class="spinner-lte"
style="{{ $sizeStyle }} {{ $colorStyle }}"
{{ $attributes->class(['animate-spin', $sizeClass, $colorClass]) }}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"

View File

@@ -1,6 +1,6 @@
{{--
Stat Card Component - AdminLTE Theme
Based on AdminLTE v4 Small Box / Info Box styling patterns
Stat Card Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.stat-card value="12" label="CPU %" />
@@ -30,28 +30,28 @@
@php
$tag = $href ? 'a' : 'div';
$variantConfig = [
'primary' => ['bg' => 'bg-primary', 'class' => 'lte-bg-primary'],
'success' => ['bg' => 'bg-success', 'class' => 'lte-bg-success'],
'warning' => ['bg' => 'bg-warning', 'class' => 'lte-bg-warning'],
'danger' => ['bg' => 'bg-danger', 'class' => 'lte-bg-danger'],
'info' => ['bg' => 'bg-info', 'class' => 'lte-bg-info'],
$variantClasses = [
'primary' => 'bg-primary',
'success' => 'bg-success',
'warning' => 'bg-warning',
'danger' => 'bg-error',
'info' => 'bg-info',
];
$progressColors = [
'primary' => 'lte-bg-primary',
'success' => 'lte-bg-success',
'warning' => 'lte-bg-warning',
'danger' => 'lte-bg-danger',
'info' => 'lte-bg-info',
'primary' => 'bg-primary',
'success' => 'bg-success',
'warning' => 'bg-warning',
'danger' => 'bg-error',
'info' => 'bg-info',
];
$statusClasses = [
'success' => 'lte-text-success',
'warning' => 'lte-text-warning',
'danger' => 'lte-text-danger',
'info' => 'lte-text-info',
'muted' => 'lte-text-muted',
'success' => 'text-success',
'warning' => 'text-warning',
'danger' => 'text-error',
'info' => 'text-info',
'muted' => 'text-secondary',
];
$statusClass = $statusClasses[$status] ?? '';
$progressBarColor = $progressColors[$progressColor] ?? $progressColors['primary'];
@@ -62,22 +62,23 @@
<{{ $tag }}
@if($href) href="{{ $href }}" @endif
{{ $attributes->class([
'lte-small-box lte-fade-in transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 hover:scale-[1.01]',
$variantConfig[$variant]['class'] ?? 'lte-bg-primary',
'relative block overflow-hidden rounded-xl text-white transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5 hover:scale-[1.01]',
$variantClasses[$variant] ?? 'bg-primary',
]) }}
>
<div class="lte-inner">
<h3 class="transition-transform duration-200 group-hover:scale-105">{{ $prefix }}{{ $value }}@if($suffix)<sup style="font-size: 0.6em;">{{ $suffix }}</sup>@endif</h3>
<p class="transition-opacity duration-200 opacity-90 group-hover:opacity-100">{{ $label }}</p>
<div class="p-5">
<h3 class="text-2xl font-bold transition-transform duration-200">{{ $prefix }}{{ $value }}@if($suffix)<sup class="text-sm opacity-75">{{ $suffix }}</sup>@endif</h3>
<p class="text-sm opacity-90 mt-1">{{ $label }}</p>
</div>
@if($icon)
<div class="lte-icon transition-transform duration-300 hover:scale-110 hover:rotate-3">
<x-dynamic-component :component="$icon" style="font-size: 70px; width: 70px; height: 70px;" />
<div class="absolute top-4 right-4 opacity-30 transition-transform duration-300 hover:scale-110">
<x-dynamic-component :component="$icon" class="size-16" />
</div>
@endif
@if($statusText)
<div class="lte-small-box-footer transition-colors duration-200 hover:bg-black/10">
{{ $statusText }} <x-ri-arrow-right-line class="inline size-4 transition-transform duration-200 group-hover:translate-x-1" />
<div class="px-5 py-3 bg-black/10 text-sm transition-colors duration-200 hover:bg-black/20 flex items-center justify-between">
<span>{{ $statusText }}</span>
<x-ri-arrow-right-line class="size-4" />
</div>
@endif
</{{ $tag }}>
@@ -86,24 +87,24 @@
<{{ $tag }}
@if($href) href="{{ $href }}" @endif
{{ $attributes->class([
'lte-info-box lte-fade-in group transition-all duration-300 hover:shadow-md hover:-translate-y-0.5',
'flex items-start gap-4 p-5 bg-background dark:bg-gray-800 border border-neutral dark:border-gray-700 rounded-xl shadow-sm group transition-all duration-300 hover:shadow-md hover:-translate-y-0.5',
'cursor-pointer' => $href || $clickable,
]) }}
>
@if($icon)
<span class="lte-info-box-icon lte-bg-primary rounded transition-transform duration-300 group-hover:scale-110" style="color: #fff;">
<span class="flex items-center justify-center w-12 h-12 bg-primary rounded-lg text-white shrink-0 transition-transform duration-300 group-hover:scale-110">
<x-dynamic-component :component="$icon" class="size-6 {{ $iconClass }}" />
</span>
@endif
<div class="lte-info-box-content">
<span class="lte-info-box-text transition-opacity duration-200 opacity-80 group-hover:opacity-100">{{ $label }}</span>
<span class="lte-info-box-number transition-colors duration-200 group-hover:text-primary">
@if($prefix)<small class="lte-text-muted">{{ $prefix }}</small>@endif{{ $value }}@if($suffix)<small class="lte-text-muted">{{ $suffix }}</small>@endif
<div class="flex-1 min-w-0">
<span class="text-xs font-medium text-secondary dark:text-gray-400 uppercase tracking-wide">{{ $label }}</span>
<span class="block text-xl font-bold text-primary dark:text-gray-100 mt-1 transition-colors duration-200 group-hover:text-primary">
@if($prefix)<small class="text-secondary dark:text-gray-400 text-sm font-normal">{{ $prefix }}</small>@endif{{ $value }}@if($suffix)<small class="text-secondary dark:text-gray-400 text-sm font-normal">{{ $suffix }}</small>@endif
</span>
@if($change !== null || $changeLabel)
<div style="font-size: 0.8rem;" class="lte-text-muted">
<div class="text-xs text-secondary dark:text-gray-400 mt-1">
@if($change !== null)
<span class="{{ $change >= 0 ? 'lte-text-success' : 'lte-text-danger' }}" style="font-weight: 600;">
<span class="{{ $change >= 0 ? 'text-success' : 'text-error' }} font-semibold">
{{ $change >= 0 ? '+' : '' }}{{ $change }}%
</span>
@endif
@@ -111,12 +112,12 @@
</div>
@endif
@if($progress !== null)
<div class="lte-progress lte-progress-sm" style="margin-top: 0.5rem;">
<div class="lte-progress-bar {{ $progressBarColor }}" style="width: {{ min(100, $progress) }}%"></div>
<div class="h-1.5 bg-neutral/30 dark:bg-gray-700 rounded-full overflow-hidden mt-3">
<div class="{{ $progressBarColor }} h-full rounded-full transition-all duration-500" style="width: {{ min(100, $progress) }}%"></div>
</div>
@endif
@if($statusText)
<span class="lte-progress-description {{ $statusClass }}">{{ $statusText }}</span>
<span class="text-xs {{ $statusClass }} mt-2 inline-block">{{ $statusText }}</span>
@endif
</div>
</{{ $tag }}>

View File

@@ -1,41 +1,18 @@
{{--
Tab Panel Component - AdminLTE style
Tab Panel Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
@props([
'name',
])
<style>
.tab-pane-lte {
animation: fadeIn var(--lte-transition-speed, 0.3s) ease-out;
}
.tab-pane-lte:focus {
outline: none;
}
.tab-pane-lte:focus-visible {
outline: 2px solid rgba(13, 110, 253, 0.25);
outline-offset: 2px;
border-radius: var(--lte-border-radius, 0.375rem);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
<div
x-show="activeTab === '{{ $name }}'"
x-transition:enter="tab-pane-lte"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
{{ $attributes->class(['tab-pane-lte']) }}
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 -translate-y-1"
x-transition:enter-end="opacity-100 translate-y-0"
{{ $attributes->class(['focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/25 focus-visible:ring-offset-2 rounded-lg']) }}
>
{{ $slot }}
</div>

View File

@@ -1,5 +1,6 @@
{{--
Table Row Component - AdminLTE style
Table Row Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
@props([
@@ -12,44 +13,20 @@
$isHoverable = $hoverable ?? true;
@endphp
<style>
.table-row-lte {
transition: background-color var(--lte-transition-speed, 0.3s) ease;
border-bottom: 1px solid var(--lte-border-color, #dee2e6);
}
.table-row-lte:last-child {
border-bottom: none;
}
.table-row-lte-clickable {
cursor: pointer;
}
.table-row-lte-clickable:hover {
background-color: rgba(13, 110, 253, 0.04);
}
.dark .table-row-lte {
border-bottom-color: #374151;
}
.dark .table-row-lte-clickable:hover {
background-color: rgba(13, 110, 253, 0.08);
}
</style>
@if($href)
<tr
{{ $attributes->class([
'table-row-lte table-row-lte-clickable',
'border-b border-neutral dark:border-gray-700 last:border-b-0 transition-colors duration-200 cursor-pointer hover:bg-primary/5 dark:hover:bg-primary/10',
]) }}
onclick="window.location='{{ $href }}'"
>
{{ $slot }}
</tr>
@else
<tr
{{ $attributes->class([
'table-row-lte',
'table-row-lte-clickable' => $clickable,
]) }}
>
<tr {{ $attributes->class([
'border-b border-neutral dark:border-gray-700 last:border-b-0 transition-colors duration-200',
'cursor-pointer hover:bg-primary/5 dark:hover:bg-primary/10' => $clickable,
]) }}>
{{ $slot }}
</tr>
@endif

View File

@@ -1,5 +1,6 @@
{{--
Table Data Cell Component - AdminLTE style
Table Data Cell Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
@props([
@@ -10,50 +11,20 @@
])
@php
$alignStyles = [
'start' => 'text-align: left;',
'center' => 'text-align: center;',
'end' => 'text-align: right;',
$alignClasses = [
'start' => 'text-left',
'center' => 'text-center',
'end' => 'text-right',
];
$alignStyle = $alignStyles[$align] ?? $alignStyles['start'];
$alignClass = $alignClasses[$align] ?? $alignClasses['start'];
@endphp
<style>
.table-td-lte {
padding: 0.875rem 1.25rem;
font-size: 0.875rem;
color: #495057;
vertical-align: middle;
}
.table-td-lte-nowrap {
white-space: nowrap;
}
.table-td-lte-muted {
color: var(--lte-secondary, #6c757d);
}
.table-td-lte-strong {
font-weight: 500;
color: #212529;
}
.dark .table-td-lte {
color: #d1d5db;
}
.dark .table-td-lte-muted {
color: #9ca3af;
}
.dark .table-td-lte-strong {
color: #f3f4f6;
}
</style>
<td
style="{{ $alignStyle }}"
{{ $attributes->class([
'table-td-lte',
'table-td-lte-nowrap' => $nowrap,
'table-td-lte-muted' => $muted,
'table-td-lte-strong' => $strong,
]) }}
>
<td {{ $attributes->class([
'px-5 py-3.5 text-sm text-secondary dark:text-gray-300 align-middle',
$alignClass,
'whitespace-nowrap' => $nowrap,
'text-secondary/80 dark:text-gray-400' => $muted,
'font-medium text-primary dark:text-gray-100' => $strong,
]) }}>
{{ $slot }}
</td>

View File

@@ -1,5 +1,6 @@
{{--
Table Header Cell Component - AdminLTE style
Table Header Cell Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
--}}
@props([
@@ -11,77 +12,35 @@
])
@php
$alignStyles = [
'start' => 'text-align: left; justify-content: flex-start;',
'center' => 'text-align: center; justify-content: center;',
'end' => 'text-align: right; justify-content: flex-end;',
$alignClasses = [
'start' => 'text-left justify-start',
'center' => 'text-center justify-center',
'end' => 'text-right justify-end',
];
$alignStyle = $alignStyles[$align] ?? $alignStyles['start'];
$alignClass = $alignClasses[$align] ?? $alignClasses['start'];
$isSorted = $sortable && $sortKey === $currentSort;
$baseClasses = 'px-5 py-3.5 text-xs font-semibold uppercase tracking-wider text-secondary dark:text-gray-400 bg-transparent align-middle whitespace-nowrap';
$sortableClasses = $sortable ? 'cursor-pointer select-none transition-colors duration-200 hover:text-primary dark:hover:text-primary' : '';
$sortedClasses = $isSorted ? 'text-primary dark:text-primary' : '';
@endphp
<style>
.table-th-lte {
padding: 0.875rem 1.25rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--lte-secondary, #6c757d);
background-color: transparent;
vertical-align: middle;
white-space: nowrap;
}
.table-th-lte-sortable {
cursor: pointer;
user-select: none;
transition: color var(--lte-transition-speed, 0.3s) ease;
}
.table-th-lte-sortable:hover {
color: var(--lte-primary, #0d6efd);
}
.table-th-lte-sorted {
color: var(--lte-primary, #0d6efd);
}
.table-th-lte-inner {
display: flex;
align-items: center;
gap: 0.375rem;
}
.table-th-lte-sort-icon {
display: inline-flex;
color: #adb5bd;
}
.table-th-lte-sorted .table-th-lte-sort-icon {
color: var(--lte-primary, #0d6efd);
}
.dark .table-th-lte {
color: #9ca3af;
}
.dark .table-th-lte-sortable:hover {
color: var(--lte-primary, #0d6efd);
}
.dark .table-th-lte-sort-icon {
color: #6b7280;
}
</style>
<th
style="{{ $alignStyle }}"
{{ $attributes->class([
'table-th-lte',
'table-th-lte-sortable' => $sortable,
'table-th-lte-sorted' => $isSorted,
$baseClasses,
$alignClass,
$sortableClasses,
$sortedClasses,
]) }}
@if($sortable && $sortKey)
wire:click="sort('{{ $sortKey }}')"
@endif
>
<div class="table-th-lte-inner" style="{{ $alignStyle }}">
<div class="flex items-center gap-1.5 {{ $alignClass }}">
<span>{{ $slot }}</span>
@if($sortable)
<span class="table-th-lte-sort-icon">
<span class="inline-flex {{ $isSorted ? 'text-primary' : 'text-secondary/50 dark:text-gray-500' }}">
@if($isSorted)
@if($currentDirection === 'asc')
<x-ri-arrow-up-s-line class="size-4" />

View File

@@ -1,5 +1,6 @@
{{--
Table Component - AdminLTE style
Table Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.table>
@@ -22,110 +23,28 @@
'bordered' => false,
])
<style>
.table-lte-wrapper {
position: relative;
width: 100%;
overflow-x: auto;
border-radius: var(--lte-border-radius, 0.375rem);
border: 1px solid var(--lte-border-color, #dee2e6);
background-color: #fff;
}
.table-lte {
width: 100%;
margin-bottom: 0;
color: #212529;
vertical-align: top;
border-collapse: collapse;
font-size: 0.875rem;
}
.table-lte thead {
background-color: #f8f9fa;
border-bottom: 2px solid var(--lte-border-color, #dee2e6);
}
.table-lte tbody {
background-color: #fff;
}
.table-lte tfoot {
background-color: #f8f9fa;
border-top: 2px solid var(--lte-border-color, #dee2e6);
font-weight: 500;
}
.table-lte-striped tbody tr:nth-of-type(even) {
background-color: rgba(0, 0, 0, 0.02);
}
.table-lte-hoverable tbody tr {
transition: background-color var(--lte-transition-speed, 0.3s) ease;
}
.table-lte-hoverable tbody tr:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.table-lte-bordered {
border: 1px solid var(--lte-border-color, #dee2e6);
}
.table-lte-bordered th,
.table-lte-bordered td {
border: 1px solid var(--lte-border-color, #dee2e6);
}
.table-lte-compact th,
.table-lte-compact td {
padding: 0.375rem 0.75rem;
}
/* Dark mode */
.dark .table-lte-wrapper {
background-color: #1f2937;
border-color: #374151;
}
.dark .table-lte {
color: #e5e7eb;
}
.dark .table-lte thead {
background-color: rgba(55, 65, 81, 0.5);
border-bottom-color: #374151;
}
.dark .table-lte tbody {
background-color: transparent;
}
.dark .table-lte tfoot {
background-color: rgba(55, 65, 81, 0.5);
border-top-color: #374151;
}
.dark .table-lte-striped tbody tr:nth-of-type(even) {
background-color: rgba(255, 255, 255, 0.02);
}
.dark .table-lte-hoverable tbody tr:hover {
background-color: rgba(255, 255, 255, 0.04);
}
.dark .table-lte-bordered th,
.dark .table-lte-bordered td {
border-color: #374151;
}
</style>
<div @class(['table-lte-wrapper' => $responsive])>
<table
{{ $attributes->class([
'table-lte',
'table-lte-striped' => $striped,
'table-lte-hoverable' => $hoverable,
'table-lte-bordered' => $bordered,
'table-lte-compact' => $compact,
]) }}
data-hoverable="{{ $hoverable ? 'true' : 'false' }}"
data-compact="{{ $compact ? 'true' : 'false' }}"
>
<div @class([
'relative w-full overflow-x-auto rounded-lg border border-neutral dark:border-gray-700 bg-background dark:bg-gray-800' => $responsive,
])>
<table {{ $attributes->class([
'w-full text-sm text-primary dark:text-gray-200 border-collapse',
'[&_tbody_tr:nth-of-type(even)]:bg-black/[0.02] dark:[&_tbody_tr:nth-of-type(even)]:bg-white/[0.02]' => $striped,
'[&_tbody_tr]:transition-colors [&_tbody_tr]:duration-200 [&_tbody_tr:hover]:bg-black/[0.04] dark:[&_tbody_tr:hover]:bg-white/[0.04]' => $hoverable,
'border border-neutral dark:border-gray-700 [&_th]:border [&_th]:border-neutral dark:[&_th]:border-gray-700 [&_td]:border [&_td]:border-neutral dark:[&_td]:border-gray-700' => $bordered,
'[&_th]:px-3 [&_th]:py-1.5 [&_td]:px-3 [&_td]:py-1.5' => $compact,
]) }}>
@if(isset($head))
<thead>
<thead class="bg-background-secondary dark:bg-gray-700/50 border-b-2 border-neutral dark:border-gray-600">
<tr>
{{ $head }}
</tr>
</thead>
@endif
<tbody>
<tbody class="bg-background dark:bg-transparent">
{{ $slot }}
</tbody>
@if(isset($foot))
<tfoot>
<tfoot class="bg-background-secondary dark:bg-gray-700/50 border-t-2 border-neutral dark:border-gray-600 font-medium">
{{ $foot }}
</tfoot>
@endif

View File

@@ -1,5 +1,6 @@
{{--
Tabs Component - AdminLTE style
Tabs Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.tabs :tabs="['Overview', 'Settings', 'Logs']" active="Overview">
@@ -19,172 +20,17 @@
$activeTab = $active ?? ($tabs[0] ?? null);
@endphp
<style>
.nav-tabs-lte {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin-bottom: 0;
list-style: none;
border-bottom: 1px solid var(--lte-border-color, #dee2e6);
}
.nav-tabs-lte .nav-item-lte {
margin-bottom: -1px;
}
.nav-link-lte {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
text-decoration: none;
background-color: transparent;
border: 1px solid transparent;
border-top-left-radius: var(--lte-border-radius, 0.375rem);
border-top-right-radius: var(--lte-border-radius, 0.375rem);
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
cursor: pointer;
}
.nav-link-lte:hover {
color: var(--lte-primary, #0d6efd);
border-color: #e9ecef #e9ecef var(--lte-border-color, #dee2e6);
background-color: rgba(0, 0, 0, 0.02);
}
.nav-link-lte.active {
color: var(--lte-primary, #0d6efd);
background-color: #fff;
border-color: var(--lte-border-color, #dee2e6) var(--lte-border-color, #dee2e6) #fff;
}
.nav-pills-lte {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
padding: 0.25rem;
background-color: #f8f9fa;
border-radius: var(--lte-border-radius, 0.375rem);
border: 1px solid var(--lte-border-color, #dee2e6);
}
.nav-pills-lte .nav-link-pill {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
background-color: transparent;
border: none;
border-radius: calc(var(--lte-border-radius, 0.375rem) - 2px);
transition: all var(--lte-transition-speed, 0.3s) ease;
cursor: pointer;
}
.nav-pills-lte .nav-link-pill:hover {
color: #495057;
background-color: rgba(0, 0, 0, 0.04);
}
.nav-pills-lte .nav-link-pill.active {
color: #212529;
background-color: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.nav-buttons-lte {
display: flex;
gap: 0.5rem;
}
.nav-buttons-lte .nav-btn-lte {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: var(--lte-secondary, #6c757d);
background-color: #fff;
border: 1px solid var(--lte-border-color, #dee2e6);
border-radius: var(--lte-border-radius, 0.375rem);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
transition: all var(--lte-transition-speed, 0.3s) ease;
cursor: pointer;
}
.nav-buttons-lte .nav-btn-lte:hover {
background-color: #f8f9fa;
border-color: #adb5bd;
}
.nav-buttons-lte .nav-btn-lte.active {
color: #fff;
background-color: var(--lte-primary, #0d6efd);
border-color: var(--lte-primary, #0d6efd);
}
.tab-badge-lte {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.125rem 0.5rem;
font-size: 0.75rem;
font-weight: 600;
line-height: 1;
color: var(--lte-primary, #0d6efd);
background-color: rgba(13, 110, 253, 0.1);
border-radius: 9999px;
}
.tab-content-lte {
margin-top: 1.25rem;
}
/* Dark mode support */
.dark .nav-tabs-lte {
border-bottom-color: #374151;
}
.dark .nav-link-lte {
color: #9ca3af;
}
.dark .nav-link-lte:hover {
color: var(--lte-primary, #0d6efd);
border-color: #374151 #374151 #374151;
background-color: rgba(255, 255, 255, 0.02);
}
.dark .nav-link-lte.active {
color: var(--lte-primary, #0d6efd);
background-color: #1f2937;
border-color: #374151 #374151 #1f2937;
}
.dark .nav-pills-lte {
background-color: #1f2937;
border-color: #374151;
}
.dark .nav-pills-lte .nav-link-pill {
color: #9ca3af;
}
.dark .nav-pills-lte .nav-link-pill:hover {
color: #e5e7eb;
background-color: rgba(255, 255, 255, 0.04);
}
.dark .nav-pills-lte .nav-link-pill.active {
color: #f3f4f6;
background-color: #374151;
}
.dark .nav-buttons-lte .nav-btn-lte {
color: #9ca3af;
background-color: #1f2937;
border-color: #374151;
}
.dark .nav-buttons-lte .nav-btn-lte:hover {
background-color: #374151;
}
</style>
<div
x-data="{ activeTab: '{{ $activeTab }}' }"
{{ $attributes }}
>
{{-- Tab Navigation --}}
@if($variant === 'pills')
<div class="nav-pills-lte">
<div class="flex flex-wrap gap-1 p-1 bg-background-secondary dark:bg-gray-800 rounded-lg border border-neutral dark:border-gray-700">
@elseif($variant === 'buttons')
<div class="nav-buttons-lte">
<div class="flex gap-2">
@else
<ul class="nav-tabs-lte">
<ul class="flex flex-wrap list-none border-b border-neutral dark:border-gray-700">
@endif
@foreach($tabs as $tab)
@php
@@ -197,46 +43,52 @@
<button
type="button"
@click="activeTab = '{{ $tabName }}'"
:class="activeTab === '{{ $tabName }}' ? 'active' : ''"
class="nav-link-pill"
:class="activeTab === '{{ $tabName }}'
? 'text-primary bg-background dark:bg-gray-700 shadow-sm'
: 'text-secondary dark:text-gray-400 hover:text-primary hover:bg-black/5 dark:hover:bg-white/5'"
class="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-md transition-all duration-200 cursor-pointer"
>
@if($tabIcon)
<x-dynamic-component :component="$tabIcon" class="size-4" />
@endif
<span>{{ $tabLabel }}</span>
@if($tabBadge)
<span class="tab-badge-lte">{{ $tabBadge }}</span>
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-semibold text-primary bg-primary/10 rounded-full">{{ $tabBadge }}</span>
@endif
</button>
@elseif($variant === 'buttons')
<button
type="button"
@click="activeTab = '{{ $tabName }}'"
:class="activeTab === '{{ $tabName }}' ? 'active' : ''"
class="nav-btn-lte"
:class="activeTab === '{{ $tabName }}'
? 'text-white bg-primary border-primary'
: 'text-secondary dark:text-gray-400 bg-background dark:bg-gray-800 border-neutral dark:border-gray-700 hover:bg-background-secondary dark:hover:bg-gray-700 hover:border-gray-400'"
class="flex items-center gap-2 px-4 py-2.5 text-sm font-medium border rounded-lg shadow-sm transition-all duration-200 cursor-pointer"
>
@if($tabIcon)
<x-dynamic-component :component="$tabIcon" class="size-4" />
@endif
<span>{{ $tabLabel }}</span>
@if($tabBadge)
<span class="tab-badge-lte">{{ $tabBadge }}</span>
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-semibold text-primary bg-primary/10 rounded-full">{{ $tabBadge }}</span>
@endif
</button>
@else
<li class="nav-item-lte">
<li class="-mb-px">
<button
type="button"
@click="activeTab = '{{ $tabName }}'"
:class="activeTab === '{{ $tabName }}' ? 'active' : ''"
class="nav-link-lte"
:class="activeTab === '{{ $tabName }}'
? 'text-primary bg-background dark:bg-gray-800 border-neutral dark:border-gray-700 border-b-background dark:border-b-gray-800'
: 'text-secondary dark:text-gray-400 border-transparent hover:text-primary hover:border-gray-200 dark:hover:border-gray-600 hover:bg-black/5 dark:hover:bg-white/5'"
class="flex items-center gap-2 px-4 py-2.5 text-sm font-medium border border-b-0 rounded-t-lg transition-all duration-150 cursor-pointer"
>
@if($tabIcon)
<x-dynamic-component :component="$tabIcon" class="size-4" />
@endif
<span>{{ $tabLabel }}</span>
@if($tabBadge)
<span class="tab-badge-lte">{{ $tabBadge }}</span>
<span class="inline-flex items-center justify-center px-2 py-0.5 text-xs font-semibold text-primary bg-primary/10 rounded-full">{{ $tabBadge }}</span>
@endif
</button>
</li>
@@ -249,7 +101,7 @@
@endif
{{-- Tab Panels --}}
<div class="tab-content-lte">
<div class="mt-5">
{{ $slot }}
</div>
</div>

View File

@@ -1,5 +1,6 @@
{{--
Toggle/Switch Component - AdminLTE style
Toggle/Switch Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.toggle wire:model="enabled" label="Enable Feature" />
@@ -11,116 +12,28 @@
'disabled' => false,
])
<style>
.toggle-wrapper-lte {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
cursor: pointer;
}
.toggle-wrapper-lte-disabled {
cursor: not-allowed;
opacity: 0.65;
}
.toggle-content-lte {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.toggle-label-lte {
font-size: 0.875rem;
font-weight: 500;
color: #495057;
}
.toggle-description-lte {
font-size: 0.8125rem;
color: var(--lte-secondary, #6c757d);
}
.toggle-switch-lte {
position: relative;
flex-shrink: 0;
}
.toggle-input-lte {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.toggle-track-lte {
display: block;
width: 2.75rem;
height: 1.5rem;
background-color: #dee2e6;
border-radius: 9999px;
border: 2px solid transparent;
transition: background-color var(--lte-transition-speed, 0.3s) ease;
}
.toggle-input-lte:checked + .toggle-track-lte {
background-color: var(--lte-primary, #0d6efd);
}
.toggle-input-lte:focus-visible + .toggle-track-lte {
outline: none;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.toggle-input-lte:disabled + .toggle-track-lte {
opacity: 0.5;
cursor: not-allowed;
}
.toggle-thumb-lte {
position: absolute;
top: 0.125rem;
left: 0.125rem;
width: 1.25rem;
height: 1.25rem;
background-color: #fff;
border-radius: 9999px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
transition: transform var(--lte-transition-speed, 0.3s) ease;
pointer-events: none;
}
.toggle-input-lte:checked ~ .toggle-thumb-lte {
transform: translateX(1.25rem);
}
/* Dark mode */
.dark .toggle-label-lte {
color: #d1d5db;
}
.dark .toggle-description-lte {
color: #9ca3af;
}
.dark .toggle-track-lte {
background-color: #374151;
}
.dark .toggle-input-lte:checked + .toggle-track-lte {
background-color: var(--lte-primary, #0d6efd);
}
</style>
<label {{ $attributes->only('class')->class(['toggle-wrapper-lte', 'toggle-wrapper-lte-disabled' => $disabled]) }}>
<label {{ $attributes->only('class')->class([
'flex items-center justify-between gap-4 cursor-pointer',
'cursor-not-allowed opacity-65' => $disabled,
]) }}>
@if($label || $description)
<div class="toggle-content-lte">
<div class="flex flex-col gap-0.5">
@if($label)
<span class="toggle-label-lte">{{ $label }}</span>
<span class="text-sm font-medium text-secondary dark:text-gray-300">{{ $label }}</span>
@endif
@if($description)
<p class="toggle-description-lte">{{ $description }}</p>
<p class="text-xs text-secondary/80 dark:text-gray-400">{{ $description }}</p>
@endif
</div>
@endif
<div class="toggle-switch-lte">
<div class="relative shrink-0">
<input
type="checkbox"
{{ $attributes->whereDoesntStartWith('class')->merge(['disabled' => $disabled]) }}
class="toggle-input-lte"
class="peer sr-only"
/>
<div class="toggle-track-lte"></div>
<div class="toggle-thumb-lte"></div>
<div class="w-11 h-6 bg-neutral/40 dark:bg-gray-600 rounded-full border-2 border-transparent transition-colors duration-200 peer-checked:bg-primary peer-focus-visible:ring-2 peer-focus-visible:ring-primary/25 peer-disabled:opacity-50 peer-disabled:cursor-not-allowed"></div>
<div class="absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow-sm transition-transform duration-200 peer-checked:translate-x-5 pointer-events-none"></div>
</div>
</label>

View File

@@ -1,5 +1,6 @@
{{--
Tooltip Component - AdminLTE style
Tooltip Component - LucentUI Theme
Migrated from AdminLTE to Tailwind
Usage:
<x-proxmox::ui.tooltip text="This is a tooltip">
@@ -13,44 +14,19 @@
])
@php
$positionStyles = [
'top' => 'bottom: 100%; left: 50%; transform: translateX(-50%); margin-bottom: 0.5rem;',
'bottom' => 'top: 100%; left: 50%; transform: translateX(-50%); margin-top: 0.5rem;',
'left' => 'right: 100%; top: 50%; transform: translateY(-50%); margin-right: 0.5rem;',
'right' => 'left: 100%; top: 50%; transform: translateY(-50%); margin-left: 0.5rem;',
$positionClasses = [
'top' => 'bottom-full left-1/2 -translate-x-1/2 mb-2',
'bottom' => 'top-full left-1/2 -translate-x-1/2 mt-2',
'left' => 'right-full top-1/2 -translate-y-1/2 mr-2',
'right' => 'left-full top-1/2 -translate-y-1/2 ml-2',
];
@endphp
<style>
.tooltip-lte-wrapper {
position: relative;
display: inline-block;
}
.tooltip-lte {
position: absolute;
z-index: 1070;
pointer-events: none;
}
.tooltip-lte-inner {
padding: 0.375rem 0.75rem;
font-size: 0.8125rem;
font-weight: 500;
color: #fff;
background-color: #212529;
border-radius: var(--lte-border-radius, 0.375rem);
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.15);
white-space: nowrap;
}
.dark .tooltip-lte-inner {
background-color: #4b5563;
}
</style>
<div
x-data="{ show: false }"
@mouseenter="show = true"
@mouseleave="show = false"
class="tooltip-lte-wrapper"
class="relative inline-block"
{{ $attributes }}
>
{{ $slot }}
@@ -64,10 +40,9 @@
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="tooltip-lte"
style="{{ $positionStyles[$position] ?? $positionStyles['top'] }}"
class="absolute z-[1070] pointer-events-none {{ $positionClasses[$position] ?? $positionClasses['top'] }}"
>
<div class="tooltip-lte-inner">
<div class="px-3 py-1.5 text-xs font-medium text-white bg-gray-900 dark:bg-gray-600 rounded-lg shadow-lg whitespace-nowrap">
{{ $text }}
</div>
</div>