' . $this->title . '
';
- $class = 'inline-flex w-full p-2 text-sm border rounded ' . $variants[$this->variant]['class'] . ' ' . $this->class;
-
- unset($this->attributes['slot']);
- unset($this->attributes['variant']);
- unset($this->attributes['class']);
- unset($this->attributes['glyph']);
- $attributes = stringify_attributes($this->attributes);
+ $title = $this->title === '' ? '' : '' . $this->title . '
';
+ $this->mergeClass('inline-flex w-full p-2 text-sm border rounded ');
+ $this->mergeClass($variantData['class']);
return <<{$glyph}' . $this->subtitle . '
';
}
+ $this->mergeClass('bg-elevated border-3 rounded-xl border-subtle');
+
return <<
+ getStringifiedAttributes()}>
{$this->title}
{$subtitleBlock}
diff --git a/app/Views/Components/DashboardCard.php b/app/Views/Components/DashboardCard.php
index 6510fe4b..e951f251 100644
--- a/app/Views/Components/DashboardCard.php
+++ b/app/Views/Components/DashboardCard.php
@@ -8,7 +8,9 @@ use ViewComponents\Component;
class DashboardCard extends Component
{
- protected ?string $href = null;
+ protected array $props = ['href', 'glyph', 'title', 'subtitle'];
+
+ protected string $href = '';
protected string $glyph;
@@ -27,11 +29,11 @@ class DashboardCard extends Component
'class' => 'flex-shrink-0 bg-base rounded-full w-8 h-8 p-2 text-accent-base',
]);
- if ($this->href !== null && $this->href !== '') {
+ if ($this->href !== '') {
$chevronRight = icon('arrow-right-s-fill');
$viewLang = lang('Common.view');
return <<
+
{$glyph}
{$this->title} {$viewLang} {$chevronRight}
{$this->subtitle}
{$this->slot}
diff --git a/app/Views/Components/DropdownMenu.php b/app/Views/Components/DropdownMenu.php
index 2a50ea71..bed6e299 100644
--- a/app/Views/Components/DropdownMenu.php
+++ b/app/Views/Components/DropdownMenu.php
@@ -9,17 +9,25 @@ use ViewComponents\Component;
class DropdownMenu extends Component
{
- public string $id = '';
+ protected array $props = ['id', 'labelledby', 'placement', 'offsetX', 'offsetY', 'items'];
- public string $labelledby;
+ protected array $casts = [
+ 'offsetX' => 'number',
+ 'offsetY' => 'number',
+ 'items' => 'array',
+ ];
- public string $placement = 'bottom-end';
+ protected string $id;
- public string $offsetX = '0';
+ protected string $labelledby;
- public string $offsetY = '0';
+ protected string $placement = 'bottom-end';
- public array $items = [];
+ protected int $offsetX = 0;
+
+ protected int $offsetY = 0;
+
+ protected array $items = [];
public function setItems(string $value): void
{
@@ -37,7 +45,7 @@ class DropdownMenu extends Component
switch ($item['type']) {
case 'link':
$menuItems .= anchor($item['uri'], $item['title'], [
- 'class' => 'inline-flex gap-x-1 items-center px-4 py-1 hover:bg-highlight focus:ring-accent focus:ring-inset' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''),
+ 'class' => 'inline-flex gap-x-1 items-center px-4 py-1 hover:bg-highlight' . (array_key_exists('class', $item) ? ' ' . $item['class'] : ''),
]);
break;
case 'html':
@@ -51,14 +59,16 @@ class DropdownMenu extends Component
}
}
+ $this->mergeClass('absolute flex flex-col py-2 rounded-lg z-60 whitespace-nowrap text-skin-base border-contrast bg-elevated border-3');
+ $this->attributes['id'] = $this->id;
+ $this->attributes['aria-labelledby'] = $this->labelledby;
+ $this->attributes['data-dropdown'] = 'menu';
+ $this->attributes['data-dropdown-placement'] = $this->placement;
+ $this->attributes['data-dropdown-offset-x'] = $this->offsetX;
+ $this->attributes['data-dropdown-offset-y'] = $this->offsetY;
+
return <<{$menuItems}
+
getStringifiedAttributes()}>{$menuItems}
HTML;
}
}
diff --git a/app/Views/Components/Forms/Checkbox.php b/app/Views/Components/Forms/Checkbox.php
index 1d81acb8..9ffafc8b 100644
--- a/app/Views/Components/Forms/Checkbox.php
+++ b/app/Views/Components/Forms/Checkbox.php
@@ -4,35 +4,41 @@ declare(strict_types=1);
namespace App\Views\Components\Forms;
+use App\Views\Components\Hint;
+
class Checkbox extends FormComponent
{
- protected ?string $hint = null;
+ protected array $props = ['hint', 'isChecked'];
+
+ protected array $casts = [
+ 'isChecked' => 'boolean',
+ ];
+
+ protected string $hint = '';
protected bool $isChecked = false;
- public function setIsChecked(string $value): void
- {
- $this->isChecked = $value === 'true';
- }
-
public function render(): string
{
- $attributes = [
- 'id' => $this->value,
- 'name' => $this->name,
- 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 focus:ring-accent w-6 h-6',
- ];
-
$checkboxInput = form_checkbox(
- $attributes,
+ [
+ 'id' => $this->value,
+ 'name' => $this->name,
+ 'class' => 'form-checkbox bg-elevated text-accent-base border-contrast border-3 w-6 h-6',
+ ],
'yes',
old($this->name) ? old($this->name) === $this->value : $this->isChecked,
);
- $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1');
+ $hint = $this->hint === '' ? '' : (new Hint([
+ 'class' => 'ml-1',
+ 'slot' => $this->hint,
+ ]))->render();
+
+ $this->mergeClass('inline-flex items-center');
return <<{$checkboxInput}
{$this->slot}{$hint}
+
getStringifiedAttributes()}>{$checkboxInput}{$this->slot}{$hint}
HTML;
}
}
diff --git a/app/Views/Components/Forms/ColorRadioButton.php b/app/Views/Components/Forms/ColorRadioButton.php
index ab2fb16f..b562d71e 100644
--- a/app/Views/Components/Forms/ColorRadioButton.php
+++ b/app/Views/Components/Forms/ColorRadioButton.php
@@ -6,15 +6,14 @@ namespace App\Views\Components\Forms;
class ColorRadioButton extends FormComponent
{
+ protected array $props = ['isChecked'];
+
+ protected array $casts = [
+ 'isChecked' => 'boolean',
+ ];
+
protected bool $isChecked = false;
- protected string $style = '';
-
- public function setIsChecked(string $value): void
- {
- $this->isChecked = $value === 'true';
- }
-
public function render(): string
{
$data = [
@@ -23,7 +22,7 @@ class ColorRadioButton extends FormComponent
'class' => 'color-radio-btn',
];
- if ($this->required) {
+ if ($this->isRequired) {
$data['required'] = 'required';
}
@@ -34,7 +33,7 @@ class ColorRadioButton extends FormComponent
);
return <<
+
getStringifiedAttributes()}>
{$radioInput}
diff --git a/app/Views/Components/Forms/DatetimePicker.php b/app/Views/Components/Forms/DatetimePicker.php
index f2d33332..54bc4fa4 100644
--- a/app/Views/Components/Forms/DatetimePicker.php
+++ b/app/Views/Components/Forms/DatetimePicker.php
@@ -6,21 +6,28 @@ namespace App\Views\Components\Forms;
class DatetimePicker extends FormComponent
{
+ protected array $attributes = [
+ 'data-picker' => 'datetime',
+ ];
+
public function render(): string
{
- $this->attributes['class'] = 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0';
- $this->attributes['data-input'] = '';
- $dateInput = form_input($this->attributes, old($this->name, $this->value));
+ $dateInput = form_input([
+ 'class' => 'rounded-l-lg border-0 border-rounded-r-none flex-1 focus:ring-0',
+ 'data-input' => '',
+ ], old($this->name, $this->value));
$clearLabel = lang(
'Episode.publish_form.scheduled_publication_date_clear',
);
$closeIcon = icon('close-fill');
+ $this->mergeClass('flex border-3 rounded-lg border-contrast focus-within:ring-accent');
+
return <<
+
getStringifiedAttributes()}>
{$dateInput}
-
+
{$closeIcon}
diff --git a/app/Views/Components/Forms/Field.php b/app/Views/Components/Forms/Field.php
index 40d219cb..784b3a91 100644
--- a/app/Views/Components/Forms/Field.php
+++ b/app/Views/Components/Forms/Field.php
@@ -4,49 +4,76 @@ declare(strict_types=1);
namespace App\Views\Components\Forms;
-class Field extends FormComponent
+use ViewComponents\Component;
+
+class Field extends Component
{
+ protected array $props = [
+ 'name',
+ 'label',
+ 'isRequired',
+ 'isReadonly',
+ 'as',
+ 'helper',
+ 'hint',
+ ];
+
+ protected array $casts = [
+ 'isRequired' => 'boolean',
+ 'isReadonly' => 'boolean',
+ ];
+
+ protected string $name;
+
+ protected string $label;
+
+ protected bool $isRequired = false;
+
+ protected bool $isReadonly = false;
+
protected string $as = 'Input';
- protected string $label = '';
+ protected string $helper = '';
- protected ?string $helper = null;
-
- protected ?string $hint = null;
+ protected string $hint = '';
public function render(): string
{
$helperText = '';
- if ($this->helper !== null) {
- $helperId = $this->id . 'Help';
- $helperText = '
' . $this->helper . ' ';
+ if ($this->helper !== '') {
+ $helperId = $this->name . 'Help';
+ $helperText = (new Helper([
+ 'id' => $helperId,
+ 'slot' => $this->helper,
+ ]))->render();
$this->attributes['aria-describedby'] = $helperId;
}
$labelAttributes = [
- 'for' => $this->id,
- 'isOptional' => $this->required ? 'false' : 'true',
+ 'for' => $this->name,
+ 'isOptional' => $this->isRequired ? 'false' : 'true',
'class' => '-mb-1',
+ 'slot' => $this->label,
];
- if ($this->hint) {
+ if ($this->hint !== '') {
$labelAttributes['hint'] = $this->hint;
}
- $labelAttributes = stringify_attributes($labelAttributes);
+ $label = new Label($labelAttributes);
- // remove field specific attributes to inject the rest to Form Component
- $fieldComponentAttributes = $this->attributes;
- unset($fieldComponentAttributes['as']);
- unset($fieldComponentAttributes['label']);
- unset($fieldComponentAttributes['class']);
- unset($fieldComponentAttributes['helper']);
- unset($fieldComponentAttributes['hint']);
+ $this->mergeClass('flex flex-col');
+ $fieldClass = $this->attributes['class'];
+ unset($this->attributes['class']);
+
+ $this->attributes['name'] = $this->name;
+ $this->attributes['isRequired'] = $this->isRequired ? 'true' : 'false';
+ $this->attributes['isReadonly'] = $this->isReadonly ? 'true' : 'false';
$element = __NAMESPACE__ . '\\' . $this->as;
- $fieldElement = new $element($fieldComponentAttributes);
+ $fieldElement = new $element($this->attributes);
return <<
-
{$this->label}
+
+ {$label->render()}
{$helperText}
{$fieldElement->render()}
diff --git a/app/Views/Components/Forms/FormComponent.php b/app/Views/Components/Forms/FormComponent.php
index f854fb64..2217b2af 100644
--- a/app/Views/Components/Forms/FormComponent.php
+++ b/app/Views/Components/Forms/FormComponent.php
@@ -6,28 +6,55 @@ namespace App\Views\Components\Forms;
use ViewComponents\Component;
-class FormComponent extends Component
+abstract class FormComponent extends Component
{
- protected ?string $id = null;
+ protected array $props = [
+ 'id',
+ 'name',
+ 'value',
+ 'isRequired',
+ 'isReadonly',
+ ];
- protected string $name = '';
+ protected array $casts = [
+ 'isRequired' => 'boolean',
+ 'isReadonly' => 'boolean',
+ ];
+
+ protected string $id;
+
+ protected string $name;
protected string $value = '';
- protected bool $required = false;
+ protected bool $isRequired = false;
- protected bool $readonly = false;
+ protected bool $isReadonly = false;
/**
* @param array
$attributes
*/
public function __construct(array $attributes)
{
+ $parentVars = get_class_vars(self::class);
+ $this->casts = [...$parentVars['casts'], ...$this->casts];
+ $this->props = [...$parentVars['props'], $this->props];
+
parent::__construct($attributes);
- if ($this->id === null) {
+ if (! isset($this->id)) {
$this->id = $this->name;
- $this->attributes['id'] = $this->id;
+ }
+
+ $this->attributes['id'] = $this->id;
+ $this->attributes['name'] = $this->name;
+
+ if ($this->isRequired) {
+ $this->attributes['required'] = 'required';
+ }
+
+ if ($this->isReadonly) {
+ $this->attributes['readonly'] = 'readonly';
}
}
@@ -35,23 +62,4 @@ class FormComponent extends Component
{
$this->value = htmlspecialchars_decode($value, ENT_QUOTES);
}
-
- public function setRequired(string $value): void
- {
- $this->required = $value === 'true';
- unset($this->attributes['required']);
- if ($this->required) {
- $this->attributes['required'] = 'required';
- }
- }
-
- public function setReadonly(string $value): void
- {
- $this->readonly = $value === 'true';
- if ($this->readonly) {
- $this->attributes['readonly'] = 'readonly';
- } else {
- unset($this->attributes['readonly']);
- }
- }
}
diff --git a/app/Views/Components/Forms/Helper.php b/app/Views/Components/Forms/Helper.php
index f1702573..331c14a9 100644
--- a/app/Views/Components/Forms/Helper.php
+++ b/app/Views/Components/Forms/Helper.php
@@ -4,19 +4,18 @@ declare(strict_types=1);
namespace App\Views\Components\Forms;
-class Helper extends FormComponent
+use ViewComponents\Component;
+
+class Helper extends Component
{
- /**
- * @var 'default'|'error'
- */
- protected string $type = 'default';
+ // TODO: add type with error and show errors inline
public function render(): string
{
- $class = 'text-skin-muted';
+ $this->mergeClass('text-skin-muted');
return <<{$this->slot}
+ getStringifiedAttributes()}>{$this->slot}
HTML;
}
}
diff --git a/app/Views/Components/Forms/Input.php b/app/Views/Components/Forms/Input.php
index 6ba0ffdb..5ca7dfb9 100644
--- a/app/Views/Components/Forms/Input.php
+++ b/app/Views/Components/Forms/Input.php
@@ -6,26 +6,29 @@ namespace App\Views\Components\Forms;
class Input extends FormComponent
{
+ protected array $props = ['type'];
+
protected string $type = 'text';
public function render(): string
{
- $baseClass = 'w-full border-contrast rounded-lg focus:border-contrast border-3 focus:ring-accent focus-within:ring-accent ' . $this->class;
-
- $this->attributes['class'] = $baseClass;
+ $this->mergeClass('w-full border-contrast rounded-lg focus:border-contrast border-3 focus-within:ring-accent');
if ($this->type === 'file') {
- $this->attributes['class'] .= ' file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-skin-muted file:text-sm file:rounded-none file:border-none file:bg-highlight file:cursor-pointer';
+ $this->mergeClass('file:px-3 file:py-2 file:h-[40px] file:font-semibold file:text-skin-muted file:text-sm file:rounded-none file:border-none file:bg-highlight file:cursor-pointer');
} else {
- $this->attributes['class'] .= ' px-3 py-2';
+ $this->mergeClass('px-3 py-2');
}
- if ($this->readonly) {
- $this->attributes['class'] .= ' bg-base';
+ if ($this->isReadonly) {
+ $this->mergeClass('bg-base');
} else {
- $this->attributes['class'] .= ' bg-elevated';
+ $this->mergeClass('bg-elevated');
}
+ $this->attributes['type'] = $this->type;
+ $this->attributes['value'] = $this->value;
+
return form_input($this->attributes, old($this->name, $this->value));
}
}
diff --git a/app/Views/Components/Forms/Label.php b/app/Views/Components/Forms/Label.php
index 1c7ef093..808458ff 100644
--- a/app/Views/Components/Forms/Label.php
+++ b/app/Views/Components/Forms/Label.php
@@ -4,39 +4,38 @@ declare(strict_types=1);
namespace App\Views\Components\Forms;
+use App\Views\Components\Hint;
use ViewComponents\Component;
class Label extends Component
{
- protected ?string $for = null;
+ protected array $props = ['for', 'hint', 'isOptional'];
- protected ?string $hint = null;
+ protected array $casts = [
+ 'isOptional' => 'boolean',
+ ];
+
+ protected string $for;
+
+ protected string $hint = '';
protected bool $isOptional = false;
- public function setIsOptional(string $value): void
- {
- $this->isOptional = $value === 'true';
- }
-
public function render(): string
{
- $labelClass = 'text-sm font-semibold ' . $this->attributes['class'];
- unset($this->attributes['class']);
+ $this->mergeClass('text-sm font-semibold');
$optionalText = $this->isOptional ? '(' .
lang('Common.optional') .
') ' : '';
- $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1');
- unset($this->attributes['isOptional']);
- unset($this->attributes['hint']);
- unset($this->attributes['slot']);
-
- $attributes = stringify_attributes($this->attributes);
+ $hint = $this->hint === '' ? '' : (new Hint([
+ 'class' => 'ml-1',
+ 'slot' => $this->hint,
+ ]))->render();
return <<{$this->slot}{$optionalText}{$hint}
+ getStringifiedAttributes()}>{$this->slot}{$optionalText}{$hint}
HTML;
}
}
diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php
index 5c6bda64..b4c253ba 100644
--- a/app/Views/Components/Forms/MarkdownEditor.php
+++ b/app/Views/Components/Forms/MarkdownEditor.php
@@ -6,6 +6,8 @@ namespace App\Views\Components\Forms;
class MarkdownEditor extends FormComponent
{
+ protected array $props = ['disallowList'];
+
/**
* @var string[]
*/
@@ -18,18 +20,20 @@ class MarkdownEditor extends FormComponent
public function render(): string
{
- $editorClass = 'w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent ' . $this->class;
+ $this->mergeClass('w-full flex flex-col bg-elevated border-3 border-contrast rounded-lg overflow-hidden focus-within:ring-accent');
+ $wrapperClass = $this->attributes['class'];
$this->attributes['class'] = 'bg-elevated border-none focus:border-none focus:outline-none focus:ring-0 w-full h-full';
$this->attributes['rows'] = 6;
- $textarea = form_textarea($this->attributes, old($this->name, $this->value));
- $markdownIcon = icon(
- 'markdown-fill',
- [
- 'class' => 'mr-1 text-lg opacity-40',
- ]
+ $textarea = form_textarea(
+ $this->attributes,
+ old($this->name, $this->value)
);
+ $markdownIcon = icon('markdown-fill', [
+ 'class' => 'mr-1 text-lg opacity-40',
+ ]);
+
$translations = [
'write' => lang('Common.forms.editor.write'),
'preview' => lang('Common.forms.editor.preview'),
@@ -85,19 +89,19 @@ class MarkdownEditor extends FormComponent
$toolbarContent .= '';
foreach ($buttonsGroup as $button) {
if (! in_array($button['name'], $this->disallowList, true)) {
- $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:ring-accent focus:opacity-100">' . $button['icon'] . '' . $button['tag'] . '>';
+ $toolbarContent .= '<' . $button['tag'] . ' class="opacity-50 hover:opacity-100 focus:opacity-100">' . $button['icon'] . '' . $button['tag'] . '>';
}
}
$toolbarContent .= '
';
}
return <<
+
-
+