-@if (session()->has('notify-flash'))
- @php
- $notify = session('notify-flash')
- @endphp
+@if ($notify)
diff --git a/resources/views/tooltip.blade.php b/resources/views/tooltip.blade.php
index aba4d332..f8a9eee0 100644
--- a/resources/views/tooltip.blade.php
+++ b/resources/views/tooltip.blade.php
@@ -1,8 +1,4 @@
-@props([
- 'tip' => '',
- 'open' => false
-])
-
-
merge(['class'=>"tooltip"]) }} data-tip="{{ $tip }}">
+
+ @isset($content)
{{ $content }}
@endisset
{{ $slot }}
diff --git a/src/Commands/LazyInstallCommand.php b/src/Commands/LazyInstallCommand.php
index e5c19d59..0eb1bc75 100644
--- a/src/Commands/LazyInstallCommand.php
+++ b/src/Commands/LazyInstallCommand.php
@@ -26,8 +26,6 @@ public function handle(): int
'axios',
'quill@^2.0.3',
'sanitize-html',
- 'theme-change',
- 'alpinejs',
];
foreach ($packages as $package) {
diff --git a/src/Components/Accordion.php b/src/Components/Accordion.php
index c2c4bd52..6ff55b54 100644
--- a/src/Components/Accordion.php
+++ b/src/Components/Accordion.php
@@ -2,34 +2,90 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Accordion extends LazyComponent
+class Accordion extends DaisyComponent
{
- /**
- * Get the view / contents that represent the component.
- */
- public function render(): Closure|View
+ protected const VIEW = 'lazy::accordion';
+
+ public array $items = [];
+
+ public bool $collectionMode = false;
+
+ public function __construct(
+ public string $title = '',
+ public string $label = '',
+ public bool $active = false,
+ public string $name = 'accordion',
+ public bool $toggle = false,
+ public string $type = 'plus',
+ array $items = [],
+ public bool $multiple = false,
+ ) {
+ $this->collectionMode = $items !== [];
+ $this->toggle = $this->toggle || $this->multiple;
+
+ foreach (array_values($items) as $index => $item) {
+ $normalized = is_array($item) ? $item : ['title' => $item];
+
+ $this->items[] = [
+ 'title' => (string) ($normalized['title'] ?? $normalized['label'] ?? ''),
+ 'content' => $normalized['content'] ?? '',
+ 'active' => (bool) ($normalized['active'] ?? false),
+ 'checkedAttribute' => (bool) ($normalized['active'] ?? false) ? 'checked=checked' : '',
+ 'disabled' => (bool) ($normalized['disabled'] ?? false),
+ 'name' => (string) ($normalized['name'] ?? $this->name),
+ 'inputType' => ($normalized['toggle'] ?? $this->toggle) ? 'checkbox' : 'radio',
+ 'classes' => $this->classes([
+ 'collapse',
+ 'bg-base-200',
+ 'mb-2',
+ 'collapse-arrow' => ($normalized['type'] ?? $this->type) === 'arrow',
+ 'collapse-plus' => ($normalized['type'] ?? $this->type) !== 'arrow',
+ ]),
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ if ($this->collectionMode) {
+ return [];
+ }
+
+ return [
+ 'collapse',
+ 'bg-base-200',
+ 'mb-2',
+ 'collapse-arrow' => $this->type === 'arrow',
+ 'collapse-plus' => $this->type !== 'arrow',
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->items;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'classes' => '',
+ ], $items);
+ }
+
+ return [
+ 'items' => $items,
+ 'resolvedTitle' => $this->title ?: $this->label,
+ 'inputType' => $this->toggle ? 'checkbox' : 'radio',
+ 'checkedAttribute' => $this->active ? 'checked=checked' : '',
+ ];
+ }
+
+ protected function viewClasses(): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
-
- $attributes['title'] ??= $attributes['label'] ?? null;
- $attributes['active'] ??= false;
- $attributes['name'] ??= 'accordion';
-
- $data['attributes'] = $attributes;
-
- return view('lazy::accordion', $this->mergeData($data, [
- 'collapse',
- 'bg-base-200 mb-2',
- 'collapse-arrow' => $attributes->get('type') === 'arrow',
- 'collapse-plus' => $attributes->get('type') === 'plus' || $attributes->get('type') === null,
- ], [
- 'type',
- ]))->render();
- };
+ return [
+ 'title' => 'collapse-title text-xl font-medium',
+ 'content' => 'collapse-content',
+ ];
}
}
diff --git a/src/Components/Alert.php b/src/Components/Alert.php
index 960315b0..57f1b825 100644
--- a/src/Components/Alert.php
+++ b/src/Components/Alert.php
@@ -8,6 +8,14 @@
class Alert extends LazyComponent
{
+ protected function allowedColors(): array
+ {
+ return [
+ ...parent::allowedColors(),
+ 'danger',
+ ];
+ }
+
public function getTypeByAttribute(ComponentAttributeBag $attribute, ?string $default = null): ?string
{
return $this->getKeyByAttribute($attribute, $this->allowedColors(), 'type', $default);
@@ -19,16 +27,23 @@ public function render(): Closure
$attributes = $this->getAttributesFromData($data);
$attributes['disabled'] = (bool) $attributes->get('disabled');
$type = $this->getTypeByAttribute($attributes) ?? $this->getColorByAttribute($attributes, 'default');
-
$data['attributes'] = $attributes;
+ $data['type'] = $type;
return view('lazy::alert', $this->mergeData($data, [
'alert',
'shadow-lg mt-1 mb-2',
+ 'alert-soft' => $attributes->get('soft', false),
+ 'alert-dash' => $attributes->get('dash', false),
'alert-info' => $type === 'info',
'alert-success' => $type === 'success',
'alert-warning' => $type === 'warning',
- 'alert-error' => $type === 'error',
+ 'alert-error' => in_array($type, ['error', 'danger'], true),
+ ], [
+ 'type',
+ 'color',
+ 'soft',
+ 'dash',
]))->render();
};
}
diff --git a/src/Components/Aura.php b/src/Components/Aura.php
new file mode 100644
index 00000000..2419eaf8
--- /dev/null
+++ b/src/Components/Aura.php
@@ -0,0 +1,34 @@
+ $this->type === 'dual',
+ 'aura-rainbow' => $this->type === 'rainbow',
+ 'aura-holo' => $this->type === 'holo',
+ 'aura-gold' => $this->type === 'gold',
+ 'aura-silver' => $this->type === 'silver',
+ 'aura-glow' => $this->glow,
+ 'aura-xs' => $this->size === 'xs',
+ 'aura-sm' => $this->size === 'sm',
+ 'aura-md' => $this->size === 'md',
+ 'aura-lg' => $this->size === 'lg',
+ 'aura-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/AvatarGroup.php b/src/Components/AvatarGroup.php
index ab42c0a5..5fadcb12 100644
--- a/src/Components/AvatarGroup.php
+++ b/src/Components/AvatarGroup.php
@@ -2,13 +2,25 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class AvatarGroup extends LazyComponent
+class AvatarGroup extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::avatar-group';
+
+ public function __construct(public string $spacing = '6') {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return view('lazy::avatar-group');
+ return [
+ 'avatar-group',
+ '-space-x-1' => $this->spacing === '1',
+ '-space-x-2' => $this->spacing === '2',
+ '-space-x-3' => $this->spacing === '3',
+ '-space-x-4' => $this->spacing === '4',
+ '-space-x-5' => $this->spacing === '5',
+ '-space-x-6' => $this->spacing === '6',
+ '-space-x-8' => $this->spacing === '8',
+ ];
}
}
diff --git a/src/Components/Badge.php b/src/Components/Badge.php
index 7c64907d..5c76183b 100644
--- a/src/Components/Badge.php
+++ b/src/Components/Badge.php
@@ -12,6 +12,15 @@ public function __construct(?string $label = '')
$this->label = $label;
}
+ protected function allowedColors(): array
+ {
+ return [
+ ...parent::allowedColors(),
+ 'ghost',
+ 'danger',
+ ];
+ }
+
public function allowedPosition(): array
{
return [
@@ -24,12 +33,12 @@ public function render(): \Closure|View
{
return function (array $data) {
$attributes = $this->getAttributesFromData($data);
-
$color = $this->getColorByAttribute($attributes);
$size = $this->getSizeByAttribute($attributes);
return view('lazy::badge', $this->mergeData($data, [
'badge',
+ 'badge-neutral' => $color === 'neutral',
'badge-primary' => $color === 'primary',
'badge-secondary' => $color === 'secondary',
'badge-accent' => $color === 'accent',
@@ -37,15 +46,19 @@ public function render(): \Closure|View
'badge-info' => $color === 'info',
'badge-success' => $color === 'success',
'badge-warning' => $color === 'warning',
- 'badge-error' => $color === 'error',
- 'badge-danger' => $color === 'danger',
- // sizes
+ 'badge-error' => in_array($color, ['error', 'danger'], true),
+ 'badge-xl' => $size === 'xl',
'badge-lg' => $size === 'lg',
'badge-md' => $size === 'md',
'badge-sm' => $size === 'sm',
'badge-xs' => $size === 'xs',
- // other
- 'badge-outline' => $attributes->get('outline'),
+ 'badge-outline' => $attributes->get('outline', false),
+ 'badge-dash' => $attributes->get('dash', false),
+ 'badge-soft' => $attributes->get('soft', false),
+ ], [
+ 'outline',
+ 'dash',
+ 'soft',
]))->render();
};
}
diff --git a/src/Components/Breadcrumbs.php b/src/Components/Breadcrumbs.php
index f9001ebf..2c516496 100644
--- a/src/Components/Breadcrumbs.php
+++ b/src/Components/Breadcrumbs.php
@@ -2,17 +2,29 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Breadcrumbs extends LazyComponent
+class Breadcrumbs extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::breadcrumbs';
+
+ public array $items = [];
+
+ public function __construct(array $items = [])
+ {
+ foreach (array_values($items) as $index => $item) {
+ $normalized = is_array($item) ? $item : ['label' => $item];
+
+ $this->items[] = [
+ 'label' => (string) ($normalized['label'] ?? $normalized['title'] ?? ''),
+ 'href' => $normalized['href'] ?? $normalized['url'] ?? null,
+ 'current' => (bool) ($normalized['current'] ?? $index === count($items) - 1),
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- return view('lazy::breadcrumbs', $this->mergeData($data, [
- 'text-sm breadcrumbs',
- ]))->render();
- };
+ return ['text-sm', 'breadcrumbs'];
}
}
diff --git a/src/Components/Btn.php b/src/Components/Btn.php
index 8b4f587a..9f8b51e6 100644
--- a/src/Components/Btn.php
+++ b/src/Components/Btn.php
@@ -29,6 +29,7 @@ public function __construct(
public function allowedColors(): array
{
return [
+ 'neutral',
'primary',
'secondary',
'accent',
@@ -42,30 +43,30 @@ public function allowedColors(): array
];
}
- /**
- * Get the view / contents that represent the component.
- */
public function render(): \Closure|View
{
return function (array $data) {
$attributes = $this->getAttributesFromData($data);
+
if ($this->tag === 'a') {
$attributes['href'] = $this->href;
} else {
- $attributes['type'] = 'submit';
+ $attributes['type'] = $attributes->get('type', 'submit');
$attributes['wire:loading.attr'] = 'disabled';
$attributes['wire:loading.class'] = $this->disableClass.' loading loading-spinner';
}
- // $data['iconSize'] = $this->iconSize($attributes);
$data['disabled'] = (bool) $attributes->get('disabled');
-
$data['attributes'] = $attributes;
$this->outline = $attributes->get('outline', false);
$color = $this->getColorByAttribute($attributes);
$size = $this->getSizeByAttribute($attributes);
+ $unstyled = $this->isTruthyAttribute($attributes, 'unstyled');
+ $data['contentClass'] = $unstyled || (! $this->icon && ! $this->rightIcon) ? '' : 'gap-2';
+ $data['standaloneClass'] = $unstyled ? '' : 'mr-2';
+
return view('lazy::btn', $this->mergeData($data, [
'btn',
'join' => $attributes->get('group', false),
@@ -73,23 +74,24 @@ public function render(): \Closure|View
'glass' => $this->glass,
'btn-active' => $this->active,
'btn-outline' => $attributes->get('outline', false),
+ 'btn-dash' => $attributes->get('dash', false),
+ 'btn-soft' => $attributes->get('soft', false),
'btn-disabled' => $attributes->get('disabled', false),
- // colors
+ 'btn-neutral' => $color === 'neutral',
'btn-primary' => $color === 'primary',
'btn-secondary' => $color === 'secondary',
'btn-accent' => $color === 'accent',
'btn-info' => $color === 'info',
'btn-success' => $color === 'success',
'btn-warning' => $color === 'warning',
- 'btn-error' => $color === 'error',
+ 'btn-error' => in_array($color, ['error', 'danger'], true),
'btn-ghost' => $color === 'ghost',
'btn-link' => $color === 'link',
- // sizes
+ 'btn-xl' => $size === 'xl',
'btn-lg' => $size === 'lg',
'btn-md' => $size === 'md',
'btn-sm' => $size === 'sm',
'btn-xs' => $size === 'xs',
- // other
'btn-wide' => $attributes->get('wide', false),
'btn-block' => $attributes->get('block', false),
'btn-circle' => $attributes->get('circle', false),
@@ -102,6 +104,8 @@ public function render(): \Closure|View
'group',
'active',
'outline',
+ 'dash',
+ 'soft',
'glass',
]))->render();
};
diff --git a/src/Components/Buttons/BtnLogout.php b/src/Components/Buttons/BtnLogout.php
index 66a4c865..11e62081 100644
--- a/src/Components/Buttons/BtnLogout.php
+++ b/src/Components/Buttons/BtnLogout.php
@@ -7,6 +7,17 @@
class BtnLogout extends Component
{
+ public string $target;
+
+ public function __construct(
+ public string $icon = '',
+ public ?string $action = null,
+ public string $route = 'logout',
+ ) {
+ $this->target = $this->action
+ ?? (app('router')->has($this->route) ? route($this->route) : '#');
+ }
+
public function render(): View
{
return view('lazy::btn.logout');
diff --git a/src/Components/Calendar.php b/src/Components/Calendar.php
new file mode 100644
index 00000000..3e0669fa
--- /dev/null
+++ b/src/Components/Calendar.php
@@ -0,0 +1,40 @@
+normalizedDriver()) {
+ 'cally' => ['cally bg-base-100 border border-base-300 shadow-lg rounded-box'],
+ 'vc' => ['vc'],
+ 'react-day-picker' => ['react-day-picker'],
+ default => ['input'],
+ };
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['tag' => $this->normalizedDriver()];
+ }
+
+ private function normalizedDriver(): string
+ {
+ return match (strtolower($this->driver)) {
+ 'cally' => 'cally',
+ 'vanilla', 'vanilla-calendar', 'vc' => 'vc',
+ 'react', 'react-day-picker', 'day-picker' => 'react-day-picker',
+ default => 'native',
+ };
+ }
+}
diff --git a/src/Components/Card.php b/src/Components/Card.php
new file mode 100644
index 00000000..3b39b1a5
--- /dev/null
+++ b/src/Components/Card.php
@@ -0,0 +1,38 @@
+ 'card-body',
+ 'title' => 'card-title',
+ 'actions' => 'card-actions justify-end',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'card',
+ 'card-border' => $this->bordered,
+ 'card-sm' => $this->compact,
+ 'card-side' => $this->side,
+ 'image-full' => $this->imageFull,
+ ];
+ }
+}
diff --git a/src/Components/Carousel.php b/src/Components/Carousel.php
new file mode 100644
index 00000000..234bd0e4
--- /dev/null
+++ b/src/Components/Carousel.php
@@ -0,0 +1,58 @@
+ $item) {
+ $normalized = is_array($item) ? $item : ['content' => $item];
+
+ $this->items[] = [
+ 'id' => (string) ($normalized['id'] ?? 'slide-'.($index + 1)),
+ 'src' => $normalized['src'] ?? null,
+ 'alt' => (string) ($normalized['alt'] ?? ''),
+ 'content' => $normalized['content'] ?? null,
+ 'classes' => $this->classes([
+ 'carousel-item',
+ (string) ($normalized['class'] ?? '') => isset($normalized['class']),
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->items;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'classes' => '',
+ ], $items);
+ }
+
+ return ['items' => $items];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'carousel',
+ 'carousel-vertical' => $this->vertical,
+ 'carousel-center' => $this->center,
+ 'carousel-end' => $this->end,
+ ];
+ }
+}
diff --git a/src/Components/CarouselItem.php b/src/Components/CarouselItem.php
new file mode 100644
index 00000000..27525fe4
--- /dev/null
+++ b/src/Components/CarouselItem.php
@@ -0,0 +1,15 @@
+getAttributesFromData($data);
- $position = $this->position ?? $this->getKeyByAttribute($data['attributes'], [
- 'left',
- 'right',
- 'start',
- 'end',
- ], 'start');
+ $position = $this->position;
+
+ if ($attributes->has('left')) {
+ $position = 'left';
+ } elseif ($attributes->has('right')) {
+ $position = 'right';
+ } elseif ($attributes->has('start')) {
+ $position = 'start';
+ } elseif ($attributes->has('end')) {
+ $position = 'end';
+ }
$color = $this->getColorByAttribute($attributes);
return view('lazy::chat', $this->mergeData($data, [
'chat',
- 'chat-start' => in_array($position, ['left', 'start']),
- 'chat-end' => in_array($position, ['right', 'end']),
+ 'chat-start' => in_array($position, ['left', 'start'], true),
+ 'chat-end' => in_array($position, ['right', 'end'], true),
'chat-bubble-primary' => $color === 'primary',
'chat-bubble-secondary' => $color === 'secondary',
'chat-bubble-accent' => $color === 'accent',
@@ -40,7 +45,12 @@ public function render(): Closure|View
'chat-bubble-success' => $color === 'success',
'chat-bubble-warning' => $color === 'warning',
'chat-bubble-error' => $color === 'error',
-
+ ], [
+ 'left',
+ 'right',
+ 'start',
+ 'end',
+ 'color',
]))->render();
};
}
diff --git a/src/Components/Checkbox.php b/src/Components/Checkbox.php
index 943c7418..4effaa87 100644
--- a/src/Components/Checkbox.php
+++ b/src/Components/Checkbox.php
@@ -2,45 +2,44 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Checkbox extends LazyComponent
+class Checkbox extends DaisyComponent
{
- public function __construct(?string $label = '')
- {
- $this->label = $label;
+ protected const VIEW = 'lazy::checkbox';
+
+ public function __construct(
+ ?string $label = '',
+ public string $color = '',
+ public string $size = '',
+ ) {
+ $this->label = $label ?? '';
}
- public function render(): \Closure|View
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['type'] = 'checkbox';
- $data['attributes'] = $attributes;
+ $attributes['type'] = 'checkbox';
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
+ return $attributes;
+ }
- return view('lazy::checkbox', $this->mergeData($data, [
- 'checkbox',
- // colors
- 'checkbox-primary' => $color === 'primary',
- 'checkbox-secondary' => $color === 'secondary',
- 'checkbox-accent' => $color === 'accent',
- 'checkbox-success' => $color === 'success',
- 'checkbox-warning' => $color === 'warning',
- 'checkbox-info' => $color === 'info',
- 'checkbox-error' => $color === 'error',
- // sizes
- 'checkbox-lg' => $size === 'lg',
- 'checkbox-md' => $size === 'md',
- 'checkbox-sm' => $size === 'sm',
- 'checkbox-xs' => $size === 'xs',
- ], [
- 'color',
- 'size',
- ]))->render();
- };
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'checkbox',
+ 'checkbox-neutral' => $this->color === 'neutral',
+ 'checkbox-primary' => $this->color === 'primary',
+ 'checkbox-secondary' => $this->color === 'secondary',
+ 'checkbox-accent' => $this->color === 'accent',
+ 'checkbox-success' => $this->color === 'success',
+ 'checkbox-warning' => $this->color === 'warning',
+ 'checkbox-info' => $this->color === 'info',
+ 'checkbox-error' => $this->color === 'error',
+ 'checkbox-xl' => $this->size === 'xl',
+ 'checkbox-lg' => $this->size === 'lg',
+ 'checkbox-md' => $this->size === 'md',
+ 'checkbox-sm' => $this->size === 'sm',
+ 'checkbox-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/Choices.php b/src/Components/Choices.php
index 7b96c2c8..0a038651 100644
--- a/src/Components/Choices.php
+++ b/src/Components/Choices.php
@@ -2,38 +2,81 @@
namespace Step2dev\LazyUI\Components;
+use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
class Choices extends LazyComponent
{
- public function __construct() {}
+ public array $normalizedOptions = [];
- /**
- * Get the view / contents that represent the component.
- */
- public function render(): \Closure
+ public function __construct(
+ public array|string $options = [],
+ public string $label = '',
+ public string $placeholder = 'Please select a value',
+ public string|int|float|null $value = null,
+ ) {
+ if (is_array($this->options)) {
+ foreach ($this->options as $key => $option) {
+ if (is_array($option)) {
+ $optionValue = $option['value'] ?? $key;
+ $optionLabel = $option['label'] ?? $option['text'] ?? $optionValue;
+ $disabled = (bool) ($option['disabled'] ?? false);
+ } else {
+ $optionValue = is_int($key) ? $option : $key;
+ $optionLabel = $option;
+ $disabled = false;
+ }
+
+ $this->normalizedOptions[] = [
+ 'value' => $optionValue,
+ 'label' => (string) $optionLabel,
+ 'disabled' => $disabled,
+ ];
+ }
+ }
+ }
+
+ public function render(): \Closure|View
{
return function (array $data) {
- if ($data['slot']->isNotEmpty()) {
- preg_match_all(
- '/
|.*?)".*?>(?.*?)<\/option>/m',
- $data['slot'],
- $matches,
- PREG_SET_ORDER,
- 0
- );
- $options = [];
-
- foreach ($matches as $item) {
- $options[] = [
- 'value' => $item['value'],
- 'label' => $item['label'],
- ];
+ $attributes = $this->getAttributesFromData($data);
+ $modelAttribute = null;
+
+ foreach (array_keys($attributes->getAttributes()) as $key) {
+ if (str_starts_with($key, 'wire:model')) {
+ $modelAttribute = $key;
+ break;
}
- $data['attributes']['options'] = json_encode($options);
}
- return view('components.admin.select', $this->mergeData($data))->render();
+ $model = $modelAttribute ? $attributes->get($modelAttribute) : null;
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+
+ return view('lazy::choices', [
+ ...$data,
+ 'attributes' => $attributes,
+ 'unstyled' => $unstyled,
+ 'viewClasses' => [
+ 'wrapper' => $unstyled ? '' : 'fieldset w-full',
+ 'label' => $unstyled ? '' : 'label',
+ 'native' => $unstyled ? '' : 'sr-only',
+ 'button' => $unstyled ? '' : 'select w-full flex items-center justify-between text-left cursor-pointer',
+ 'listbox' => $unstyled ? '' : 'menu dropdown-content bg-base-100 rounded-box z-50 mt-1 max-h-60 w-full overflow-auto p-2 shadow-lg',
+ ],
+ 'model' => $model,
+ 'modelLive' => $modelAttribute && str_contains($modelAttribute, '.live'),
+ 'optionsExpression' => is_string($this->options) ? ($this->options ?: '[]') : '[]',
+ 'nativeAttributes' => $attributes->only(['id', 'name', 'required', 'disabled', 'form']),
+ 'visualAttributes' => $attributes->except(array_filter([
+ $modelAttribute,
+ 'id', 'name', 'required', 'disabled', 'form', 'unstyled',
+ ])),
+ ]);
};
}
+
+ private function truthy(mixed $value): bool
+ {
+ return $value !== false && $value !== null && $value !== 'false' && $value !== '0' && $value !== 0;
+ }
}
diff --git a/src/Components/Collapse.php b/src/Components/Collapse.php
new file mode 100644
index 00000000..915d3d52
--- /dev/null
+++ b/src/Components/Collapse.php
@@ -0,0 +1,34 @@
+ 'collapse-title',
+ 'content' => 'collapse-content',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'collapse',
+ 'collapse-arrow' => $this->arrow && ! $this->plus,
+ 'collapse-plus' => $this->plus,
+ ];
+ }
+}
diff --git a/src/Components/Countdown.php b/src/Components/Countdown.php
index d256c49c..3e49c8dc 100644
--- a/src/Components/Countdown.php
+++ b/src/Components/Countdown.php
@@ -2,15 +2,23 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Countdown extends LazyComponent
+class Countdown extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::countdown';
+
+ public int $resolvedValue;
+
+ public function __construct(
+ public int|float|string $value = 0,
+ public string $label = '',
+ ) {
+ $this->resolvedValue = max(0, min(999, (int) $this->value));
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- return view('lazy::countdown', $this->mergeData($data))->render();
- };
+ return ['countdown'];
}
}
diff --git a/src/Components/DaisyComponent.php b/src/Components/DaisyComponent.php
new file mode 100644
index 00000000..ddb57799
--- /dev/null
+++ b/src/Components/DaisyComponent.php
@@ -0,0 +1,101 @@
+prepareAttributes($this->getAttributesFromData($data));
+ $smartAttributes = $this->syncCommonSmartAttributes($attributes);
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+
+ $data['unstyled'] = $unstyled;
+ $data['attributes'] = $attributes
+ ->except(['unstyled', ...$smartAttributes, ...$this->consumedAttributes()])
+ ->class($unstyled ? [] : $this->componentClasses($data, $attributes));
+
+ return view(static::VIEW, [
+ ...$data,
+ 'viewClasses' => $this->resolvedViewClasses($unstyled),
+ ...$this->componentData($data, $attributes),
+ ])->render();
+ };
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
+ {
+ return $attributes;
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [];
+ }
+
+ protected function viewClasses(): array
+ {
+ return [];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return [];
+ }
+
+ protected function truthy(mixed $value): bool
+ {
+ return $value !== false && $value !== null && $value !== 'false' && $value !== '0' && $value !== 0;
+ }
+
+ private function resolvedViewClasses(bool $unstyled): array
+ {
+ $classes = [];
+
+ foreach ($this->viewClasses() as $key => $value) {
+ $classes[$key] = $unstyled ? '' : $this->classes($value);
+ }
+
+ return $classes;
+ }
+
+ private function syncCommonSmartAttributes(ComponentAttributeBag $attributes): array
+ {
+ $consumed = [];
+
+ if (property_exists($this, 'color')) {
+ foreach ($this->allowedColors() as $color) {
+ if ($attributes->has($color) && $this->truthy($attributes->get($color))) {
+ $this->color = $color;
+ $consumed[] = $color;
+ break;
+ }
+ }
+ }
+
+ if (property_exists($this, 'size')) {
+ foreach ($this->allowedSizes() as $size) {
+ if ($attributes->has($size) && $this->truthy($attributes->get($size))) {
+ $this->size = $size;
+ $consumed[] = $size;
+ break;
+ }
+ }
+ }
+
+ return $consumed;
+ }
+}
diff --git a/src/Components/DataList.php b/src/Components/DataList.php
new file mode 100644
index 00000000..6a03b02a
--- /dev/null
+++ b/src/Components/DataList.php
@@ -0,0 +1,72 @@
+rows[] = $this->normalizeRow($item);
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $rows = $this->rows;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $rows = array_map(static fn (array $row): array => [
+ ...$row,
+ 'classes' => '',
+ ], $rows);
+ }
+
+ return ['rows' => $rows];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['list'];
+ }
+
+ private function normalizeRow(mixed $item): array
+ {
+ if (! is_array($item)) {
+ return [
+ 'cells' => [(string) $item],
+ 'classes' => 'list-row',
+ ];
+ }
+
+ $classes = $this->classes([
+ 'list-row',
+ (string) ($item['class'] ?? '') => isset($item['class']),
+ ]);
+
+ if (isset($item['cells']) && is_array($item['cells'])) {
+ return [
+ 'cells' => array_values($item['cells']),
+ 'classes' => $classes,
+ ];
+ }
+
+ $semantic = [];
+ foreach (['media', 'title', 'subtitle', 'description', 'content', 'actions'] as $key) {
+ if (array_key_exists($key, $item)) {
+ $semantic[] = $item[$key];
+ }
+ }
+
+ return [
+ 'cells' => $semantic !== [] ? $semantic : array_values($item),
+ 'classes' => $classes,
+ ];
+ }
+}
diff --git a/src/Components/Diff.php b/src/Components/Diff.php
new file mode 100644
index 00000000..cf934e9e
--- /dev/null
+++ b/src/Components/Diff.php
@@ -0,0 +1,24 @@
+ 'diff-item-1',
+ 'second' => 'diff-item-2',
+ 'resizer' => 'diff-resizer',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['diff'];
+ }
+}
diff --git a/src/Components/Divider.php b/src/Components/Divider.php
index 51abb246..28ee9793 100644
--- a/src/Components/Divider.php
+++ b/src/Components/Divider.php
@@ -2,24 +2,35 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Divider extends LazyComponent
+class Divider extends DaisyComponent
{
- public function render(): Closure|View
+ protected const VIEW = 'lazy::divider';
+
+ public function __construct(
+ public string $text = '',
+ public string $orientation = 'vertical',
+ ) {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
+ $orientation = $this->orientation;
- $orientation = $attributes['orientation'] ?? $attributes['hr'] ?? 'vertical';
- unset($attributes['orientation'], $attributes['hr']);
+ if ($attributes->has('hr') || $attributes->has('horizontal')) {
+ $orientation = 'horizontal';
+ } elseif ($attributes->has('vertical')) {
+ $orientation = 'vertical';
+ }
- return view('lazy::divider', $this->mergeData($data, [
- 'divider',
- 'divider-horizontal' => in_array($orientation, ['horizontal', 'hr']),
- ]))->render();
- };
+ return [
+ 'divider',
+ 'divider-horizontal' => in_array($orientation, ['horizontal', 'hr'], true),
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return ['hr', 'horizontal', 'vertical'];
}
}
diff --git a/src/Components/Dock.php b/src/Components/Dock.php
new file mode 100644
index 00000000..00b740d1
--- /dev/null
+++ b/src/Components/Dock.php
@@ -0,0 +1,63 @@
+ $item) {
+ $normalized = is_array($item) ? $item : ['label' => $item];
+
+ $this->items[] = [
+ 'label' => (string) ($normalized['label'] ?? ''),
+ 'href' => $normalized['href'] ?? null,
+ 'content' => $normalized['content'] ?? $normalized['icon'] ?? null,
+ 'active' => (bool) ($normalized['active'] ?? false),
+ 'disabled' => (bool) ($normalized['disabled'] ?? false),
+ 'classes' => $this->classes([
+ 'dock-active' => (bool) ($normalized['active'] ?? false),
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->items;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'classes' => '',
+ ], $items);
+ }
+
+ return ['items' => $items];
+ }
+
+ protected function viewClasses(): array
+ {
+ return ['label' => 'dock-label'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'dock',
+ 'dock-xs' => $this->size === 'xs',
+ 'dock-sm' => $this->size === 'sm',
+ 'dock-md' => $this->size === 'md',
+ 'dock-lg' => $this->size === 'lg',
+ 'dock-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/DockItem.php b/src/Components/DockItem.php
new file mode 100644
index 00000000..484341c0
--- /dev/null
+++ b/src/Components/DockItem.php
@@ -0,0 +1,31 @@
+ 'dock-label'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['dock-active' => $this->active];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['tag' => $this->href ? 'a' : 'button'];
+ }
+}
diff --git a/src/Components/Drawer.php b/src/Components/Drawer.php
index 53d126d3..4744dc19 100644
--- a/src/Components/Drawer.php
+++ b/src/Components/Drawer.php
@@ -2,21 +2,62 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\Support\Str;
+use Illuminate\View\ComponentAttributeBag;
-class Drawer extends LazyComponent
+class Drawer extends DaisyComponent
{
- public $drawerContent;
+ protected const VIEW = 'lazy::drawer';
- public function __construct($drawerContent)
+ public string $id;
+
+ public array $sideClasses;
+
+ public function __construct(
+ public mixed $drawerContent = null,
+ ?string $id = null,
+ public bool $open = false,
+ public bool $end = false,
+ public bool $menu = true,
+ public string $width = 'md',
+ public string $padding = 'md',
+ public string $background = 'base-100',
+ ) {
+ $this->id = $id ?: 'lazy-drawer-'.Str::uuid();
+
+ $widths = ['xs' => 'w-56', 'sm' => 'w-64', 'md' => 'w-80', 'lg' => 'w-96', 'full' => 'w-full'];
+ $paddings = ['none' => 'p-0', 'xs' => 'p-1', 'sm' => 'p-2', 'md' => 'p-4', 'lg' => 'p-6'];
+ $backgrounds = [
+ 'none' => '',
+ 'base-100' => 'bg-base-100',
+ 'base-200' => 'bg-base-200',
+ 'base-300' => 'bg-base-300',
+ 'neutral' => 'bg-neutral text-neutral-content',
+ 'primary' => 'bg-primary text-primary-content',
+ 'secondary' => 'bg-secondary text-secondary-content',
+ ];
+
+ $this->sideClasses = [
+ 'menu' => $this->menu,
+ $widths[$this->width] ?? $widths['md'],
+ $paddings[$this->padding] ?? $paddings['md'],
+ $backgrounds[$this->background] ?? $backgrounds['base-100'],
+ ];
+ }
+
+ protected function viewClasses(): array
{
- $this->drawerContent = $drawerContent;
+ return [
+ 'toggle' => 'drawer-toggle',
+ 'content' => 'drawer-content',
+ 'side' => 'drawer-side',
+ 'overlay' => 'drawer-overlay',
+ 'panel' => $this->sideClasses,
+ ];
}
- public function render(): View|Closure
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return view('components.drawer');
+ return ['drawer', 'drawer-end' => $this->end];
}
}
diff --git a/src/Components/Dropdown.php b/src/Components/Dropdown.php
new file mode 100644
index 00000000..c489c453
--- /dev/null
+++ b/src/Components/Dropdown.php
@@ -0,0 +1,49 @@
+ 'btn',
+ 'content' => 'dropdown-content menu bg-base-100 rounded-box z-10 mt-2 w-52 p-2 shadow-sm',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'dropdown',
+ 'dropdown-hover' => $this->hover,
+ 'dropdown-open' => $this->open,
+ 'dropdown-close' => $this->close,
+ 'dropdown-start' => $this->start,
+ 'dropdown-center' => $this->center,
+ 'dropdown-end' => $this->end,
+ 'dropdown-top' => $this->top,
+ 'dropdown-bottom' => $this->bottom,
+ 'dropdown-left' => $this->left,
+ 'dropdown-right' => $this->right,
+ ];
+ }
+}
diff --git a/src/Components/Fab.php b/src/Components/Fab.php
new file mode 100644
index 00000000..6f6a7edb
--- /dev/null
+++ b/src/Components/Fab.php
@@ -0,0 +1,29 @@
+ 'btn btn-circle',
+ 'close' => 'fab-close',
+ 'main' => 'fab-main-action',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['fab', 'fab-flower' => $this->flower];
+ }
+}
diff --git a/src/Components/Fieldset.php b/src/Components/Fieldset.php
new file mode 100644
index 00000000..239edbe6
--- /dev/null
+++ b/src/Components/Fieldset.php
@@ -0,0 +1,30 @@
+ 'fieldset-legend',
+ 'label' => 'label',
+ 'hint' => 'label',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['fieldset'];
+ }
+}
diff --git a/src/Components/FileInput.php b/src/Components/FileInput.php
new file mode 100644
index 00000000..87ac184a
--- /dev/null
+++ b/src/Components/FileInput.php
@@ -0,0 +1,42 @@
+ 'join-item'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'file-input',
+ 'file-input-ghost' => $this->ghost,
+ 'file-input-primary' => $this->color === 'primary',
+ 'file-input-secondary' => $this->color === 'secondary',
+ 'file-input-accent' => $this->color === 'accent',
+ 'file-input-neutral' => $this->color === 'neutral',
+ 'file-input-info' => $this->color === 'info',
+ 'file-input-success' => $this->color === 'success',
+ 'file-input-warning' => $this->color === 'warning',
+ 'file-input-error' => $this->color === 'error',
+ 'file-input-xs' => $this->size === 'xs',
+ 'file-input-sm' => $this->size === 'sm',
+ 'file-input-md' => $this->size === 'md',
+ 'file-input-lg' => $this->size === 'lg',
+ 'file-input-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/Filter.php b/src/Components/Filter.php
new file mode 100644
index 00000000..3379638e
--- /dev/null
+++ b/src/Components/Filter.php
@@ -0,0 +1,62 @@
+ $option) {
+ $normalized = is_array($option) ? $option : ['label' => $option];
+ $itemValue = $normalized['value'] ?? (is_int($key) ? $option : $key);
+ $label = $normalized['label'] ?? $normalized['text'] ?? $itemValue;
+
+ $this->items[] = [
+ 'value' => $itemValue,
+ 'label' => (string) $label,
+ 'checked' => array_key_exists('checked', $normalized)
+ ? (bool) $normalized['checked']
+ : (string) $itemValue === (string) $this->value,
+ 'disabled' => (bool) ($normalized['disabled'] ?? false),
+ 'classes' => $this->classes([
+ 'btn',
+ 'btn-active' => (string) $itemValue === (string) $this->value,
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->items;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'classes' => '',
+ ], $items);
+ }
+
+ return ['items' => $items];
+ }
+
+ protected function viewClasses(): array
+ {
+ return ['reset' => 'btn btn-square'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['filter'];
+ }
+}
diff --git a/src/Components/Footer.php b/src/Components/Footer.php
new file mode 100644
index 00000000..04de38ca
--- /dev/null
+++ b/src/Components/Footer.php
@@ -0,0 +1,24 @@
+ $this->horizontal,
+ 'footer-center' => $this->center,
+ ];
+ }
+}
diff --git a/src/Components/Form/FormCheckbox.php b/src/Components/Form/FormCheckbox.php
index 271d5ee6..652066d6 100644
--- a/src/Components/Form/FormCheckbox.php
+++ b/src/Components/Form/FormCheckbox.php
@@ -4,18 +4,27 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormCheckbox extends LazyComponent
{
- public function __construct(string $label = '')
- {
- $this->label = $label;
- }
+ use ResolvesFormFieldState;
+
+ public function __construct(
+ public string $label = '',
+ public string $help = '',
+ public bool $hr = false,
+ ) {}
public function render(): \Closure|View
{
return function (array $data) {
- return view('lazy::form.checkbox', $this->mergeData($data))->render();
+ $attributes = $this->getAttributesFromData($data);
+
+ return view('lazy::form.checkbox', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormImage.php b/src/Components/Form/FormImage.php
index d9e460f7..c5a2e705 100644
--- a/src/Components/Form/FormImage.php
+++ b/src/Components/Form/FormImage.php
@@ -4,22 +4,37 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormImage extends LazyComponent
{
+ use ResolvesFormFieldState;
+
public ?string $placeholder;
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
- {
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public string $help = '',
+ public bool $hr = false,
+ public string $outerClass = '',
+ public string $src = '',
+ ) {
$this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
}
public function render(): \Closure|View
{
return function (array $data) {
- $data['attributes']['required'] = $this->required;
+ $attributes = $this->getAttributesFromData($data);
+ $attributes['required'] = $this->required;
+ $data['attributes'] = $attributes;
- return view('lazy::form.image', $this->mergeData($data))->render();
+ return view('lazy::form.image', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormInput.php b/src/Components/Form/FormInput.php
index ed6d911f..935c5b0f 100644
--- a/src/Components/Form/FormInput.php
+++ b/src/Components/Form/FormInput.php
@@ -4,22 +4,37 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormInput extends LazyComponent
{
+ use ResolvesFormFieldState;
+
public ?string $placeholder;
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
- {
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public string $help = '',
+ public bool $hr = false,
+ public string $outerClass = '',
+ public string $src = '',
+ ) {
$this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
}
public function render(): \Closure|View
{
return function (array $data) {
- $data['attributes']['required'] = $this->required;
+ $attributes = $this->getAttributesFromData($data);
+ $attributes['required'] = $this->required;
+ $data['attributes'] = $attributes;
- return view('lazy::form.input', $this->mergeData($data))->render();
+ return view('lazy::form.input', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormRichtext.php b/src/Components/Form/FormRichtext.php
index 2321d0b4..6111199d 100644
--- a/src/Components/Form/FormRichtext.php
+++ b/src/Components/Form/FormRichtext.php
@@ -4,22 +4,37 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormRichtext extends LazyComponent
{
+ use ResolvesFormFieldState;
+
public ?string $placeholder;
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
- {
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public string $help = '',
+ public bool $hr = false,
+ public string $outerClass = '',
+ public string $src = '',
+ ) {
$this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
}
public function render(): \Closure|View
{
return function (array $data) {
- $data['attributes']['required'] = $this->required;
+ $attributes = $this->getAttributesFromData($data);
+ $attributes['required'] = $this->required;
+ $data['attributes'] = $attributes;
- return view('lazy::form.richtext', $this->mergeData($data))->render();
+ return view('lazy::form.richtext', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormSelect.php b/src/Components/Form/FormSelect.php
index f5ba9ddf..fc4c0c75 100644
--- a/src/Components/Form/FormSelect.php
+++ b/src/Components/Form/FormSelect.php
@@ -4,22 +4,37 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormSelect extends LazyComponent
{
+ use ResolvesFormFieldState;
+
public ?string $placeholder;
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
- {
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public string $help = '',
+ public bool $hr = false,
+ public string $outerClass = '',
+ public string $src = '',
+ ) {
$this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
}
public function render(): \Closure|View
{
return function (array $data) {
- $data['attributes']['required'] = $this->required;
+ $attributes = $this->getAttributesFromData($data);
+ $attributes['required'] = $this->required;
+ $data['attributes'] = $attributes;
- return view('lazy::form.select', $this->mergeData($data))->render();
+ return view('lazy::form.select', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormTextarea.php b/src/Components/Form/FormTextarea.php
index 7f1e4533..4efdd10c 100644
--- a/src/Components/Form/FormTextarea.php
+++ b/src/Components/Form/FormTextarea.php
@@ -4,22 +4,37 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormTextarea extends LazyComponent
{
+ use ResolvesFormFieldState;
+
public ?string $placeholder;
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
- {
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public string $help = '',
+ public bool $hr = false,
+ public string $outerClass = '',
+ public string $src = '',
+ ) {
$this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
}
public function render(): \Closure|View
{
return function (array $data) {
- $data['attributes']['required'] = $this->required;
+ $attributes = $this->getAttributesFromData($data);
+ $attributes['required'] = $this->required;
+ $data['attributes'] = $attributes;
- return view('lazy::form.textarea', $this->mergeData($data))->render();
+ return view('lazy::form.textarea', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/Form/FormToggle.php b/src/Components/Form/FormToggle.php
index 4831b470..ffeb3264 100644
--- a/src/Components/Form/FormToggle.php
+++ b/src/Components/Form/FormToggle.php
@@ -4,15 +4,27 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormToggle extends LazyComponent
{
- public function __construct(public string $label = '') {}
+ use ResolvesFormFieldState;
+
+ public function __construct(
+ public string $label = '',
+ public string $help = '',
+ public bool $hr = false,
+ ) {}
public function render(): \Closure|View
{
return function (array $data) {
- return view('lazy::form.toggle', $this->mergeData($data))->render();
+ $attributes = $this->getAttributesFromData($data);
+
+ return view('lazy::form.toggle', [
+ ...$this->mergeData($data),
+ ...$this->resolveFieldState($data, $attributes),
+ ])->render();
};
}
}
diff --git a/src/Components/FormGroup.php b/src/Components/FormGroup.php
index 68a0f232..76889a5f 100644
--- a/src/Components/FormGroup.php
+++ b/src/Components/FormGroup.php
@@ -4,18 +4,30 @@
use Illuminate\Contracts\View\View;
use Step2dev\LazyUI\LazyComponent;
+use Step2dev\LazyUI\Traits\ResolvesFormFieldState;
class FormGroup extends LazyComponent
{
- public function __construct(string $label = '')
- {
+ use ResolvesFormFieldState;
+
+ public function __construct(
+ string $label = '',
+ public string $help = '',
+ public bool $hr = false,
+ ) {
$this->label = $label;
}
public function render(): \Closure|View
{
return function (array $data) {
- return view('lazy::form-group', $this->mergeData($data))->render();
+ $attributes = $this->getAttributesFromData($data);
+ $state = $this->resolveFieldState($data, $attributes);
+
+ return view('lazy::form-group', [
+ ...$this->mergeData($data),
+ ...$state,
+ ])->render();
};
}
}
diff --git a/src/Components/Hero.php b/src/Components/Hero.php
index 9a4bcbe6..fce2b036 100644
--- a/src/Components/Hero.php
+++ b/src/Components/Hero.php
@@ -2,24 +2,62 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Hero extends LazyComponent
+class Hero extends DaisyComponent
{
- public $title;
+ protected const VIEW = 'lazy::hero';
- public $description;
+ public array $contentClasses;
- public function __construct($title, $description)
+ public array $innerClasses;
+
+ public array $titleClasses;
+
+ public array $spacingClasses;
+
+ public function __construct(
+ public string $title = '',
+ public string $description = '',
+ public string $background = 'base-200',
+ public string $align = 'center',
+ public string $width = 'md',
+ public string $titleSize = 'lg',
+ public string $spacing = 'md',
+ ) {
+ $aligns = ['start' => 'text-left', 'center' => 'text-center', 'end' => 'text-right'];
+ $widths = ['none' => '', 'sm' => 'max-w-sm', 'md' => 'max-w-md', 'lg' => 'max-w-lg', 'xl' => 'max-w-xl', 'full' => 'max-w-none'];
+ $sizes = ['xs' => 'text-2xl', 'sm' => 'text-3xl', 'md' => 'text-4xl', 'lg' => 'text-5xl', 'xl' => 'text-6xl'];
+ $spacings = ['none' => '', 'xs' => 'py-2', 'sm' => 'py-4', 'md' => 'py-6', 'lg' => 'py-8'];
+
+ $this->contentClasses = ['hero-content', $aligns[$this->align] ?? $aligns['center']];
+ $this->innerClasses = [$widths[$this->width] ?? $widths['md']];
+ $this->titleClasses = ['font-bold', $sizes[$this->titleSize] ?? $sizes['lg']];
+ $this->spacingClasses = [$spacings[$this->spacing] ?? $spacings['md']];
+ }
+
+ protected function viewClasses(): array
{
- $this->title = $title;
- $this->description = $description;
+ return [
+ 'content' => $this->contentClasses,
+ 'inner' => $this->innerClasses,
+ 'title' => $this->titleClasses,
+ 'spacing' => $this->spacingClasses,
+ ];
}
- public function render(): View|Closure
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return view('components.hero');
+ $backgrounds = [
+ 'none' => '',
+ 'base-100' => 'bg-base-100',
+ 'base-200' => 'bg-base-200',
+ 'base-300' => 'bg-base-300',
+ 'neutral' => 'bg-neutral text-neutral-content',
+ 'primary' => 'bg-primary text-primary-content',
+ 'secondary' => 'bg-secondary text-secondary-content',
+ ];
+
+ return ['hero', $backgrounds[$this->background] ?? $backgrounds['base-200']];
}
}
diff --git a/src/Components/Hover3d.php b/src/Components/Hover3d.php
new file mode 100644
index 00000000..513a5cf1
--- /dev/null
+++ b/src/Components/Hover3d.php
@@ -0,0 +1,22 @@
+zones = range(1, max(1, min(16, $this->zoneCount)));
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['hover-3d'];
+ }
+}
diff --git a/src/Components/HoverGallery.php b/src/Components/HoverGallery.php
new file mode 100644
index 00000000..79b01119
--- /dev/null
+++ b/src/Components/HoverGallery.php
@@ -0,0 +1,30 @@
+ $image];
+
+ $this->images[] = [
+ 'src' => (string) ($normalized['src'] ?? ''),
+ 'alt' => (string) ($normalized['alt'] ?? ''),
+ 'classes' => (string) ($normalized['class'] ?? ''),
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['hover-gallery'];
+ }
+}
diff --git a/src/Components/Indicator.php b/src/Components/Indicator.php
index d071aa12..bdfb4c4c 100644
--- a/src/Components/Indicator.php
+++ b/src/Components/Indicator.php
@@ -2,24 +2,98 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Indicator extends LazyComponent
+class Indicator extends DaisyComponent
{
- public $indicator;
+ protected const VIEW = 'lazy::indicator';
- public $indicatorClass;
+ public function __construct(
+ public mixed $indicator = null,
+ public ?string $indicatorClass = null,
+ public string $color = 'secondary',
+ public ?string $size = null,
+ public ?string $horizontal = null,
+ public ?string $vertical = null,
+ ) {}
- public function __construct($indicator = null, $indicatorClass = 'badge badge-secondary')
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- $this->indicator = $indicator;
- $this->indicatorClass = $indicatorClass;
+ return ['indicator'];
}
- public function render(): View|Closure
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
{
- return view('components.indicator');
+ $colors = [
+ 'neutral' => 'badge-neutral',
+ 'primary' => 'badge-primary',
+ 'secondary' => 'badge-secondary',
+ 'accent' => 'badge-accent',
+ 'info' => 'badge-info',
+ 'success' => 'badge-success',
+ 'warning' => 'badge-warning',
+ 'error' => 'badge-error',
+ ];
+ $sizes = [
+ 'xs' => 'badge-xs',
+ 'sm' => 'badge-sm',
+ 'md' => 'badge-md',
+ 'lg' => 'badge-lg',
+ 'xl' => 'badge-xl',
+ ];
+ $horizontalClasses = [
+ 'start' => 'indicator-start',
+ 'center' => 'indicator-center',
+ 'end' => 'indicator-end',
+ ];
+ $verticalClasses = [
+ 'top' => 'indicator-top',
+ 'middle' => 'indicator-middle',
+ 'bottom' => 'indicator-bottom',
+ ];
+
+ $color = $this->color;
+ $size = $this->size;
+
+ foreach (array_keys($colors) as $candidate) {
+ if ($attributes->has($candidate)) {
+ $color = $candidate;
+ break;
+ }
+ }
+
+ foreach (array_keys($sizes) as $candidate) {
+ if ($attributes->has($candidate)) {
+ $size = $candidate;
+ break;
+ }
+ }
+
+ $markerClasses = [
+ 'indicator-item',
+ 'badge' => ! $this->indicatorClass,
+ $colors[$color] ?? $colors['secondary'] => ! $this->indicatorClass,
+ $sizes[$size] ?? '' => ! $this->indicatorClass && $size,
+ $horizontalClasses[$this->horizontal] ?? '' => (bool) $this->horizontal,
+ $verticalClasses[$this->vertical] ?? '' => (bool) $this->vertical,
+ $this->indicatorClass => filled($this->indicatorClass),
+ ];
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $markerClasses = [];
+ }
+
+ return [
+ 'markerClasses' => $markerClasses,
+ 'markerClass' => $this->classes($markerClasses),
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return [
+ 'neutral', 'primary', 'secondary', 'accent', 'info', 'success', 'warning', 'error',
+ 'xs', 'sm', 'md', 'lg', 'xl',
+ ];
}
}
diff --git a/src/Components/Input.php b/src/Components/Input.php
index 0e168a93..e50d8376 100644
--- a/src/Components/Input.php
+++ b/src/Components/Input.php
@@ -2,13 +2,26 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Input extends LazyComponent
+class Input extends DaisyComponent
{
+ protected const VIEW = 'lazy::input';
+
public ?string $placeholder;
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public bool $validator = false,
+ public string $hint = '',
+ public string $color = '',
+ public string $size = '',
+ ) {
+ $this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
+ }
+
protected function allowedColors(): array
{
return [
@@ -18,36 +31,42 @@ protected function allowedColors(): array
];
}
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- $this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
+ $attributes['required'] = $this->required;
+
+ return $attributes;
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+
+ return [
+ 'controlClass' => $unstyled ? '' : 'w-full'.(($data['hasError'] ?? false) ? ' text-error input-error' : ''),
+ 'hintClass' => $unstyled ? '' : 'validator-hint',
+ ];
}
- public function render(): \Closure|View
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['required'] = $this->required;
- $data['attributes'] = $attributes;
-
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
-
- return view('lazy::input', $this->mergeData($data, [
- 'input',
- 'input-ghost' => $color === 'ghost' || $color === 'no-border',
- 'input-primary' => $color === 'primary',
- 'input-secondary' => $color === 'secondary',
- 'input-accent' => $color === 'accent',
- 'input-info' => $color === 'info',
- 'input-success' => $color === 'success',
- 'input-warning' => $color === 'warning',
- 'input-error' => $color === 'error',
- 'input-lg' => $size === 'lg',
- 'input-md' => $size === 'md',
- 'input-sm' => $size === 'sm',
- 'input-xs' => $size === 'xs',
- ]))->render();
- };
+ return [
+ 'input',
+ 'validator' => $this->validator,
+ 'input-ghost' => in_array($this->color, ['ghost', 'no-border'], true),
+ 'input-neutral' => $this->color === 'neutral',
+ 'input-primary' => $this->color === 'primary',
+ 'input-secondary' => $this->color === 'secondary',
+ 'input-accent' => $this->color === 'accent',
+ 'input-info' => $this->color === 'info',
+ 'input-success' => $this->color === 'success',
+ 'input-warning' => $this->color === 'warning',
+ 'input-error' => $this->color === 'error',
+ 'input-xl' => $this->size === 'xl',
+ 'input-lg' => $this->size === 'lg',
+ 'input-md' => $this->size === 'md',
+ 'input-sm' => $this->size === 'sm',
+ 'input-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/InputGroup.php b/src/Components/InputGroup.php
new file mode 100644
index 00000000..58dbe08c
--- /dev/null
+++ b/src/Components/InputGroup.php
@@ -0,0 +1,5 @@
+getAttributesFromData($data);
+ $position = $this->position;
- $position = $this->getPositionByAttribute($attributes);
+ if ($attributes->has('vertical')) {
+ $position = 'vertical';
+ } elseif ($attributes->has('horizontal')) {
+ $position = 'horizontal';
+ }
- return view('lazy::join', $this->mergeData($data, [
- 'join',
- 'join-vertical' => $position === 'vertical',
- 'join-horizontal' => $position === 'horizontal',
- ]))->render();
- };
+ return [
+ 'join',
+ 'join-vertical' => $position === 'vertical',
+ 'join-horizontal' => $position === 'horizontal',
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return ['vertical', 'horizontal'];
}
}
diff --git a/src/Components/Kbd.php b/src/Components/Kbd.php
index bbe60cc3..6c8461ad 100644
--- a/src/Components/Kbd.php
+++ b/src/Components/Kbd.php
@@ -2,24 +2,26 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Kbd extends LazyComponent
+class Kbd extends DaisyComponent
{
- public function render(): \Closure|View
- {
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $size = $this->getSizeByAttribute($attributes, '');
+ protected const VIEW = 'lazy::kbd';
+
+ public function __construct(
+ public string $value = '',
+ public string $size = '',
+ ) {}
- return view('lazy::kbd', $this->mergeData($data, [
- 'kbd',
- 'kbd-lg' => $size === 'lg',
- 'kbd-md' => $size === 'md',
- 'kbd-sm' => $size === 'sm',
- 'kbd-xs' => $size === 'xs',
- ]))->render();
- };
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'kbd',
+ 'kbd-xl' => $this->size === 'xl',
+ 'kbd-lg' => $this->size === 'lg',
+ 'kbd-md' => $this->size === 'md',
+ 'kbd-sm' => $this->size === 'sm',
+ 'kbd-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/ListRow.php b/src/Components/ListRow.php
new file mode 100644
index 00000000..adb2fbd8
--- /dev/null
+++ b/src/Components/ListRow.php
@@ -0,0 +1,15 @@
+getKeyByAttribute($attribute, $this->allowedTypes(), 'type', $default);
- }
+ protected const VIEW = 'lazy::loading';
- /**
- * Get the view / contents that represent the component.
- */
- public function render(): Closure|View
+ public function __construct(
+ public string $type = 'spinner',
+ public string $size = 'md',
+ public string $color = '',
+ ) {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $size = $this->getSizeByAttribute($attributes, 'md');
- $type = $this->getTypeByAttribute($attributes, 'spinner');
- $color = $this->getColorByAttribute($attributes, '');
+ $type = $this->type;
- return view('lazy::loading', $this->mergeData($data, [
- 'loading',
- 'loading-xs' => $size === 'xs',
- 'loading-sm' => $size === 'sm',
- 'loading-md' => $size === 'md',
- 'loading-lg' => $size === 'lg',
- 'loading-spinner' => $type === 'spinner',
- 'loading-dots' => $type === 'dots',
- 'loading-ring' => $type === 'ring',
- 'loading-ball' => $type === 'ball',
- 'loading-bars' => $type === 'bars',
- 'loading-infinity' => $type === 'infinity',
- 'text-primary' => $color === 'primary',
- 'text-secondary' => $color === 'secondary',
- 'text-accent' => $color === 'accent',
- 'text-neutral' => $color === 'neutral',
- 'text-info' => $color === 'info',
- 'text-success' => $color === 'success',
- 'text-warning' => $color === 'warning',
- 'text-error' => $color === 'error',
- ], [
- 'type',
- 'size',
- 'color',
- ]))->render();
- };
- }
+ foreach (['spinner', 'dots', 'ring', 'ball', 'bars', 'infinity'] as $candidate) {
+ if ($attributes->has($candidate) && $this->truthy($attributes->get($candidate))) {
+ $type = $candidate;
+ break;
+ }
+ }
- private function allowedTypes(): array
- {
return [
- 'spinner',
- 'dots',
- 'ring',
- 'ball',
- 'bars',
- 'infinity',
+ 'loading',
+ 'loading-xl' => $this->size === 'xl',
+ 'loading-xs' => $this->size === 'xs',
+ 'loading-sm' => $this->size === 'sm',
+ 'loading-md' => $this->size === 'md',
+ 'loading-lg' => $this->size === 'lg',
+ 'loading-spinner' => $type === 'spinner',
+ 'loading-dots' => $type === 'dots',
+ 'loading-ring' => $type === 'ring',
+ 'loading-ball' => $type === 'ball',
+ 'loading-bars' => $type === 'bars',
+ 'loading-infinity' => $type === 'infinity',
+ 'text-primary' => $this->color === 'primary',
+ 'text-secondary' => $this->color === 'secondary',
+ 'text-accent' => $this->color === 'accent',
+ 'text-neutral' => $this->color === 'neutral',
+ 'text-info' => $this->color === 'info',
+ 'text-success' => $this->color === 'success',
+ 'text-warning' => $this->color === 'warning',
+ 'text-error' => $this->color === 'error',
];
}
+
+ protected function consumedAttributes(): array
+ {
+ return ['spinner', 'dots', 'ring', 'ball', 'bars', 'infinity'];
+ }
}
diff --git a/src/Components/Mask.php b/src/Components/Mask.php
new file mode 100644
index 00000000..2b2e2002
--- /dev/null
+++ b/src/Components/Mask.php
@@ -0,0 +1,38 @@
+ $this->shape === 'squircle',
+ 'mask-heart' => $this->shape === 'heart',
+ 'mask-hexagon' => $this->shape === 'hexagon',
+ 'mask-hexagon-2' => $this->shape === 'hexagon-2',
+ 'mask-decagon' => $this->shape === 'decagon',
+ 'mask-pentagon' => $this->shape === 'pentagon',
+ 'mask-diamond' => $this->shape === 'diamond',
+ 'mask-circle' => $this->shape === 'circle',
+ 'mask-star' => $this->shape === 'star',
+ 'mask-star-2' => $this->shape === 'star-2',
+ 'mask-triangle' => $this->shape === 'triangle',
+ 'mask-triangle-2' => $this->shape === 'triangle-2',
+ 'mask-triangle-3' => $this->shape === 'triangle-3',
+ 'mask-triangle-4' => $this->shape === 'triangle-4',
+ 'mask-square' => $this->shape === 'square',
+ 'mask-parallelogram' => $this->shape === 'parallelogram',
+ 'mask-parallelogram-2' => $this->shape === 'parallelogram-2',
+ 'mask-parallelogram-3' => $this->shape === 'parallelogram-3',
+ 'mask-parallelogram-4' => $this->shape === 'parallelogram-4',
+ ];
+ }
+}
diff --git a/src/Components/Megamenu.php b/src/Components/Megamenu.php
new file mode 100644
index 00000000..b5197058
--- /dev/null
+++ b/src/Components/Megamenu.php
@@ -0,0 +1,37 @@
+ 'megamenu-active'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'megamenu',
+ 'megamenu-wide' => $this->wide,
+ 'megamenu-full' => $this->full,
+ 'megamenu-vertical' => $this->vertical,
+ 'megamenu-xs' => $this->size === 'xs',
+ 'megamenu-sm' => $this->size === 'sm',
+ 'megamenu-md' => $this->size === 'md',
+ 'megamenu-lg' => $this->size === 'lg',
+ 'megamenu-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/Menu.php b/src/Components/Menu.php
index 58966a98..bc656c07 100644
--- a/src/Components/Menu.php
+++ b/src/Components/Menu.php
@@ -8,17 +8,53 @@
class Menu extends LazyComponent
{
- public function __construct(public bool $join = true) {}
+ public string|int $countLabel = 0;
+
+ public string $resolvedHref = '#';
+
+ public bool $isActive = false;
+
+ public function __construct(
+ public string $route = '',
+ public string $href = '#',
+ public string $icon = '',
+ public string $label = '',
+ public string $title = '',
+ public bool $show = true,
+ public string|int $count = 0,
+ public string $inlineIcon = '',
+ public mixed $indicator = '',
+ public bool $toggle = false,
+ public bool $active = false,
+ public bool $disabled = false,
+ public bool $focus = false,
+ public bool $join = true,
+ ) {
+ if (! $this->show) {
+ return;
+ }
+
+ $routePath = $this->route ? route($this->route, [], false) : $this->href;
+ $path = trim($routePath !== '/admin' ? $routePath.'*' : $routePath, '/');
+
+ $this->resolvedHref = $this->route ? route($this->route) : $this->href;
+ $this->countLabel = (int) $this->count > 99 ? '99+' : $this->count;
+ $this->isActive = $this->active || request()->is($path);
+ }
- /**
- * Get the view / contents that represent the component.
- *
- * @return View
- */
public function render(): View|Closure
{
return function (array $data) {
- return view('lazy::menu', $this->mergeData($data))->render();
+ if (! $this->show) {
+ return '';
+ }
+
+ return view('lazy::menu', $this->mergeData($data, [
+ 'menu-active' => $this->isActive,
+ 'menu-disabled' => $this->disabled,
+ 'menu-focus' => $this->focus,
+ 'menu-dropdown-toggle' => $this->toggle,
+ ]))->render();
};
}
}
diff --git a/src/Components/MenuList.php b/src/Components/MenuList.php
new file mode 100644
index 00000000..c321bfb3
--- /dev/null
+++ b/src/Components/MenuList.php
@@ -0,0 +1,31 @@
+ ! $this->horizontal,
+ 'menu-horizontal' => $this->horizontal,
+ 'menu-paged' => $this->paged,
+ 'menu-xs' => $this->size === 'xs',
+ 'menu-sm' => $this->size === 'sm',
+ 'menu-md' => $this->size === 'md',
+ 'menu-lg' => $this->size === 'lg',
+ 'menu-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/Mockup/MockupBrowser.php b/src/Components/Mockup/MockupBrowser.php
index 9d34715d..bd0eefdb 100644
--- a/src/Components/Mockup/MockupBrowser.php
+++ b/src/Components/Mockup/MockupBrowser.php
@@ -2,15 +2,26 @@
namespace Step2dev\LazyUI\Components\Mockup;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
+use Step2dev\LazyUI\Components\DaisyComponent;
-class MockupBrowser extends LazyComponent
+class MockupBrowser extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::mockup.browser';
+
+ public function __construct(public string $url = '') {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['mockup-browser border border-base-300'];
+ }
+
+ protected function viewClasses(): array
{
- return function (array $data) {
- return view('lazy::mockup.browser', $this->mergeData($data))->render();
- };
+ return [
+ 'toolbar' => 'mockup-browser-toolbar',
+ 'url' => 'input border border-base-300',
+ 'content' => 'border-t border-base-300',
+ ];
}
}
diff --git a/src/Components/Mockup/MockupCode.php b/src/Components/Mockup/MockupCode.php
index 943fdd5f..9d76f9a8 100644
--- a/src/Components/Mockup/MockupCode.php
+++ b/src/Components/Mockup/MockupCode.php
@@ -2,15 +2,64 @@
namespace Step2dev\LazyUI\Components\Mockup;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
+use Step2dev\LazyUI\Components\DaisyComponent;
-class MockupCode extends LazyComponent
+class MockupCode extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::mockup.code';
+
+ public array $lines = [];
+
+ public function __construct(
+ array $lines = [],
+ public string $prefix = '$',
+ public bool $scroll = true,
+ ) {
+ foreach (array_values($lines) as $index => $line) {
+ $normalized = is_array($line) ? $line : ['code' => $line];
+
+ $state = (string) ($normalized['state'] ?? '');
+ $color = (string) ($normalized['color'] ?? '');
+
+ $this->lines[] = [
+ 'code' => (string) ($normalized['code'] ?? $normalized['text'] ?? ''),
+ 'prefix' => array_key_exists('prefix', $normalized)
+ ? (string) $normalized['prefix']
+ : $this->prefix,
+ 'classes' => $this->classes([
+ (string) ($normalized['class'] ?? '') => isset($normalized['class']),
+ 'bg-warning text-warning-content' => $state === 'warning' || $color === 'warning',
+ 'bg-error text-error-content' => $state === 'error' || $color === 'error',
+ 'bg-success text-success-content' => $state === 'success' || $color === 'success',
+ 'bg-info text-info-content' => $state === 'info' || $color === 'info',
+ 'bg-primary text-primary-content' => $state === 'primary' || $color === 'primary',
+ 'bg-secondary text-secondary-content' => $state === 'secondary' || $color === 'secondary',
+ 'bg-accent text-accent-content' => $state === 'accent' || $color === 'accent',
+ ]),
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- return view('lazy::mockup.code', $this->mergeData($data))->render();
- };
+ return [
+ 'mockup-code',
+ 'overflow-x-auto' => $this->scroll,
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $lines = $this->lines;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $lines = array_map(static fn (array $line): array => [
+ ...$line,
+ 'classes' => '',
+ ], $lines);
+ }
+
+ return ['lines' => $lines];
}
}
diff --git a/src/Components/Mockup/MockupWindow.php b/src/Components/Mockup/MockupWindow.php
index 9360c945..7b397ca8 100644
--- a/src/Components/Mockup/MockupWindow.php
+++ b/src/Components/Mockup/MockupWindow.php
@@ -2,15 +2,20 @@
namespace Step2dev\LazyUI\Components\Mockup;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
+use Step2dev\LazyUI\Components\DaisyComponent;
-class MockupWindow extends LazyComponent
+class MockupWindow extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::mockup.window';
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['mockup-window border border-base-300'];
+ }
+
+ protected function viewClasses(): array
{
- return function (array $data) {
- return view('lazy::mockup.window', $this->mergeData($data))->render();
- };
+ return ['content' => 'border-t border-base-300'];
}
}
diff --git a/src/Components/Modal.php b/src/Components/Modal.php
new file mode 100644
index 00000000..e6797fa1
--- /dev/null
+++ b/src/Components/Modal.php
@@ -0,0 +1,43 @@
+ 'modal-box',
+ 'title' => 'text-lg font-bold',
+ 'actions' => 'modal-action',
+ 'backdrop' => 'modal-backdrop',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'modal',
+ 'modal-open' => $this->open,
+ 'modal-top' => $this->top,
+ 'modal-middle' => $this->middle,
+ 'modal-bottom' => $this->bottom,
+ 'modal-start' => $this->start,
+ 'modal-end' => $this->end,
+ ];
+ }
+}
diff --git a/src/Components/Navbar.php b/src/Components/Navbar.php
new file mode 100644
index 00000000..7e279fd6
--- /dev/null
+++ b/src/Components/Navbar.php
@@ -0,0 +1,24 @@
+ 'navbar-start',
+ 'center' => 'navbar-center',
+ 'end' => 'navbar-end',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['navbar'];
+ }
+}
diff --git a/src/Components/Otp.php b/src/Components/Otp.php
new file mode 100644
index 00000000..75192b6f
--- /dev/null
+++ b/src/Components/Otp.php
@@ -0,0 +1,82 @@
+length));
+ $this->length = $count;
+ $this->cells = array_fill(0, $count, true);
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'otp',
+ 'otp-joined' => $this->joined,
+ 'otp-neutral' => $this->color === 'neutral',
+ 'otp-primary' => $this->color === 'primary',
+ 'otp-secondary' => $this->color === 'secondary',
+ 'otp-accent' => $this->color === 'accent',
+ 'otp-success' => $this->color === 'success',
+ 'otp-info' => $this->color === 'info',
+ 'otp-warning' => $this->color === 'warning',
+ 'otp-error' => $this->color === 'error',
+ 'otp-xs' => $this->size === 'xs',
+ 'otp-sm' => $this->size === 'sm',
+ 'otp-md' => $this->size === 'md',
+ 'otp-lg' => $this->size === 'lg',
+ 'otp-xl' => $this->size === 'xl',
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $inputAttributes = $attributes->only([
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'disabled',
+ 'required',
+ 'form',
+ ]);
+
+ return [
+ 'inputAttributes' => $inputAttributes,
+ 'inputMode' => $this->numeric ? 'numeric' : 'text',
+ 'pattern' => $this->numeric ? '[0-9]*' : null,
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return [
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'disabled',
+ 'required',
+ 'form',
+ ];
+ }
+}
diff --git a/src/Components/Pagination.php b/src/Components/Pagination.php
new file mode 100644
index 00000000..5f21f61d
--- /dev/null
+++ b/src/Components/Pagination.php
@@ -0,0 +1,118 @@
+total);
+ $current = max(1, min(max(1, $last), $this->current));
+ $window = max(0, min(10, $this->window));
+
+ if ($last === 0) {
+ return;
+ }
+
+ if ($this->showEdges) {
+ $this->pages[] = $this->pageItem(
+ label: $this->previousLabel,
+ page: max(1, $current - 1),
+ active: false,
+ disabled: $current === 1,
+ );
+ }
+
+ $visible = [];
+ for ($page = 1; $page <= $last; $page++) {
+ if ($page === 1 || $page === $last || abs($page - $current) <= $window) {
+ $visible[] = $page;
+ }
+ }
+
+ $previous = null;
+ foreach ($visible as $page) {
+ if ($previous !== null && $page - $previous > 1) {
+ $this->pages[] = [
+ 'label' => '…',
+ 'page' => null,
+ 'active' => false,
+ 'disabled' => true,
+ 'href' => null,
+ 'classes' => 'join-item btn btn-disabled',
+ ];
+ }
+
+ $this->pages[] = $this->pageItem(
+ label: (string) $page,
+ page: $page,
+ active: $page === $current,
+ );
+ $previous = $page;
+ }
+
+ if ($this->showEdges) {
+ $this->pages[] = $this->pageItem(
+ label: $this->nextLabel,
+ page: min($last, $current + 1),
+ active: false,
+ disabled: $current === $last,
+ );
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $pages = $this->pages;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $pages = array_map(static fn (array $page): array => [
+ ...$page,
+ 'classes' => '',
+ ], $pages);
+ }
+
+ return ['pages' => $pages];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['join'];
+ }
+
+ private function pageItem(
+ string $label,
+ int $page,
+ bool $active = false,
+ bool $disabled = false,
+ ): array {
+ return [
+ 'label' => $label,
+ 'page' => $page,
+ 'active' => $active,
+ 'disabled' => $disabled,
+ 'href' => ! $disabled && $this->url
+ ? str_replace('{page}', (string) $page, $this->url)
+ : null,
+ 'classes' => $this->classes([
+ 'join-item',
+ 'btn',
+ 'btn-active' => $active,
+ 'btn-disabled' => $disabled,
+ ]),
+ ];
+ }
+}
diff --git a/src/Components/PaginationItem.php b/src/Components/PaginationItem.php
new file mode 100644
index 00000000..fb33f483
--- /dev/null
+++ b/src/Components/PaginationItem.php
@@ -0,0 +1,29 @@
+ $this->active,
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['tag' => $this->href && ! $this->disabled ? 'a' : 'button'];
+ }
+}
diff --git a/src/Components/Progress.php b/src/Components/Progress.php
new file mode 100644
index 00000000..3b0c6d8a
--- /dev/null
+++ b/src/Components/Progress.php
@@ -0,0 +1,42 @@
+ $this->color === 'primary',
+ 'progress-secondary' => $this->color === 'secondary',
+ 'progress-accent' => $this->color === 'accent',
+ 'progress-neutral' => $this->color === 'neutral',
+ 'progress-info' => $this->color === 'info',
+ 'progress-success' => $this->color === 'success',
+ 'progress-warning' => $this->color === 'warning',
+ 'progress-error' => $this->color === 'error',
+ ];
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
+ {
+ $attributes['max'] = $attributes->get('max', $this->max);
+
+ if ($this->value !== null) {
+ $attributes['value'] = $attributes->get('value', $this->value);
+ }
+
+ return $attributes;
+ }
+}
diff --git a/src/Components/Radial.php b/src/Components/Radial.php
index 5933b889..7b511cc6 100644
--- a/src/Components/Radial.php
+++ b/src/Components/Radial.php
@@ -2,23 +2,57 @@
namespace Step2dev\LazyUI\Components;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Radial extends LazyComponent
+class Radial extends DaisyComponent
{
- public function render(): \Closure
+ protected const VIEW = 'lazy::radial';
+
+ public string $resolvedStyle;
+
+ public function __construct(
+ public int|float $value = 0,
+ public string $size = '',
+ public string $thickness = '',
+ public string $color = '',
+ public string $label = '',
+ ) {
+ $this->value = max(0, min(100, (float) $this->value));
+
+ $parts = ['--value: '.$this->value];
+
+ if ($this->size !== '') {
+ $parts[] = '--size: '.$this->size;
+ }
+
+ if ($this->thickness !== '') {
+ $parts[] = '--thickness: '.$this->thickness;
+ }
+
+ $this->resolvedStyle = implode('; ', $parts).';';
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'radial-progress',
+ 'text-primary' => $this->color === 'primary',
+ 'text-secondary' => $this->color === 'secondary',
+ 'text-accent' => $this->color === 'accent',
+ 'text-neutral' => $this->color === 'neutral',
+ 'text-info' => $this->color === 'info',
+ 'text-success' => $this->color === 'success',
+ 'text-warning' => $this->color === 'warning',
+ 'text-error' => $this->color === 'error',
+ ];
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- return function (array $data) {
- $data['attributes']['value'] ??= 0;
- $data['attributes']['style'] ??= '';
-
- $data['attributes']['style'] .= implode(';', [
- isset($data['attributes']['size']) ? "--size: {$data['attributes']['size']};" : '',
- isset($data['attributes']['thickness']) ? "--thickness: {$data['attributes']['thickness']};" : '',
- "--value: {$data['attributes']['value']};",
- ]);
-
- return view('lazy::radial', $this->mergeData($data))->render();
- };
+ $existing = trim((string) $attributes->get('style', ''));
+
+ $attributes['style'] = trim($existing.' '.$this->resolvedStyle);
+
+ return $attributes;
}
}
diff --git a/src/Components/Radio.php b/src/Components/Radio.php
index 90fdea1f..229cde7e 100644
--- a/src/Components/Radio.php
+++ b/src/Components/Radio.php
@@ -2,39 +2,41 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Radio extends LazyComponent
+class Radio extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::radio';
+
+ public function __construct(
+ public string $color = '',
+ public string $size = '',
+ ) {}
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['type'] = 'radio';
- $data['attributes'] = $attributes;
+ $attributes['type'] = 'radio';
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
+ return $attributes;
+ }
- return view('lazy::radio', $this->mergeData($data, [
- 'radio',
- // colors
- 'radio-primary' => $color === 'primary',
- 'radio-secondary' => $color === 'secondary',
- 'radio-accent' => $color === 'accent',
- 'radio-info' => $color === 'info',
- 'radio-success' => $color === 'success',
- 'radio-warning' => $color === 'warning',
- 'radio-error' => $color === 'error',
- // sizes
- 'radio-lg' => $size === 'lg',
- 'radio-md' => $size === 'md',
- 'radio-sm' => $size === 'sm',
- 'radio-xs' => $size === 'xs',
- ], [
- 'color',
- ]))->render();
- };
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'radio',
+ 'radio-neutral' => $this->color === 'neutral',
+ 'radio-primary' => $this->color === 'primary',
+ 'radio-secondary' => $this->color === 'secondary',
+ 'radio-accent' => $this->color === 'accent',
+ 'radio-info' => $this->color === 'info',
+ 'radio-success' => $this->color === 'success',
+ 'radio-warning' => $this->color === 'warning',
+ 'radio-error' => $this->color === 'error',
+ 'radio-xl' => $this->size === 'xl',
+ 'radio-lg' => $this->size === 'lg',
+ 'radio-md' => $this->size === 'md',
+ 'radio-sm' => $this->size === 'sm',
+ 'radio-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/Range.php b/src/Components/Range.php
index d42a139a..dd8f84c7 100644
--- a/src/Components/Range.php
+++ b/src/Components/Range.php
@@ -2,47 +2,65 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Range extends LazyComponent
+class Range extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::range';
+
+ public array $marks = [];
+
+ public function __construct(
+ public int|float $min = 0,
+ public int|float $max = 100,
+ public int|float|null $value = null,
+ public int|float|null $step = null,
+ public ?int $steps = null,
+ public string $color = '',
+ public string $size = '',
+ public bool $vertical = false,
+ ) {
+ if ($this->steps !== null) {
+ $this->steps = max(2, min(100, $this->steps));
+ $this->step ??= ($this->max - $this->min) / ($this->steps - 1);
+ $this->marks = array_fill(0, $this->steps, true);
+ }
+
+ $this->value ??= $this->min;
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['type'] = 'range';
- $attributes['min'] ??= 0;
- $attributes['max'] ??= 100;
- $attributes['value'] ??= $attributes['min'];
- $attributes['step'] ??= null;
-
- $attributes['steps'] ??= null;
-
- if ($attributes['steps']) {
- $attributes['step'] = ($attributes['max'] - $attributes['min']) / ($attributes['steps'] - 1);
- }
- $data['attributes'] = $attributes;
-
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
-
- return view('lazy::range', $this->mergeData($data, [
- 'range',
- // colors
- 'range-primary' => $color === 'primary',
- 'range-secondary' => $color === 'secondary',
- 'range-accent' => $color === 'accent',
- 'range-info' => $color === 'info',
- 'range-success' => $color === 'success',
- 'range-warning' => $color === 'warning',
- 'range-error' => $color === 'error',
- // sizes
- 'range-lg' => $size === 'lg',
- 'range-md' => $size === 'md',
- 'range-sm' => $size === 'sm',
- 'range-xs' => $size === 'xs',
- ]))->render();
- };
+ return [
+ 'range',
+ 'range-neutral' => $this->color === 'neutral',
+ 'range-primary' => $this->color === 'primary',
+ 'range-secondary' => $this->color === 'secondary',
+ 'range-accent' => $this->color === 'accent',
+ 'range-info' => $this->color === 'info',
+ 'range-success' => $this->color === 'success',
+ 'range-warning' => $this->color === 'warning',
+ 'range-error' => $this->color === 'error',
+ 'range-xl' => $this->size === 'xl',
+ 'range-lg' => $this->size === 'lg',
+ 'range-md' => $this->size === 'md',
+ 'range-sm' => $this->size === 'sm',
+ 'range-xs' => $this->size === 'xs',
+ 'range-vertical' => $this->vertical,
+ ];
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
+ {
+ $attributes['type'] = 'range';
+ $attributes['min'] = $attributes->get('min', $this->min);
+ $attributes['max'] = $attributes->get('max', $this->max);
+ $attributes['value'] = $attributes->get('value', $this->value);
+
+ if ($this->step !== null) {
+ $attributes['step'] = $attributes->get('step', $this->step);
+ }
+
+ return $attributes;
}
}
diff --git a/src/Components/Rating.php b/src/Components/Rating.php
index 9e5697aa..f7eb15b0 100644
--- a/src/Components/Rating.php
+++ b/src/Components/Rating.php
@@ -2,15 +2,120 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Rating extends LazyComponent
+class Rating extends DaisyComponent
{
- public function render(): \Closure|View
- {
- return function (array $data) {
- return view('lazy::rating', $this->mergeData($data))->render();
+ protected const VIEW = 'lazy::rating';
+
+ public array $ratingItems = [];
+
+ public function __construct(
+ public string $name = 'rating',
+ public int $items = 5,
+ public int|float|null $value = null,
+ public string $mask = 'star-2',
+ public ?string $type = null,
+ public string $color = '',
+ public string $size = '',
+ public bool $half = false,
+ public bool $clearable = false,
+ public bool $readonly = false,
+ ) {
+ $maskClass = match ($this->type ?: $this->mask) {
+ 'heart' => 'mask-heart',
+ 'star' => 'mask-star',
+ default => 'mask-star-2',
+ };
+
+ $colorClass = match ($this->color) {
+ 'primary' => 'bg-primary',
+ 'secondary' => 'bg-secondary',
+ 'accent' => 'bg-accent',
+ 'success' => 'bg-success',
+ 'info' => 'bg-info',
+ 'warning' => 'bg-warning',
+ 'error' => 'bg-error',
+ default => '',
};
+
+ if ($this->clearable && ! $this->readonly) {
+ $this->ratingItems[] = [
+ 'value' => 0,
+ 'label' => 'clear',
+ 'checked' => (float) $this->value === 0.0,
+ 'classes' => 'rating-hidden',
+ 'clear' => true,
+ ];
+ }
+
+ $count = max(1, min(100, $this->items));
+ $steps = $this->half ? $count * 2 : $count;
+
+ for ($index = 1; $index <= $steps; $index++) {
+ $ratingValue = $this->half ? $index / 2 : $index;
+ $halfClass = ! $this->half ? '' : ($index % 2 === 1 ? 'mask-half-1' : 'mask-half-2');
+
+ $this->ratingItems[] = [
+ 'value' => $ratingValue,
+ 'label' => $ratingValue.' '.($ratingValue == 1 ? 'star' : 'stars'),
+ 'checked' => (float) $this->value === (float) $ratingValue,
+ 'classes' => $this->classes(array_filter(['mask', $maskClass, $colorClass, $halfClass])),
+ 'clear' => false,
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'rating',
+ 'rating-half' => $this->half,
+ 'rating-xs' => $this->size === 'xs',
+ 'rating-sm' => $this->size === 'sm',
+ 'rating-md' => $this->size === 'md',
+ 'rating-lg' => $this->size === 'lg',
+ 'rating-xl' => $this->size === 'xl',
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->ratingItems;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'classes' => '',
+ ], $items);
+ }
+
+ return [
+ 'ratingItems' => $items,
+ 'inputAttributes' => $attributes->only([
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'disabled',
+ 'form',
+ 'required',
+ ]),
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return [
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'disabled',
+ 'form',
+ 'required',
+ ];
}
}
diff --git a/src/Components/Richtext.php b/src/Components/Richtext.php
index 1b3cfd03..a0c58843 100644
--- a/src/Components/Richtext.php
+++ b/src/Components/Richtext.php
@@ -17,8 +17,15 @@ public function __construct(public string $placeholder = '', public bool $requir
{
$this->placeholder = (string) str($placeholder)->trim()->ucfirst();
$this->quillOptions = $quillOptions ?? QuillOptions::defaults();
- $this->autoFocus = false;
- $this->readonly = false;
+ }
+
+ protected function allowedColors(): array
+ {
+ return [
+ ...parent::allowedColors(),
+ 'no-border',
+ 'ghost',
+ ];
}
public function render(): \Closure|View
@@ -34,9 +41,8 @@ public function render(): \Closure|View
return view('lazy::richtext', $this->mergeData($data, [
'textarea',
- // colors
- 'textarea-bordered' => ! $color || $color === 'bordered' || $color !== 'no-border',
- 'textarea-ghost' => $color === 'ghost',
+ 'textarea-ghost' => $color === 'ghost' || $color === 'no-border',
+ 'textarea-neutral' => $color === 'neutral',
'textarea-primary' => $color === 'primary',
'textarea-secondary' => $color === 'secondary',
'textarea-accent' => $color === 'accent',
@@ -44,11 +50,14 @@ public function render(): \Closure|View
'textarea-success' => $color === 'success',
'textarea-warning' => $color === 'warning',
'textarea-error' => $color === 'error',
- // sizes
+ 'textarea-xl' => $size === 'xl',
'textarea-lg' => $size === 'lg',
'textarea-md' => $size === 'md',
'textarea-sm' => $size === 'sm',
'textarea-xs' => $size === 'xs',
+ ], [
+ 'color',
+ 'size',
]))->render();
};
}
diff --git a/src/Components/Select.php b/src/Components/Select.php
index 65df8196..02e154ab 100644
--- a/src/Components/Select.php
+++ b/src/Components/Select.php
@@ -2,13 +2,45 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Select extends LazyComponent
+class Select extends DaisyComponent
{
+ protected const VIEW = 'lazy::select';
+
public ?string $placeholder;
+ public array $normalizedOptions = [];
+
+ public function __construct(
+ public string $label = '',
+ string $placeholder = '',
+ public bool $required = false,
+ public bool $validator = false,
+ public string $hint = '',
+ array $options = [],
+ public string|int|float|null $value = null,
+ public bool $multiple = false,
+ public string $color = '',
+ public string $size = '',
+ ) {
+ $this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
+
+ foreach ($options as $key => $option) {
+ $normalized = is_array($option) ? $option : ['label' => $option];
+ $optionValue = $normalized['value'] ?? (is_int($key) ? $option : $key);
+
+ $this->normalizedOptions[] = [
+ 'value' => $optionValue,
+ 'label' => (string) ($normalized['label'] ?? $normalized['text'] ?? $optionValue),
+ 'disabled' => (bool) ($normalized['disabled'] ?? false),
+ 'selected' => array_key_exists('selected', $normalized)
+ ? (bool) $normalized['selected']
+ : (string) $optionValue === (string) $this->value,
+ ];
+ }
+ }
+
protected function allowedColors(): array
{
return [
@@ -18,36 +50,47 @@ protected function allowedColors(): array
];
}
- public function __construct(public string $label = '', string $placeholder = '', public bool $required = false)
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- $this->placeholder = (string) str($placeholder ?: $this->label)->trim()->ucfirst();
+ $attributes['required'] = $this->required;
+
+ if ($this->multiple) {
+ $attributes['multiple'] = true;
+ }
+
+ return $attributes;
}
- public function render(): \Closure|View
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['required'] = $this->required;
- $data['attributes'] = $attributes;
-
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
-
- return view('lazy::select', $this->mergeData($data, [
- 'select',
- 'select-ghost' => $color === 'ghost' || $color === 'no-border',
- 'select-primary' => $color === 'primary',
- 'select-secondary' => $color === 'secondary',
- 'select-accent' => $color === 'accent',
- 'select-info' => $color === 'info',
- 'select-success' => $color === 'success',
- 'select-warning' => $color === 'warning',
- 'select-error' => $color === 'error',
- 'select-lg' => $size === 'lg',
- 'select-md' => $size === 'md',
- 'select-sm' => $size === 'sm',
- 'select-xs' => $size === 'xs',
- ]))->render();
- };
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+
+ return [
+ 'options' => $this->normalizedOptions,
+ 'controlClass' => $unstyled ? '' : 'w-full'.(($data['hasError'] ?? false) ? ' text-error select-error' : ''),
+ 'hintClass' => $unstyled ? '' : 'validator-hint',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'select',
+ 'validator' => $this->validator,
+ 'select-ghost' => in_array($this->color, ['ghost', 'no-border'], true),
+ 'select-neutral' => $this->color === 'neutral',
+ 'select-primary' => $this->color === 'primary',
+ 'select-secondary' => $this->color === 'secondary',
+ 'select-accent' => $this->color === 'accent',
+ 'select-info' => $this->color === 'info',
+ 'select-success' => $this->color === 'success',
+ 'select-warning' => $this->color === 'warning',
+ 'select-error' => $this->color === 'error',
+ 'select-xl' => $this->size === 'xl',
+ 'select-lg' => $this->size === 'lg',
+ 'select-md' => $this->size === 'md',
+ 'select-sm' => $this->size === 'sm',
+ 'select-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/Skeleton.php b/src/Components/Skeleton.php
new file mode 100644
index 00000000..e501e509
--- /dev/null
+++ b/src/Components/Skeleton.php
@@ -0,0 +1,20 @@
+ $this->text,
+ ];
+ }
+}
diff --git a/src/Components/Stack.php b/src/Components/Stack.php
index 6fb3730a..69498401 100644
--- a/src/Components/Stack.php
+++ b/src/Components/Stack.php
@@ -2,19 +2,27 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Stack extends LazyComponent
+class Stack extends DaisyComponent
{
- /**
- * Get the view / contents that represent the component.
- */
- public function render(): Closure|View
+ protected const VIEW = 'lazy::stack';
+
+ public function __construct(
+ public bool $top = false,
+ public bool $bottom = false,
+ public bool $start = false,
+ public bool $end = false,
+ ) {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return static function (array $data) {
- return view('lazy::stack', $data)->render();
- };
+ return [
+ 'stack',
+ 'stack-top' => $this->top,
+ 'stack-bottom' => $this->bottom,
+ 'stack-start' => $this->start,
+ 'stack-end' => $this->end,
+ ];
}
}
diff --git a/src/Components/Stat.php b/src/Components/Stat.php
new file mode 100644
index 00000000..901db36c
--- /dev/null
+++ b/src/Components/Stat.php
@@ -0,0 +1,32 @@
+ 'stat-figure',
+ 'title' => 'stat-title',
+ 'value' => 'stat-value',
+ 'description' => 'stat-desc',
+ 'actions' => 'stat-actions',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['stat'];
+ }
+}
diff --git a/src/Components/Stats.php b/src/Components/Stats.php
new file mode 100644
index 00000000..d92dd4da
--- /dev/null
+++ b/src/Components/Stats.php
@@ -0,0 +1,68 @@
+ $item];
+
+ $this->stats[] = [
+ 'title' => (string) ($normalized['title'] ?? ''),
+ 'value' => $normalized['value'] ?? '',
+ 'description' => (string) ($normalized['description'] ?? $normalized['desc'] ?? ''),
+ 'figure' => $normalized['figure'] ?? null,
+ 'actions' => $normalized['actions'] ?? null,
+ 'classes' => $this->classes([
+ 'stat',
+ (string) ($normalized['class'] ?? '') => isset($normalized['class']),
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $stats = $this->stats;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $stats = array_map(static fn (array $stat): array => [
+ ...$stat,
+ 'classes' => '',
+ ], $stats);
+ }
+
+ return ['stats' => $stats];
+ }
+
+ protected function viewClasses(): array
+ {
+ return [
+ 'figure' => 'stat-figure',
+ 'title' => 'stat-title',
+ 'value' => 'stat-value',
+ 'description' => 'stat-desc',
+ 'actions' => 'stat-actions',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'stats',
+ 'stats-vertical' => $this->vertical,
+ 'stats-horizontal' => $this->horizontal,
+ ];
+ }
+}
diff --git a/src/Components/Status.php b/src/Components/Status.php
new file mode 100644
index 00000000..1e56670d
--- /dev/null
+++ b/src/Components/Status.php
@@ -0,0 +1,35 @@
+ $this->color === 'neutral',
+ 'status-primary' => $this->color === 'primary',
+ 'status-secondary' => $this->color === 'secondary',
+ 'status-accent' => $this->color === 'accent',
+ 'status-info' => $this->color === 'info',
+ 'status-success' => $this->color === 'success',
+ 'status-warning' => $this->color === 'warning',
+ 'status-error' => $this->color === 'error',
+ 'status-xs' => $this->size === 'xs',
+ 'status-sm' => $this->size === 'sm',
+ 'status-md' => $this->size === 'md',
+ 'status-lg' => $this->size === 'lg',
+ 'status-xl' => $this->size === 'xl',
+ ];
+ }
+}
diff --git a/src/Components/Step.php b/src/Components/Step.php
new file mode 100644
index 00000000..d6906764
--- /dev/null
+++ b/src/Components/Step.php
@@ -0,0 +1,32 @@
+ 'step-icon'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'step',
+ 'step-primary' => $this->color === 'primary',
+ 'step-secondary' => $this->color === 'secondary',
+ 'step-accent' => $this->color === 'accent',
+ 'step-neutral' => $this->color === 'neutral',
+ 'step-info' => $this->color === 'info',
+ 'step-success' => $this->color === 'success',
+ 'step-warning' => $this->color === 'warning',
+ 'step-error' => $this->color === 'error',
+ ];
+ }
+}
diff --git a/src/Components/Steps.php b/src/Components/Steps.php
new file mode 100644
index 00000000..fab27743
--- /dev/null
+++ b/src/Components/Steps.php
@@ -0,0 +1,90 @@
+steps = [];
+
+ foreach (array_values($items) as $index => $item) {
+ $normalized = is_array($item) ? $item : ['label' => $item];
+ $position = $index + 1;
+ $key = (string) ($normalized['key'] ?? $normalized['value'] ?? $position);
+ $stepColor = (string) ($normalized['color'] ?? $this->color);
+
+ $active = array_key_exists('active', $normalized)
+ ? (bool) $normalized['active']
+ : $this->isActive($position, $key);
+
+ $this->steps[] = [
+ 'label' => (string) ($normalized['label'] ?? $normalized['title'] ?? $position),
+ 'icon' => $normalized['icon'] ?? null,
+ 'active' => $active,
+ 'classes' => $this->classes([
+ 'step',
+ 'step-primary' => $active && $stepColor === 'primary',
+ 'step-secondary' => $active && $stepColor === 'secondary',
+ 'step-accent' => $active && $stepColor === 'accent',
+ 'step-neutral' => $active && $stepColor === 'neutral',
+ 'step-info' => $active && $stepColor === 'info',
+ 'step-success' => $active && $stepColor === 'success',
+ 'step-warning' => $active && $stepColor === 'warning',
+ 'step-error' => $active && $stepColor === 'error',
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $steps = $this->steps;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $steps = array_map(static fn (array $step): array => [
+ ...$step,
+ 'classes' => '',
+ ], $steps);
+ }
+
+ return ['steps' => $steps];
+ }
+
+ protected function viewClasses(): array
+ {
+ return ['icon' => 'step-icon'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'steps',
+ 'steps-vertical' => $this->vertical,
+ 'steps-horizontal' => ! $this->vertical,
+ ];
+ }
+
+ private function isActive(int $position, string $key): bool
+ {
+ if ($this->current === null) {
+ return false;
+ }
+
+ if (is_int($this->current) || ctype_digit((string) $this->current)) {
+ return $position <= (int) $this->current;
+ }
+
+ return $key === (string) $this->current;
+ }
+}
diff --git a/src/Components/Swap.php b/src/Components/Swap.php
new file mode 100644
index 00000000..92caf7c6
--- /dev/null
+++ b/src/Components/Swap.php
@@ -0,0 +1,70 @@
+ 'swap-on',
+ 'off' => 'swap-off',
+ 'indeterminate' => 'swap-indeterminate',
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'swap',
+ 'swap-active' => $this->active,
+ 'swap-rotate' => $this->rotate,
+ 'swap-flip' => $this->flip,
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'inputAttributes' => $attributes->only([
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'form',
+ 'required',
+ ]),
+ ];
+ }
+
+ protected function consumedAttributes(): array
+ {
+ return [
+ 'wire:model',
+ 'wire:model.live',
+ 'wire:model.blur',
+ 'wire:model.change',
+ 'wire:model.lazy',
+ 'form',
+ 'required',
+ ];
+ }
+}
diff --git a/src/Components/Tab.php b/src/Components/Tab.php
index 93e880da..1fde742c 100644
--- a/src/Components/Tab.php
+++ b/src/Components/Tab.php
@@ -2,31 +2,47 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Tab extends LazyComponent
+class Tab extends DaisyComponent
{
- public function render(): View|Closure
+ protected const VIEW = 'lazy::tab';
+
+ public function __construct(
+ public string $label = '',
+ public bool $active = false,
+ public bool $disabled = false,
+ public string $size = '',
+ ) {}
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
+ {
+ if ($this->disabled) {
+ $attributes['aria-disabled'] = 'true';
+ $attributes['tabindex'] = '-1';
+ }
+
+ return $attributes;
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'label' => $this->label ?: (string) $attributes->get('title', ''),
+ ];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'tab',
+ 'tab-active' => $this->active,
+ 'tab-disabled' => $this->disabled,
+ ];
+ }
+
+ protected function consumedAttributes(): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $data['label'] = $data['label'] ?: $attributes['title'] ?? '';
-
- $size = $this->getSizeByAttribute($attributes);
-
- return view('lazy::tab', $this->mergeData($data, [
- 'tab',
- 'tab-active' => $attributes['active'] ?? false,
- 'tab-sm' => $size === 'sm',
- 'tab-md' => $size === 'md',
- 'tab-lg' => $size === 'lg',
- 'tab-xl' => $size === 'xl',
- ], [
- 'active',
- 'size',
- ]))->render();
- };
+ return ['title'];
}
}
diff --git a/src/Components/Table.php b/src/Components/Table.php
new file mode 100644
index 00000000..87235569
--- /dev/null
+++ b/src/Components/Table.php
@@ -0,0 +1,88 @@
+ $header) {
+ $normalized = is_array($header) ? $header : ['label' => $header];
+ $resolvedKey = (string) ($normalized['key'] ?? (is_string($key) ? $key : $key));
+
+ $keys[] = $resolvedKey;
+ $this->headerItems[] = [
+ 'label' => (string) ($normalized['label'] ?? $normalized['title'] ?? $resolvedKey),
+ 'key' => $resolvedKey,
+ 'classes' => (string) ($normalized['class'] ?? ''),
+ ];
+ }
+
+ foreach ($rows as $row) {
+ $normalizedRow = is_array($row) ? $row : [$row];
+ $cells = [];
+
+ if ($this->headerItems !== []) {
+ foreach ($keys as $index => $key) {
+ $value = array_key_exists($key, $normalizedRow)
+ ? $normalizedRow[$key]
+ : ($normalizedRow[$index] ?? null);
+
+ $cells[] = $this->normalizeCell($value);
+ }
+ } else {
+ foreach (array_values($normalizedRow) as $value) {
+ $cells[] = $this->normalizeCell($value);
+ }
+ }
+
+ $this->rowItems[] = [
+ 'cells' => $cells,
+ 'classes' => is_array($row) ? (string) ($row['_class'] ?? '') : '',
+ ];
+ }
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'table',
+ 'table-zebra' => $this->zebra,
+ 'table-pin-rows' => $this->pinRows,
+ 'table-pin-cols' => $this->pinCols,
+ 'table-xs' => $this->size === 'xs',
+ 'table-sm' => $this->size === 'sm',
+ 'table-md' => $this->size === 'md',
+ 'table-lg' => $this->size === 'lg',
+ 'table-xl' => $this->size === 'xl',
+ ];
+ }
+
+ private function normalizeCell(mixed $cell): array
+ {
+ if (! is_array($cell)) {
+ return ['value' => $cell, 'classes' => ''];
+ }
+
+ return [
+ 'value' => $cell['value'] ?? $cell['label'] ?? '',
+ 'classes' => (string) ($cell['class'] ?? ''),
+ ];
+ }
+}
diff --git a/src/Components/Tabs.php b/src/Components/Tabs.php
index 999fb7ab..e27f726b 100644
--- a/src/Components/Tabs.php
+++ b/src/Components/Tabs.php
@@ -2,45 +2,108 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Tabs extends LazyComponent
+class Tabs extends DaisyComponent
{
- public function __construct(public string $type = '', public string $size = '') {}
+ protected const VIEW = 'lazy::tabs';
- public function allowedTabType(): array
+ public array $items = [];
+
+ public function __construct(
+ public string $type = '',
+ public string $size = '',
+ public string $placement = '',
+ public bool $top = false,
+ public bool $bottom = false,
+ array $items = [],
+ public int|string|null $active = null,
+ ) {
+ foreach (array_values($items) as $index => $item) {
+ $normalized = is_array($item) ? $item : ['label' => $item];
+ $key = $normalized['key'] ?? $normalized['value'] ?? $index;
+
+ $this->items[] = [
+ 'label' => (string) ($normalized['label'] ?? $normalized['title'] ?? $key),
+ 'href' => $normalized['href'] ?? null,
+ 'content' => $normalized['content'] ?? null,
+ 'disabled' => (bool) ($normalized['disabled'] ?? false),
+ 'active' => array_key_exists('active', $normalized)
+ ? (bool) $normalized['active']
+ : ((string) $this->active === (string) $key),
+ ];
+ }
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
+ $attributes['role'] = 'tablist';
+
+ return $attributes;
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+ $items = $this->items;
+
+ foreach ($items as &$item) {
+ $item['classes'] = $unstyled
+ ? ''
+ : $this->classes([
+ 'tab',
+ 'tab-active' => $item['active'],
+ 'tab-disabled' => $item['disabled'],
+ ]);
+ }
+ unset($item);
+
+ return ['items' => $items];
+ }
+
+ protected function viewClasses(): array
+ {
+ return ['content' => 'tab-content'];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ $type = $this->type;
+
+ foreach (['boxed', 'box', 'lifted', 'lift', 'bordered', 'border'] as $candidate) {
+ if ($attributes->has($candidate) && $this->truthy($attributes->get($candidate))) {
+ $type = $candidate;
+ break;
+ }
+ }
+
+ $placement = $this->placement;
+ if ($this->top || $attributes->has('top')) {
+ $placement = 'top';
+ } elseif ($this->bottom || $attributes->has('bottom')) {
+ $placement = 'bottom';
+ }
+
return [
- 'boxed',
- 'lifted',
- 'bordered',
+ 'tabs',
+ 'tabs-box' => in_array($type, ['boxed', 'box'], true),
+ 'tabs-lift' => in_array($type, ['lifted', 'lift'], true),
+ 'tabs-border' => in_array($type, ['bordered', 'border'], true),
+ 'tabs-top' => $placement === 'top',
+ 'tabs-bottom' => $placement === 'bottom',
+ 'tabs-xl' => $this->size === 'xl',
+ 'tabs-lg' => $this->size === 'lg',
+ 'tabs-md' => $this->size === 'md',
+ 'tabs-sm' => $this->size === 'sm',
+ 'tabs-xs' => $this->size === 'xs',
];
}
- public function render(): \Closure|View
+ protected function consumedAttributes(): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['role'] = 'tablist';
- $data['attributes'] = $attributes;
-
- $size = $this->getSizeByAttribute($attributes);
- $this->type = $this->getKeyByAttribute($attributes, $this->allowedTabType(), 'type', '');
-
- return view('lazy::tabs', $this->mergeData($data, [
- 'tabs',
- 'tabs-box' => $this->type === 'boxed',
- 'tabs-lift' => $this->type === 'lifted',
- 'tabs-border' => $this->type === 'bordered',
- 'tabs-lg' => $size === 'lg',
- 'tabs-md' => $size === 'md',
- 'tabs-sm' => $size === 'sm',
- 'tabs-xs' => $size === 'xs',
- ], [
- 'size',
- 'type',
- ]))->render();
- };
+ return [
+ 'boxed', 'box', 'lifted', 'lift', 'bordered', 'border',
+ 'top', 'bottom',
+ ];
}
}
diff --git a/src/Components/TextRotate.php b/src/Components/TextRotate.php
new file mode 100644
index 00000000..4726a844
--- /dev/null
+++ b/src/Components/TextRotate.php
@@ -0,0 +1,36 @@
+items = array_values(array_map(static fn ($item): string => (string) $item, $items));
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return ['text-rotate'];
+ }
+
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
+ {
+ if ($this->duration !== null) {
+ $attributes['style'] = trim(
+ (string) $attributes->get('style', '').'; --duration: '.max(1, $this->duration).'ms;',
+ '; '
+ );
+ }
+
+ return $attributes;
+ }
+}
diff --git a/src/Components/Textarea.php b/src/Components/Textarea.php
index 75cd00a6..f8ee0866 100644
--- a/src/Components/Textarea.php
+++ b/src/Components/Textarea.php
@@ -2,13 +2,26 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Textarea extends LazyComponent
+class Textarea extends DaisyComponent
{
+ protected const VIEW = 'lazy::textarea';
+
public ?string $placeholder;
+ public function __construct(
+ string $placeholder = '',
+ public bool $required = false,
+ public bool $validator = false,
+ public string $hint = '',
+ public string $value = '',
+ public string $color = '',
+ public string $size = '',
+ ) {
+ $this->placeholder = (string) str($placeholder)->trim()->ucfirst();
+ }
+
protected function allowedColors(): array
{
return [
@@ -18,36 +31,42 @@ protected function allowedColors(): array
];
}
- public function __construct(string $placeholder = '', public bool $required = false)
+ protected function prepareAttributes(ComponentAttributeBag $attributes): ComponentAttributeBag
{
- $this->placeholder = (string) str($placeholder)->trim()->ucfirst();
+ $attributes['required'] = $this->required;
+
+ return $attributes;
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $unstyled = $this->truthy($attributes->get('unstyled'));
+
+ return [
+ 'controlClass' => $unstyled ? '' : 'w-full'.(($data['hasError'] ?? false) ? ' textarea-error' : ''),
+ 'hintClass' => $unstyled ? '' : 'validator-hint',
+ ];
}
- public function render(): \Closure|View
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $attributes['required'] = $this->required;
- $data['attributes'] = $attributes;
-
- $color = $this->getColorByAttribute($attributes);
- $size = $this->getSizeByAttribute($attributes);
-
- return view('lazy::textarea', $this->mergeData($data, [
- 'textarea',
- 'textarea-ghost' => $color === 'ghost' || $color === 'no-border',
- 'textarea-primary' => $color === 'primary',
- 'textarea-secondary' => $color === 'secondary',
- 'textarea-accent' => $color === 'accent',
- 'textarea-info' => $color === 'info',
- 'textarea-success' => $color === 'success',
- 'textarea-warning' => $color === 'warning',
- 'textarea-error' => $color === 'error',
- 'textarea-lg' => $size === 'lg',
- 'textarea-md' => $size === 'md',
- 'textarea-sm' => $size === 'sm',
- 'textarea-xs' => $size === 'xs',
- ]))->render();
- };
+ return [
+ 'textarea',
+ 'validator' => $this->validator,
+ 'textarea-ghost' => in_array($this->color, ['ghost', 'no-border'], true),
+ 'textarea-neutral' => $this->color === 'neutral',
+ 'textarea-primary' => $this->color === 'primary',
+ 'textarea-secondary' => $this->color === 'secondary',
+ 'textarea-accent' => $this->color === 'accent',
+ 'textarea-info' => $this->color === 'info',
+ 'textarea-success' => $this->color === 'success',
+ 'textarea-warning' => $this->color === 'warning',
+ 'textarea-error' => $this->color === 'error',
+ 'textarea-xl' => $this->size === 'xl',
+ 'textarea-lg' => $this->size === 'lg',
+ 'textarea-md' => $this->size === 'md',
+ 'textarea-sm' => $this->size === 'sm',
+ 'textarea-xs' => $this->size === 'xs',
+ ];
}
}
diff --git a/src/Components/ThemeController.php b/src/Components/ThemeController.php
new file mode 100644
index 00000000..b9587b81
--- /dev/null
+++ b/src/Components/ThemeController.php
@@ -0,0 +1,20 @@
+mergeData($data))->render();
- };
+ return view('lazy::themeswitcher', [
+ 'themeToggle' => config('lazy.themes.theme_toggle', 'multiple'),
+ 'toggleThemes' => config('lazy.themes.toggle_themes', ['light', 'dark']),
+ 'themes' => config('lazy.themes.themes', []),
+ ]);
}
}
diff --git a/src/Components/Timeline.php b/src/Components/Timeline.php
new file mode 100644
index 00000000..af4257a0
--- /dev/null
+++ b/src/Components/Timeline.php
@@ -0,0 +1,70 @@
+ $item) {
+ $normalized = is_array($item) ? $item : ['end' => $item];
+
+ $this->items[] = [
+ 'start' => $normalized['start'] ?? null,
+ 'middle' => $normalized['middle'] ?? $normalized['icon'] ?? null,
+ 'end' => $normalized['end'] ?? $normalized['content'] ?? $normalized['label'] ?? null,
+ 'before' => $index > 0,
+ 'after' => $index < $lastIndex,
+ 'startClasses' => $this->classes([
+ 'timeline-start',
+ 'timeline-box' => $this->box || (bool) ($normalized['box'] ?? false),
+ ]),
+ 'middleClasses' => 'timeline-middle',
+ 'endClasses' => $this->classes([
+ 'timeline-end',
+ 'timeline-box' => $this->box || (bool) ($normalized['box'] ?? false),
+ ]),
+ ];
+ }
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
+ {
+ $items = $this->items;
+
+ if ($this->truthy($attributes->get('unstyled'))) {
+ $items = array_map(static fn (array $item): array => [
+ ...$item,
+ 'startClasses' => '',
+ 'middleClasses' => '',
+ 'endClasses' => '',
+ ], $items);
+ }
+
+ return ['items' => $items];
+ }
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'timeline',
+ 'timeline-vertical' => $this->vertical,
+ 'timeline-horizontal' => ! $this->vertical,
+ 'timeline-compact' => $this->compact,
+ 'timeline-snap-icon' => $this->snapIcon,
+ ];
+ }
+}
diff --git a/src/Components/TimelineItem.php b/src/Components/TimelineItem.php
new file mode 100644
index 00000000..77b58c5c
--- /dev/null
+++ b/src/Components/TimelineItem.php
@@ -0,0 +1,23 @@
+ ['timeline-start', 'timeline-box' => $this->box],
+ 'middle' => 'timeline-middle',
+ 'end' => ['timeline-end', 'timeline-box' => $this->box],
+ ];
+ }
+}
diff --git a/src/Components/Toast.php b/src/Components/Toast.php
index 08aa8fe5..88744edb 100644
--- a/src/Components/Toast.php
+++ b/src/Components/Toast.php
@@ -2,15 +2,37 @@
namespace Step2dev\LazyUI\Components;
-use Illuminate\Contracts\View\View;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Toast extends LazyComponent
+class Toast extends DaisyComponent
{
- public function render(): \Closure|View
+ protected const VIEW = 'lazy::toast';
+
+ public function __construct(
+ public bool $top = true,
+ public bool $middle = false,
+ public bool $bottom = false,
+ public bool $start = false,
+ public bool $center = false,
+ public bool $end = true,
+ ) {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ return [
+ 'toast',
+ 'toast-top' => $this->top,
+ 'toast-middle' => $this->middle,
+ 'toast-bottom' => $this->bottom,
+ 'toast-start' => $this->start,
+ 'toast-center' => $this->center,
+ 'toast-end' => $this->end,
+ 'z-[1000]',
+ ];
+ }
+
+ protected function componentData(array $data, ComponentAttributeBag $attributes): array
{
- return function (array $data) {
- return view('lazy::toast', $this->mergeData($data))->render();
- };
+ return ['notify' => session('notify-flash')];
}
}
diff --git a/src/Components/Toggle.php b/src/Components/Toggle.php
index 5b5b5491..5897575b 100644
--- a/src/Components/Toggle.php
+++ b/src/Components/Toggle.php
@@ -18,15 +18,22 @@ public function render(): \Closure|View
return view('lazy::toggle', $this->mergeData($data, [
'toggle',
- // colors
+ 'toggle-neutral' => $color === 'neutral',
'toggle-primary' => $color === 'primary',
'toggle-secondary' => $color === 'secondary',
'toggle-accent' => $color === 'accent',
- // sizes
+ 'toggle-info' => $color === 'info',
+ 'toggle-success' => $color === 'success',
+ 'toggle-warning' => $color === 'warning',
+ 'toggle-error' => $color === 'error',
+ 'toggle-xl' => $size === 'xl',
'toggle-lg' => $size === 'lg',
'toggle-md' => $size === 'md',
'toggle-sm' => $size === 'sm',
'toggle-xs' => $size === 'xs',
+ ], [
+ 'color',
+ 'size',
]))->render();
};
}
diff --git a/src/Components/Tooltip.php b/src/Components/Tooltip.php
index 888cfe3d..e6403f3e 100644
--- a/src/Components/Tooltip.php
+++ b/src/Components/Tooltip.php
@@ -2,37 +2,80 @@
namespace Step2dev\LazyUI\Components;
-use Closure;
-use Step2dev\LazyUI\LazyComponent;
+use Illuminate\View\ComponentAttributeBag;
-class Tooltip extends LazyComponent
+class Tooltip extends DaisyComponent
{
- /**
- * Get the view / contents that represent the component.
- */
- public function render(): Closure
+ protected const VIEW = 'lazy::tooltip';
+
+ public function __construct(
+ public string $tip = '',
+ public bool $open = false,
+ public ?string $position = null,
+ public ?string $align = null,
+ public ?string $color = null,
+ ) {}
+
+ protected function componentClasses(array $data, ComponentAttributeBag $attributes): array
+ {
+ $position = $this->position ?: 'top';
+
+ foreach (['top', 'right', 'bottom', 'left'] as $candidate) {
+ if ($attributes->has($candidate)) {
+ $position = $candidate;
+ break;
+ }
+ }
+
+ $align = $this->align;
+
+ foreach (['start', 'center', 'end'] as $candidate) {
+ if ($attributes->has($candidate)) {
+ $align = $candidate;
+ break;
+ }
+ }
+
+ $color = $this->color;
+
+ foreach (['primary', 'secondary', 'accent', 'info', 'success', 'warning', 'error'] as $candidate) {
+ if ($attributes->has($candidate)) {
+ $color = $candidate;
+ break;
+ }
+ }
+
+ return [
+ 'tooltip',
+ 'tooltip-open' => $this->open,
+ 'tooltip-top' => $position === 'top',
+ 'tooltip-right' => $position === 'right',
+ 'tooltip-bottom' => $position === 'bottom',
+ 'tooltip-left' => $position === 'left',
+ 'tooltip-start' => $align === 'start',
+ 'tooltip-center' => $align === 'center',
+ 'tooltip-end' => $align === 'end',
+ 'tooltip-primary' => $color === 'primary',
+ 'tooltip-secondary' => $color === 'secondary',
+ 'tooltip-accent' => $color === 'accent',
+ 'tooltip-info' => $color === 'info',
+ 'tooltip-success' => $color === 'success',
+ 'tooltip-warning' => $color === 'warning',
+ 'tooltip-error' => $color === 'error',
+ ];
+ }
+
+ protected function viewClasses(): array
+ {
+ return ['content' => 'tooltip-content'];
+ }
+
+ protected function consumedAttributes(): array
{
- return function (array $data) {
- $attributes = $this->getAttributesFromData($data);
- $position = $this->getPositionByAttribute($attributes, 'top');
- $color = $this->getColorByAttribute($attributes);
-
- return view('lazy::tooltip', $this->mergeData($data, [
- 'tooltip',
- 'tooltip-open' => $attributes->get('open', false),
- 'tooltip-top' => $position === 'top',
- 'tooltip-right' => $position === 'right',
- 'tooltip-bottom' => $position === 'bottom',
- 'tooltip-left' => $position === 'left',
- // colors
- 'tooltip-primary' => $color === 'primary',
- 'tooltip-secondary' => $color === 'secondary',
- 'tooltip-accent' => $color === 'accent',
- 'tooltip-info' => $color === 'info',
- 'tooltip-success' => $color === 'success',
- 'tooltip-warning' => $color === 'warning',
- 'tooltip-error' => $color === 'error',
- ]))->render();
- };
+ return [
+ 'top', 'right', 'bottom', 'left',
+ 'start', 'center', 'end',
+ 'primary', 'secondary', 'accent', 'info', 'success', 'warning', 'error',
+ ];
}
}
diff --git a/src/DTO/RichText/QuillOptions.php b/src/DTO/RichText/QuillOptions.php
index 5382131b..ecd03a43 100644
--- a/src/DTO/RichText/QuillOptions.php
+++ b/src/DTO/RichText/QuillOptions.php
@@ -2,7 +2,7 @@
namespace Step2dev\LazyUI\DTO\RichText;
-use InvalidArgumentException;
+use Closure;
class QuillOptions
{
@@ -10,7 +10,6 @@ class QuillOptions
public ?array $toolbar = null;
- // Toolbar options
public bool|array $font = true;
public bool $size = true;
@@ -49,19 +48,19 @@ class QuillOptions
public array $customToolbarButtons = [];
- public static $defaultCallback;
+ public static ?Closure $defaultCallback = null;
public static function default(): self
{
- $options = is_callable(static::$defaultCallback)
- ? call_user_func(static::$defaultCallback)
- : static::$defaultCallback;
+ if (static::$defaultCallback !== null) {
+ $options = (static::$defaultCallback)();
- if ($options instanceof static) {
- return $options;
+ if ($options instanceof static) {
+ return $options;
+ }
}
- /** @phpstan-ignore-next-line */
+ /** @phpstan-ignore-next-line Keep late-static construction for custom QuillOptions subclasses. */
return new static;
}
@@ -76,11 +75,7 @@ public static function defaults(?callable $callback = null): ?self
return static::default();
}
- if (! is_callable($callback) || ! $callback instanceof static) {
- throw new InvalidArgumentException('The callback must be a callable or an instance of '.static::class);
- }
-
- static::$defaultCallback = $callback;
+ static::$defaultCallback = Closure::fromCallable($callback);
return null;
}
diff --git a/src/LazyComponent.php b/src/LazyComponent.php
index c800516b..e88e035c 100644
--- a/src/LazyComponent.php
+++ b/src/LazyComponent.php
@@ -15,6 +15,36 @@ abstract class LazyComponent extends Component
protected const DEFAULT = 'default';
+ protected const DEFAULT_SIZES = [
+ 'xs',
+ 'sm',
+ 'md',
+ 'lg',
+ 'xl',
+ ];
+
+ protected const DEFAULT_COLORS = [
+ 'primary',
+ 'secondary',
+ 'accent',
+ 'neutral',
+ 'info',
+ 'success',
+ 'warning',
+ 'error',
+ ];
+
+ protected const DEFAULT_POSITIONS = [
+ 'vertical',
+ 'horizontal',
+ 'top',
+ 'bottom',
+ 'left',
+ 'right',
+ 'start',
+ 'end',
+ ];
+
public string $label = '';
protected array $smartAttributes = [
@@ -42,12 +72,21 @@ public function componentSlot(mixed $slot): ComponentSlot
protected function mergeData(array $data, array $classes = [], array $exceptAttributes = []): array
{
- $attributes = $this->mergeClasses($data['attributes'], $classes);
+ $attributes = $this->getAttributesFromData($data);
+ $unstyled = $this->isTruthyAttribute($attributes, 'unstyled');
+
+ $data['unstyled'] = $unstyled;
+
+ if (! $unstyled) {
+ $attributes = $this->mergeClasses($attributes, $classes);
+ }
$attributes['disabled'] = (bool) $attributes->get('disabled');
+
$data['attributes'] = $attributes->except([
...$this->smartAttributes,
...$exceptAttributes,
+ 'unstyled',
]);
return $data;
@@ -55,28 +94,12 @@ protected function mergeData(array $data, array $classes = [], array $exceptAttr
protected function allowedSizes(): array
{
- return [
- 'xs',
- 'sm',
- 'md',
- 'lg',
- 'xl',
- // '2xl',
- ];
+ return static::DEFAULT_SIZES;
}
protected function allowedColors(): array
{
- return [
- 'primary',
- 'secondary',
- 'accent',
- 'neutral',
- 'info',
- 'success',
- 'warning',
- 'error',
- ];
+ return static::DEFAULT_COLORS;
}
final protected function findBySmartAttribute(
@@ -84,11 +107,15 @@ final protected function findBySmartAttribute(
array $keys,
?string $default = null
): ?string {
- $modifier = collect($attributes->only($keys)->getAttributes())->filter()->keys()->first();
+ foreach ($keys as $candidate) {
+ if ($this->isTruthyAttribute($attributes, $candidate)) {
+ $this->addSmartAttribute($candidate);
- $this->addSmartAttribute($modifier);
+ return $candidate;
+ }
+ }
- return $modifier ?? $default;
+ return $default;
}
public function getSizeByAttribute(ComponentAttributeBag $attribute, ?string $default = null): ?string
@@ -102,18 +129,19 @@ final protected function getKeyByAttribute(
?string $key = null,
?string $default = null
): ?string {
- $key = $this->findBySmartAttribute($attribute, $keys)
- ?? $attribute->get($key, $default);
-
- $this->addSmartAttribute($key);
+ $value = $this->findBySmartAttribute($attribute, $keys);
- $key = strtolower((string) $key);
+ if ($value === null) {
+ $value = $attribute->get($key, $default);
- if (in_array($key, $keys, true)) {
- return $key;
+ if ($key !== null && $attribute->has($key)) {
+ $this->addSmartAttribute($key);
+ }
}
- return $default;
+ $value = strtolower((string) $value);
+
+ return in_array($value, $keys, true) ? $value : $default;
}
public function getColorByAttribute(ComponentAttributeBag $attribute, ?string $default = null): ?string
@@ -149,14 +177,19 @@ final protected function getValueByKeyOrSmartAttribute(
?string $key = null,
?string $default = null
): ?string {
- $value = collect($attributes->only($allowedValues)->getAttributes())->filter()->keys()->first()
- ?? $attributes->get($key, $default);
+ $value = $this->findBySmartAttribute($attributes, $allowedValues);
+
+ if ($value === null) {
+ $value = $attributes->get($key, $default);
+
+ if ($key !== null && $attributes->has($key)) {
+ $this->addSmartAttribute($key);
+ }
+ }
$value = strtolower((string) $value);
if (in_array($value, $allowedValues, true)) {
- $this->addSmartAttribute($value);
-
return $value;
}
@@ -170,15 +203,17 @@ public function getPositionByAttribute(ComponentAttributeBag $attribute, ?string
protected function allowedPosition(): array
{
- return [
- 'vertical',
- 'horizontal',
- 'top',
- 'bottom',
- 'left',
- 'right',
- 'start',
- 'end',
- ];
+ return static::DEFAULT_POSITIONS;
+ }
+
+ protected function isTruthyAttribute(ComponentAttributeBag $attributes, string $key): bool
+ {
+ if (! $attributes->has($key)) {
+ return false;
+ }
+
+ $value = $attributes->get($key);
+
+ return $value !== false && $value !== null && $value !== 'false' && $value !== '0' && $value !== 0;
}
}
diff --git a/src/LazyUiServiceProvider.php b/src/LazyUiServiceProvider.php
index d20be1fe..a94195f4 100644
--- a/src/LazyUiServiceProvider.php
+++ b/src/LazyUiServiceProvider.php
@@ -2,6 +2,7 @@
namespace Step2dev\LazyUI;
+use Illuminate\Support\Facades\Blade;
use Livewire\Component;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
@@ -9,6 +10,7 @@
use Step2dev\LazyUI\Commands\LazyInstallCommand;
use Step2dev\LazyUI\Components\Accordion;
use Step2dev\LazyUI\Components\Alert;
+use Step2dev\LazyUI\Components\Aura;
use Step2dev\LazyUI\Components\Avatar;
use Step2dev\LazyUI\Components\AvatarGroup;
use Step2dev\LazyUI\Components\Badge;
@@ -18,12 +20,28 @@
use Step2dev\LazyUI\Components\Buttons\BtnBack;
use Step2dev\LazyUI\Components\Buttons\BtnDelete;
use Step2dev\LazyUI\Components\Buttons\BtnLogout;
+use Step2dev\LazyUI\Components\Calendar;
+use Step2dev\LazyUI\Components\Card;
+use Step2dev\LazyUI\Components\Carousel;
+use Step2dev\LazyUI\Components\CarouselItem;
use Step2dev\LazyUI\Components\Chat;
use Step2dev\LazyUI\Components\Checkbox;
use Step2dev\LazyUI\Components\Choices;
+use Step2dev\LazyUI\Components\Collapse;
use Step2dev\LazyUI\Components\Countdown;
+use Step2dev\LazyUI\Components\DataList;
+use Step2dev\LazyUI\Components\Diff;
use Step2dev\LazyUI\Components\Divider;
+use Step2dev\LazyUI\Components\Dock;
+use Step2dev\LazyUI\Components\DockItem;
+use Step2dev\LazyUI\Components\Drawer;
+use Step2dev\LazyUI\Components\Dropdown;
use Step2dev\LazyUI\Components\Error;
+use Step2dev\LazyUI\Components\Fab;
+use Step2dev\LazyUI\Components\Fieldset;
+use Step2dev\LazyUI\Components\FileInput;
+use Step2dev\LazyUI\Components\Filter;
+use Step2dev\LazyUI\Components\Footer;
use Step2dev\LazyUI\Components\Form;
use Step2dev\LazyUI\Components\Form\FormCheckbox;
use Step2dev\LazyUI\Components\Form\FormImage;
@@ -33,29 +51,56 @@
use Step2dev\LazyUI\Components\Form\FormTextarea;
use Step2dev\LazyUI\Components\Form\FormToggle;
use Step2dev\LazyUI\Components\FormGroup;
+use Step2dev\LazyUI\Components\Hero;
+use Step2dev\LazyUI\Components\Hover3d;
+use Step2dev\LazyUI\Components\HoverGallery;
use Step2dev\LazyUI\Components\Image;
+use Step2dev\LazyUI\Components\Indicator;
use Step2dev\LazyUI\Components\Input;
+use Step2dev\LazyUI\Components\InputGroup;
use Step2dev\LazyUI\Components\Join;
use Step2dev\LazyUI\Components\Kbd;
use Step2dev\LazyUI\Components\Label;
use Step2dev\LazyUI\Components\Link;
+use Step2dev\LazyUI\Components\ListRow;
use Step2dev\LazyUI\Components\Loading;
+use Step2dev\LazyUI\Components\Mask;
+use Step2dev\LazyUI\Components\Megamenu;
use Step2dev\LazyUI\Components\Menu;
+use Step2dev\LazyUI\Components\MenuList;
use Step2dev\LazyUI\Components\Mockup\MockupBrowser;
use Step2dev\LazyUI\Components\Mockup\MockupCode;
use Step2dev\LazyUI\Components\Mockup\MockupPhone;
use Step2dev\LazyUI\Components\Mockup\MockupWindow;
+use Step2dev\LazyUI\Components\Modal;
+use Step2dev\LazyUI\Components\Navbar;
+use Step2dev\LazyUI\Components\Otp;
+use Step2dev\LazyUI\Components\Pagination;
+use Step2dev\LazyUI\Components\PaginationItem;
+use Step2dev\LazyUI\Components\Progress;
use Step2dev\LazyUI\Components\Radial;
use Step2dev\LazyUI\Components\Radio;
use Step2dev\LazyUI\Components\Range;
use Step2dev\LazyUI\Components\Rating;
use Step2dev\LazyUI\Components\Richtext;
use Step2dev\LazyUI\Components\Select;
+use Step2dev\LazyUI\Components\Skeleton;
use Step2dev\LazyUI\Components\Stack;
+use Step2dev\LazyUI\Components\Stat;
+use Step2dev\LazyUI\Components\Stats;
+use Step2dev\LazyUI\Components\Status;
+use Step2dev\LazyUI\Components\Step;
+use Step2dev\LazyUI\Components\Steps;
+use Step2dev\LazyUI\Components\Swap;
use Step2dev\LazyUI\Components\Tab;
+use Step2dev\LazyUI\Components\Table;
use Step2dev\LazyUI\Components\Tabs;
use Step2dev\LazyUI\Components\Textarea;
+use Step2dev\LazyUI\Components\TextRotate;
+use Step2dev\LazyUI\Components\ThemeController;
use Step2dev\LazyUI\Components\ThemeSwitcher;
+use Step2dev\LazyUI\Components\Timeline;
+use Step2dev\LazyUI\Components\TimelineItem;
use Step2dev\LazyUI\Components\Toast;
use Step2dev\LazyUI\Components\Toggle;
use Step2dev\LazyUI\Components\Tooltip;
@@ -65,21 +110,15 @@ class LazyUiServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
- /*
- * This class is a Package Service Provider
- *
- * More info: https://github.com/spatie/laravel-package-tools
- */
$package
->name('lazy-ui')
->hasViews('lazy')
->hasTranslations()
->hasConfigFile(['lazy/themes'])
->hasViewComponents('lazy',
- // Dropdown::class,
- // Carousel::class,
Accordion::class,
Alert::class,
+ Aura::class,
Avatar::class,
AvatarGroup::class,
Badge::class,
@@ -89,12 +128,28 @@ public function configurePackage(Package $package): void
BtnDelete::class,
BtnGroup::class,
BtnLogout::class,
+ Calendar::class,
+ Card::class,
+ Carousel::class,
+ CarouselItem::class,
Chat::class,
Checkbox::class,
- Countdown::class,
Choices::class,
+ Collapse::class,
+ Countdown::class,
+ DataList::class,
+ Diff::class,
Divider::class,
+ Dock::class,
+ DockItem::class,
+ Drawer::class,
+ Dropdown::class,
Error::class,
+ Fab::class,
+ Fieldset::class,
+ FileInput::class,
+ Filter::class,
+ Footer::class,
Form::class,
FormCheckbox::class,
FormGroup::class,
@@ -104,33 +159,59 @@ public function configurePackage(Package $package): void
FormSelect::class,
FormTextarea::class,
FormToggle::class,
+ Hero::class,
+ Hover3d::class,
+ HoverGallery::class,
Image::class,
+ Indicator::class,
Input::class,
+ InputGroup::class,
Join::class,
Kbd::class,
Label::class,
Link::class,
+ ListRow::class,
Loading::class,
+ Mask::class,
+ Megamenu::class,
Menu::class,
+ MenuList::class,
MockupBrowser::class,
MockupCode::class,
MockupPhone::class,
MockupWindow::class,
+ Modal::class,
+ Navbar::class,
+ Otp::class,
+ Pagination::class,
+ PaginationItem::class,
+ Progress::class,
Radial::class,
Radio::class,
Range::class,
Rating::class,
Richtext::class,
Select::class,
+ Skeleton::class,
Stack::class,
+ Stat::class,
+ Stats::class,
+ Status::class,
+ Step::class,
+ Steps::class,
+ Swap::class,
Tab::class,
+ Table::class,
Tabs::class,
Textarea::class,
+ TextRotate::class,
+ ThemeController::class,
ThemeSwitcher::class,
+ Timeline::class,
+ TimelineItem::class,
Toast::class,
Toggle::class,
Tooltip::class,
- // widgets
CountryTimeWidget::class,
)
->hasCommand(LazyInstallCommand::class)
@@ -158,4 +239,10 @@ public function packageRegistered(): void
session()->flash('notify-flash', compact('message', 'title', 'type'));
});
}
+
+ public function packageBooted(): void
+ {
+ Blade::component(Hover3d::class, 'lazy-hover-3d');
+ Blade::component(DataList::class, 'lazy-list');
+ }
}
diff --git a/src/Traits/HasErrors.php b/src/Traits/HasErrors.php
deleted file mode 100644
index da26ddd9..00000000
--- a/src/Traits/HasErrors.php
+++ /dev/null
@@ -1,13 +0,0 @@
-hasErrors ?? false;
- }
-}
diff --git a/src/Traits/ResolvesFormFieldState.php b/src/Traits/ResolvesFormFieldState.php
new file mode 100644
index 00000000..4ca99913
--- /dev/null
+++ b/src/Traits/ResolvesFormFieldState.php
@@ -0,0 +1,20 @@
+wire('model')->value();
+ $errors = $data['errors'] ?? null;
+
+ return [
+ 'parameter' => $parameter,
+ 'hasError' => $parameter && $errors ? $errors->has($parameter) : false,
+ 'required' => (bool) $attributes->get('required', false),
+ ];
+ }
+}
diff --git a/stubs/css/lazy.css b/stubs/css/lazy.css
index 8cc9de3b..ec93a3ca 100644
--- a/stubs/css/lazy.css
+++ b/stubs/css/lazy.css
@@ -1,8 +1,25 @@
+@import "tailwindcss";
@import "../../vendor/step2dev/lazy-ui/resources/css/lazy.css";
@source "../views/**/*.blade.php";
@source "../js/**/*.js";
@source "../../app/**/*.php";
+@source "../../vendor/step2dev/lazy-ui/src/**/*.php";
+@source "../../vendor/step2dev/lazy-ui/resources/views/**/*.blade.php";
+@source "../../vendor/step2dev/lazy-ui/resources/js/**/*.js";
+
+@plugin "@tailwindcss/forms" {
+ strategy: "class";
+}
+
+@plugin "daisyui" {
+ themes: light --default, dark --prefersdark, cupcake, bumblebee, emerald, corporate, synthwave, retro, cyberpunk, valentine, halloween, garden, forest, aqua, lofi, pastel, fantasy, wireframe, black, luxury, dracula, cmyk, autumn, business, acid, lemonade, night, coffee, winter, dim, nord, sunset, caramellatte, abyss, silk;
+ logs: false;
+}
+
+@theme {
+ --font-base: "Inter", sans-serif;
+}
@supports (-webkit-touch-callout: none) {
.h-screen {
diff --git a/stubs/js/lazy.js b/stubs/js/lazy.js
index de0dd43d..45bb02ba 100644
--- a/stubs/js/lazy.js
+++ b/stubs/js/lazy.js
@@ -1,16 +1,8 @@
-import {Livewire, Alpine} from '../../vendor/livewire/livewire/dist/livewire.esm'
-
-import Quill from "quill";
-
-window.Quill = Quill;
-
+import { Alpine } from '../../vendor/livewire/livewire/dist/livewire.esm';
import axios from 'axios';
-window.axios = axios;
-import '../../vendor/step2dev/lazy-ui/resources/js/lazy.js'
+import '../../vendor/step2dev/lazy-ui/resources/js/lazy.js';
+window.Alpine ??= Alpine;
+window.axios ??= axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
-
-window.addEventListener('DOMContentLoaded', async (event) => {
- Livewire.start()
-})
diff --git a/tests/Architecture/BladeArchitectureTest.php b/tests/Architecture/BladeArchitectureTest.php
new file mode 100644
index 00000000..f2d0d0f8
--- /dev/null
+++ b/tests/Architecture/BladeArchitectureTest.php
@@ -0,0 +1,34 @@
+isFile() || ! str_ends_with($file->getFilename(), '.blade.php')) {
+ continue;
+ }
+
+ $source = file_get_contents($file->getPathname());
+ $relative = str_replace($root.'/', '', $file->getPathname());
+
+ $patterns = [
+ '@php block' => '/@php\b/',
+ 'PHP match expression' => '/\bmatch\s*\(/',
+ 'unstyled ternary' => '/\$unstyled\s*\?/',
+ 'conditional @class with unstyled' => '/@class\s*\([^)]*\$unstyled/s',
+ 'attribute class mapping with unstyled' => '/->class\s*\(\s*\[[^\]]*\$unstyled/s',
+ ];
+
+ foreach ($patterns as $name => $pattern) {
+ if (preg_match($pattern, $source) === 1) {
+ $violations[] = $relative.': '.$name;
+ }
+ }
+ }
+
+ expect($violations)->toBe([]);
+});
diff --git a/tests/Components/DaisyUiIntegrationTest.php b/tests/Components/DaisyUiIntegrationTest.php
new file mode 100644
index 00000000..d8fc6086
--- /dev/null
+++ b/tests/Components/DaisyUiIntegrationTest.php
@@ -0,0 +1,522 @@
+toContain('@source "../../vendor/step2dev/lazy-ui/src/**/*.php"')
+ ->toContain('@source "../../vendor/step2dev/lazy-ui/resources/views/**/*.blade.php"')
+ ->toContain('@source "../../vendor/step2dev/lazy-ui/resources/js/**/*.js"');
+});
+
+it('renders universal daisyUI wrappers with their default component classes', function (string $component, string $class) {
+ $this
+ ->blade(" ")
+ ->assertSee($class);
+})->with([
+ ['aura', 'aura'],
+ ['calendar', 'input'],
+ ['card', 'card'],
+ ['carousel', 'carousel'],
+ ['collapse', 'collapse'],
+ ['diff', 'diff'],
+ ['dock', 'dock'],
+ ['dropdown', 'dropdown'],
+ ['fab', 'fab'],
+ ['fieldset', 'fieldset'],
+ ['file-input', 'file-input'],
+ ['filter', 'filter'],
+ ['footer', 'footer'],
+ ['hover-gallery', 'hover-gallery'],
+ ['hover-3d', 'hover-3d'],
+ ['list', 'list'],
+ ['mask', 'mask'],
+ ['megamenu', 'megamenu'],
+ ['modal', 'modal'],
+ ['navbar', 'navbar'],
+ ['otp', 'otp'],
+ ['pagination', 'join'],
+ ['progress', 'progress'],
+ ['skeleton', 'skeleton'],
+ ['stats', 'stats'],
+ ['status', 'status'],
+ ['steps', 'steps'],
+ ['swap', 'swap'],
+ ['table', 'table'],
+ ['text-rotate', 'text-rotate'],
+ ['theme-controller', 'theme-controller'],
+ ['timeline', 'timeline'],
+]);
+
+it('registers and renders legacy components that existed but were not wired into Lazy UI', function (string $component, string $class) {
+ $this
+ ->blade(" ")
+ ->assertSee($class);
+})->with([
+ ['drawer', 'drawer'],
+ ['hero', 'hero'],
+ ['indicator', 'indicator'],
+]);
+
+it('keeps legacy component aliases mapped to daisyUI 5 classes', function () {
+ $this
+ ->blade('One Two ')
+ ->assertSee('join')
+ ->assertSee('join-item');
+
+ $this
+ ->blade('Go ')
+ ->assertSee('join')
+ ->assertSee('join-item');
+
+ $this
+ ->blade('Content ')
+ ->assertSee('card-border')
+ ->assertSee('card-sm');
+
+ $this
+ ->blade(' ')
+ ->assertSee('tabs-box');
+
+ $this
+ ->blade('Delete ')
+ ->assertSee('btn-error')
+ ->assertDontSee('btn-danger');
+
+ $this
+ ->blade('Danger ')
+ ->assertSee('badge-error')
+ ->assertDontSee('badge-danger');
+
+ $this
+ ->blade('Danger ')
+ ->assertSee('alert-error')
+ ->assertDontSee('alert-danger');
+});
+
+it('supports daisyUI 5 colors sizes and styles without manual class names', function () {
+ $this
+ ->blade('Save ')
+ ->assertSee('btn-neutral')
+ ->assertSee('btn-xl')
+ ->assertSee('btn-soft');
+
+ $this
+ ->blade('Badge ')
+ ->assertSee('badge-primary')
+ ->assertSee('badge-xl')
+ ->assertSee('badge-dash');
+
+ $this
+ ->blade(' ')
+ ->assertSee('checkbox-neutral')
+ ->assertSee('checkbox-xl');
+
+ $this
+ ->blade(' ')
+ ->assertSee('radio-neutral')
+ ->assertSee('radio-xl');
+
+ $this
+ ->blade(' ')
+ ->assertSee('range-neutral')
+ ->assertSee('range-xl')
+ ->assertSee('range-vertical');
+
+ $this
+ ->blade(' ')
+ ->assertSee('loading-xl');
+
+ $this
+ ->blade('K ')
+ ->assertSee('kbd-xl');
+});
+
+it('integrates daisyUI validator into form inputs', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('validator')
+ ->assertSee('validator-hint')
+ ->assertSee('Invalid value');
+
+ $this
+ ->blade('One ')
+ ->assertSee('validator')
+ ->assertSee('validator-hint');
+
+ $this
+ ->blade(' ')
+ ->assertSee('validator')
+ ->assertSee('validator-hint');
+});
+
+it('renders old demo-only components as reusable components', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('--value:42', false)
+ ->assertDontSee('Large text');
+
+ $this
+ ->blade(' ')
+ ->assertSee('name="score"', false)
+ ->assertDontSee('rating-10');
+
+ $this
+ ->blade('Browser content ')
+ ->assertSee('https://example.com')
+ ->assertSee('Browser content')
+ ->assertDontSee('Hello!');
+
+ $this
+ ->blade('npm test ')
+ ->assertSee('npm test')
+ ->assertDontSee('npm i daisyui');
+
+ $this
+ ->blade(' ')
+ ->assertSee('data-prefix="$"', false)
+ ->assertSee('data-prefix=">"', false)
+ ->assertSee('bg-warning text-warning-content', false)
+ ->assertSee('bg-success text-success-content', false)
+ ->assertSee('bg-error text-error-content', false)
+ ->assertSee('Error!');
+
+ $this
+ ->blade('Window content ')
+ ->assertSee('Window content')
+ ->assertDontSee('Hello!');
+});
+
+it('covers every daisyUI 5 component family with a Lazy UI wrapper or integrated modifier', function (string $component, string $class) {
+ $this
+ ->blade(" ")
+ ->assertSee($class);
+})->with([
+ ['accordion', 'collapse'],
+ ['alert', 'alert'],
+ ['aura', 'aura'],
+ ['avatar', 'avatar'],
+ ['badge', 'badge'],
+ ['breadcrumbs', 'breadcrumbs'],
+ ['btn', 'btn'],
+ ['calendar', 'input'],
+ ['card', 'card'],
+ ['carousel', 'carousel'],
+ ['chat', 'chat'],
+ ['checkbox', 'checkbox'],
+ ['collapse', 'collapse'],
+ ['countdown', 'countdown'],
+ ['diff', 'diff'],
+ ['divider', 'divider'],
+ ['dock', 'dock'],
+ ['drawer', 'drawer'],
+ ['dropdown', 'dropdown'],
+ ['fab', 'fab'],
+ ['fieldset', 'fieldset'],
+ ['file-input', 'file-input'],
+ ['filter', 'filter'],
+ ['footer', 'footer'],
+ ['hero', 'hero'],
+ ['hover-3d', 'hover-3d'],
+ ['hover-gallery', 'hover-gallery'],
+ ['indicator', 'indicator'],
+ ['input', 'input'],
+ ['join', 'join'],
+ ['kbd', 'kbd'],
+ ['label', 'label'],
+ ['list', 'list'],
+ ['loading', 'loading'],
+ ['mask', 'mask'],
+ ['megamenu', 'megamenu'],
+ ['menu-list', 'menu'],
+ ['mockup-browser', 'mockup-browser'],
+ ['mockup-code', 'mockup-code'],
+ ['mockup-phone', 'mockup-phone'],
+ ['mockup-window', 'mockup-window'],
+ ['modal', 'modal'],
+ ['navbar', 'navbar'],
+ ['otp', 'otp'],
+ ['pagination', 'join'],
+ ['progress', 'progress'],
+ ['radial', 'radial-progress'],
+ ['radio', 'radio'],
+ ['range', 'range'],
+ ['rating', 'rating'],
+ ['select', 'select'],
+ ['skeleton', 'skeleton'],
+ ['stack', 'stack'],
+ ['stat', 'stat'],
+ ['status', 'status'],
+ ['steps', 'steps'],
+ ['swap', 'swap'],
+ ['tabs', 'tabs'],
+ ['table', 'table'],
+ ['text-rotate', 'text-rotate'],
+ ['textarea', 'textarea'],
+ ['theme-controller', 'theme-controller'],
+ ['timeline', 'timeline'],
+ ['toast', 'toast'],
+ ['toggle', 'toggle'],
+ ['tooltip', 'tooltip'],
+]);
+
+it('exposes new daisyUI 5 modifiers without requiring manual class names', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('dropdown-center')
+ ->assertSee('dropdown-bottom')
+ ->assertSee('dropdown-close');
+
+ $this
+ ->blade(' ')
+ ->assertSee('modal-top')
+ ->assertSee('modal-end');
+
+ $this
+ ->blade(' ')
+ ->assertSee('stack-top')
+ ->assertSee('stack-start');
+
+ $this
+ ->blade('Saved ')
+ ->assertSee('alert-success')
+ ->assertSee('alert-soft')
+ ->assertSee('alert-dash');
+
+ $this
+ ->blade('1 Done ')
+ ->assertSee('step-icon')
+ ->assertSee('Done');
+
+ $this
+ ->blade('Rich tip Hover ')
+ ->assertSee('tooltip-content')
+ ->assertSee('Rich tip');
+});
+
+it('builds complete accessible rating markup from semantic props', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('rating rating-lg', false)
+ ->assertSee('name="rating-10"', false)
+ ->assertSee('class="rating-hidden"', false)
+ ->assertSee('aria-label="clear"', false)
+ ->assertSee('class="mask mask-star-2"', false)
+ ->assertSee('aria-label="1 star"', false)
+ ->assertSee('aria-label="2 stars"', false)
+ ->assertSee('value="2"', false)
+ ->assertSee('checked', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('mask-heart')
+ ->assertSee('aria-current="true"', false)
+ ->assertDontSee('type="radio"', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('rating-half')
+ ->assertSee('mask-half-1')
+ ->assertSee('mask-half-2')
+ ->assertSee('wire:model.live="score"', false);
+});
+
+it('generates data-driven component markup from semantic props', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('Start')
+ ->assertSee('Done')
+ ->assertSee('step-primary');
+
+ $this
+ ->blade(' ')
+ ->assertSee('href="/page/1"', false)
+ ->assertSee('href="/page/2"', false)
+ ->assertSee('btn-active');
+
+ $this
+ ->blade(' ')
+ ->assertSee('Users')
+ ->assertSee('42')
+ ->assertSee('Active');
+
+ $this
+ ->blade(' ')
+ ->assertSee('table-zebra')
+ ->assertSee('Name ', false)
+ ->assertSee('Yurii ', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('value="active"', false)
+ ->assertSee('checked', false);
+});
+
+it('generates collection components from semantic data', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('value="2"', false)
+ ->assertSee('selected', false)
+ ->assertSee('Two');
+
+ $this
+ ->blade(' ')
+ ->assertSee('tabs-box')
+ ->assertSee('tab-active')
+ ->assertSee('Summary');
+
+ $this
+ ->blade(' ')
+ ->assertSee('carousel-item')
+ ->assertSee('src="/one.jpg"', false)
+ ->assertSee('Second');
+
+ $this
+ ->blade(' ')
+ ->assertSee('First')
+ ->assertSee('Body')
+ ->assertSee('checked', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('href="/"', false)
+ ->assertSee('aria-current="page"', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('dock-active')
+ ->assertSee('dock-label')
+ ->assertSee('href="/"', false);
+});
+
+it('generates interactive controls from semantic props', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('type="checkbox"', false)
+ ->assertSee('name="enabled"', false)
+ ->assertSee('checked', false)
+ ->assertSee('swap-on')
+ ->assertSee('swap-off');
+
+ $this
+ ->blade(' ')
+ ->assertSee('maxlength="4"', false)
+ ->assertSee('wire:model.live="code"', false)
+ ->assertSee('inputmode="numeric"', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('range-neutral')
+ ->assertSee('range-xl')
+ ->assertSee('range-vertical')
+ ->assertSee('step="2"', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('radial-progress')
+ ->assertSee('text-success')
+ ->assertSee('--size: 4rem', false)
+ ->assertSee('--thickness: 4px', false)
+ ->assertSee('75 percent');
+});
+
+it('keeps Blade templates presentation-only without server-side php blocks', function () {
+ $iterator = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator(dirname(__DIR__, 2).'/resources/views')
+ );
+
+ foreach ($iterator as $file) {
+ if (! $file->isFile() || ! str_ends_with($file->getFilename(), '.blade.php')) {
+ continue;
+ }
+
+ expect(file_get_contents($file->getPathname()))
+ ->not->toContain('@php');
+ }
+});
+
+it('keeps legacy toast behavior while using daisyUI toast and alert classes', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('toast')
+ ->assertSee('toast-top')
+ ->assertSee('toast-end')
+ ->assertSee('alert')
+ ->assertSee('$store.toasts.list', false);
+});
+
+it('keeps drawer hero and indicator defaults compatible but configurable', function () {
+ $this
+ ->blade('Content ')
+ ->assertSee('drawer')
+ ->assertSee('menu')
+ ->assertSee('w-80')
+ ->assertSee('p-4')
+ ->assertSee('bg-base-100')
+ ->assertSee('Menu')
+ ->assertSee('Content');
+
+ $this
+ ->blade(' ')
+ ->assertSee('drawer-end')
+ ->assertSee('w-96')
+ ->assertSee('p-2')
+ ->assertSee('bg-base-200');
+
+ $this
+ ->blade(' ')
+ ->assertSee('hero')
+ ->assertSee('bg-base-200')
+ ->assertSee('text-center')
+ ->assertSee('max-w-md')
+ ->assertSee('text-5xl')
+ ->assertSee('py-6');
+
+ $this
+ ->blade(' ')
+ ->assertSee('bg-primary')
+ ->assertSee('text-primary-content')
+ ->assertSee('text-left')
+ ->assertSee('max-w-lg')
+ ->assertSee('text-3xl');
+
+ $this
+ ->blade('Inbox ')
+ ->assertSee('indicator-item')
+ ->assertSee('badge')
+ ->assertSee('badge-secondary')
+ ->assertSee('Inbox');
+
+ $this
+ ->blade('Inbox ')
+ ->assertSee('indicator-start')
+ ->assertSee('indicator-bottom');
+});
+
+it('keeps legacy drawer hero and indicator view paths as adapters', function () {
+ expect(file_get_contents(dirname(__DIR__, 2).'/resources/views/components/drawer.blade.php'))
+ ->toContain("@include('lazy::drawer')")
+ ->and(file_get_contents(dirname(__DIR__, 2).'/resources/views/components/hero.blade.php'))
+ ->toContain("@include('lazy::hero')")
+ ->and(file_get_contents(dirname(__DIR__, 2).'/resources/views/components/indicator.blade.php'))
+ ->toContain("@include('lazy::indicator')");
+});
+
+it('generates independent drawer toggle ids and allows explicit ids', function () {
+ $first = (string) $this->blade(' ');
+ $second = (string) $this->blade(' ');
+
+ preg_match('/id="([^"]+)"/', $first, $firstId);
+ preg_match('/id="([^"]+)"/', $second, $secondId);
+
+ expect($firstId[1] ?? null)->not->toBeNull()
+ ->and($secondId[1] ?? null)->not->toBeNull()
+ ->and($firstId[1])->not->toBe($secondId[1]);
+
+ $this
+ ->blade(' ')
+ ->assertSee('id="settings-drawer"', false)
+ ->assertSee('for="settings-drawer"', false);
+});
diff --git a/tests/Components/LegacyCompatibilityTest.php b/tests/Components/LegacyCompatibilityTest.php
new file mode 100644
index 00000000..b00dd3a1
--- /dev/null
+++ b/tests/Components/LegacyCompatibilityTest.php
@@ -0,0 +1,73 @@
+blade('Save ')
+ ->assertSee('btn-primary')
+ ->assertSee('btn-sm')
+ ->assertSee('btn-outline');
+
+ $this->blade('Danger ')
+ ->assertSee('badge-error')
+ ->assertDontSee('badge-danger');
+
+ $this->blade('Danger ')
+ ->assertSee('alert-error')
+ ->assertSee('alert-soft');
+
+ $this->blade(' ')
+ ->assertSee('tabs-box');
+
+ $this->blade(' ')
+ ->assertSee('loading-dots')
+ ->assertSee('loading-lg')
+ ->assertSee('text-primary');
+
+ $this->blade('OR ')
+ ->assertSee('divider-horizontal')
+ ->assertSee('OR');
+
+ $this->blade(' ')
+ ->assertSee('type="checkbox"', false)
+ ->assertSee('checkbox-neutral')
+ ->assertSee('checkbox-xl');
+
+ $this->blade(' ')
+ ->assertSee('type="radio"', false)
+ ->assertSee('radio-success')
+ ->assertSee('radio-sm');
+});
+
+it('keeps legacy slot based usage alongside semantic APIs', function () {
+ $this->blade('One ')
+ ->assertSee('Choose')
+ ->assertSee('One ', false);
+
+ $this->blade('Profile ')
+ ->assertSee('tabs-box')
+ ->assertSee('tab-active')
+ ->assertSee('Profile');
+
+ $this->blade('Slide ')
+ ->assertSee('carousel')
+ ->assertSee('carousel-item')
+ ->assertSee('Slide');
+
+ $this->blade('Content ')
+ ->assertSee('drawer')
+ ->assertSee('Menu')
+ ->assertSee('Content');
+});
+
+it('keeps unstyled as an opt out for generated presentation classes', function () {
+ $this->blade('Body ')
+ ->assertDontSee('card-body')
+ ->assertDontSee('card-title');
+
+ $this->blade('Hover ')
+ ->assertDontSee('tooltip-top');
+
+ $this->blade(' ')
+ ->assertDontSee('step-primary');
+});
diff --git a/tests/Components/LegacyInteractiveComponentsTest.php b/tests/Components/LegacyInteractiveComponentsTest.php
new file mode 100644
index 00000000..2574e7a6
--- /dev/null
+++ b/tests/Components/LegacyInteractiveComponentsTest.php
@@ -0,0 +1,90 @@
+blade('Generate ')
+ ->assertSee('tooltip')
+ ->assertSee('tooltip-bottom')
+ ->assertSee('tooltip-info')
+ ->assertSee('join-item')
+ ->assertSee('data-tip="Manual generate site map"', false)
+ ->assertSee('Generate');
+});
+
+it('supports rich tooltip content without breaking the legacy tip API', function () {
+ $this
+ ->blade('Rich tip Hover ')
+ ->assertSee('tooltip')
+ ->assertSee('tooltip-content')
+ ->assertSee('Rich tip ', false)
+ ->assertSee('data-tip="Fallback"', false);
+});
+
+it('keeps choices as an interactive Alpine listbox with array options', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('x-data="select(', false)
+ ->assertSee('x-bind="button"', false)
+ ->assertSee('x-bind="listbox"', false)
+ ->assertSee('role="listbox"', false)
+ ->assertSee('class="select', false)
+ ->assertSee('value="1"', false)
+ ->assertSee('News')
+ ->assertSee('Tech');
+});
+
+it('keeps choices option slot compatibility without server-side html parsing', function () {
+ $this
+ ->blade('Українська English ')
+ ->assertSee('x-ref="native"', false)
+ ->assertSee('value="uk"', false)
+ ->assertSee('Українська')
+ ->assertSee('value="en"', false)
+ ->assertSee('English');
+});
+
+it('keeps legacy raw javascript choices options for backwards compatibility', function () {
+ $this
+ ->blade(' ')
+ ->assertSee("select([{'label':'One','value':1}], defaultValue", false);
+});
+
+it('keeps old tabs, avatar and range contracts while translating to daisyUI 5', function () {
+ $this
+ ->blade('Tab ')
+ ->assertSee('tabs-lift')
+ ->assertSee('tabs-sm')
+ ->assertSee('tab-active')
+ ->assertSee('Tab');
+
+ $this
+ ->blade(' ')
+ ->assertSee('avatar-online')
+ ->assertSee('w-16')
+ ->assertSee('src="avatar.jpg"', false);
+
+ $this
+ ->blade(' ')
+ ->assertSee('range-primary')
+ ->assertSee('step="25"', false);
+});
+
+it('keeps Lazy UI frontend modules self contained', function () {
+ $source = file_get_contents(dirname(__DIR__, 2).'/resources/js/lazy.js');
+ $choices = file_get_contents(dirname(__DIR__, 2).'/resources/js/components/select.js');
+
+ expect($source)
+ ->toContain("import './components/select'")
+ ->and($choices)
+ ->toContain("Alpine.data('select'")
+ ->toContain("this.\$dispatch('input', item.value)")
+ ->not->toContain('console.log');
+});
+
+it('keeps legacy component view paths available', function () {
+ expect(view()->exists('lazy::components.drawer'))->toBeTrue()
+ ->and(view()->exists('lazy::components.hero'))->toBeTrue()
+ ->and(view()->exists('lazy::components.indicator'))->toBeTrue();
+});
diff --git a/tests/Components/LivewireCompatibilityTest.php b/tests/Components/LivewireCompatibilityTest.php
new file mode 100644
index 00000000..5c62b06d
--- /dev/null
+++ b/tests/Components/LivewireCompatibilityTest.php
@@ -0,0 +1,46 @@
+blade(' ')
+ ->assertSee('wire:model.live="name"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.live="country"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.blur="bio"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.live="enabled"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model="type"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.live="volume"', false);
+});
+
+it('propagates Livewire state to generated interactive inputs', function () {
+ $rating = (string) $this->blade(' ');
+
+ expect(substr_count($rating, 'wire:model.live="score"'))->toBe(5);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.live="code"', false);
+
+ $this->blade(' ')
+ ->assertSee('wire:model.live="enabled"', false);
+});
+
+it('bridges Choices to Livewire through Alpine entanglement', function () {
+ $html = (string) $this->blade(
+ ' '
+ );
+
+ expect($html)
+ ->toContain('$wire.entangle')
+ ->toContain('country')
+ ->toContain('.live');
+});
diff --git a/tests/Performance/CustomizationContractTest.php b/tests/Performance/CustomizationContractTest.php
new file mode 100644
index 00000000..04b1a4d2
--- /dev/null
+++ b/tests/Performance/CustomizationContractTest.php
@@ -0,0 +1,61 @@
+blade('Go ')
+ ->assertSee('custom-button')
+ ->assertSee('data-user="yes"', false)
+ ->assertSee('aria-label="Custom button"', false)
+ ->assertSee('wire:key="custom-button"', false);
+});
+
+it('supports unstyled components without losing user classes', function () {
+ $this
+ ->blade('Go ')
+ ->assertSee('class="custom-button"', false)
+ ->assertDontSee('btn-primary')
+ ->assertDontSee('btn-lg');
+
+ $this
+ ->blade(' ')
+ ->assertSee('class="custom-input"', false)
+ ->assertDontSee('input-primary')
+ ->assertDontSee('input-lg');
+});
+
+it('does not cache rendered component attributes between renders', function () {
+ $this
+ ->blade('First ')
+ ->assertSee('btn-primary')
+ ->assertSee('first-render');
+
+ $this
+ ->blade('Second ')
+ ->assertSee('class="second-render"', false)
+ ->assertDontSee('btn-primary')
+ ->assertDontSee('first-render');
+});
+
+it('passes theme configuration as view data instead of html attributes', function () {
+ config()->set('lazy.themes.theme_toggle', 'toggle');
+ config()->set('lazy.themes.toggle_themes', ['light', 'dark']);
+ config()->set('lazy.themes.themes', ['light', 'dark', 'business']);
+
+ $this
+ ->blade(' ')
+ ->assertSee('data-toggle-theme="light,dark"', false)
+ ->assertDontSee('toggleThemes=', false)
+ ->assertDontSee('themes=', false);
+});
+
+it('keeps logout on the Lazy UI button styling contract without javascript', function () {
+ $this
+ ->blade(' ')
+ ->assertSee('class="btn', false)
+ ->assertSee('btn-primary')
+ ->assertSee('custom-logout')
+ ->assertSee('method="POST"', false)
+ ->assertDontSee('class="logout"', false);
+});
diff --git a/tests/Performance/FrontendContractTest.php b/tests/Performance/FrontendContractTest.php
new file mode 100644
index 00000000..9b0436d5
--- /dev/null
+++ b/tests/Performance/FrontendContractTest.php
@@ -0,0 +1,64 @@
+not->toContain('Livewire.start(')
+ ->not->toContain('import {Livewire')
+ ->not->toContain('import { Livewire');
+});
+
+it('loads Quill dynamically instead of bundling it into the base javascript', function () {
+ $source = packageFile('resources/js/components/quill.js');
+
+ expect($source)
+ ->toContain("import('quill')")
+ ->not->toContain('import Quill from');
+});
+
+it('keeps Tailwind and daisyUI configuration in the application css entrypoint', function () {
+ $packageCss = packageFile('resources/css/lazy.css');
+ $applicationCss = packageFile('stubs/css/lazy.css');
+
+ expect($packageCss)
+ ->not->toContain('@import "tailwindcss"')
+ ->not->toContain('@plugin "daisyui"')
+ ->not->toContain('quill/dist/quill.snow.css');
+
+ expect($applicationCss)
+ ->toContain('@import "tailwindcss"')
+ ->toContain('@plugin "daisyui"')
+ ->toContain('@source "../../vendor/step2dev/lazy-ui/resources/views/**/*.blade.php"');
+});
+
+it('does not duplicate toast css inside the blade template', function () {
+ expect(packageFile('resources/views/toast.blade.php'))
+ ->not->toContain('