diff --git a/app/Libraries/ViewComponents/Component.php b/app/Libraries/ViewComponents/Component.php index 779d12c298ce530651bd9769a7677d502d4104b1..2aa0a9135b155ac1c0cee3aa0a103a3c959d5150 100644 --- a/app/Libraries/ViewComponents/Component.php +++ b/app/Libraries/ViewComponents/Component.php @@ -22,11 +22,12 @@ class Component implements ComponentInterface */ public function __construct(array $attributes) { + helper('viewcomponents'); + if ($attributes !== []) { $this->hydrate($attributes); } // overwrite default attributes if set - $this->attributes = array_merge($this->attributes, $attributes); } diff --git a/app/Libraries/ViewComponents/ComponentRenderer.php b/app/Libraries/ViewComponents/ComponentRenderer.php index ccac323990b45a2cd4392500110e370c89ac1efd..37a16eee1e30194931e01a584495686a839814e5 100644 --- a/app/Libraries/ViewComponents/ComponentRenderer.php +++ b/app/Libraries/ViewComponents/ComponentRenderer.php @@ -109,7 +109,7 @@ class ComponentRenderer private function renderPairedTags(string $output): string { $pattern = '/<\s*(?<name>[A-Z][A-Za-z0-9\.]*?)(?<attributes>[\s\S\=\'\"]*)>(?<slot>.*)<\/\s*\1\s*>/uUsm'; - + ini_set('pcre.backtrack_limit', '-1'); /* $matches[0] = full tags matched and all of its content $matches[name] = pascal cased tag name diff --git a/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php b/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php new file mode 100644 index 0000000000000000000000000000000000000000..1555f937f2aad2589894f24c2529697942d146b1 --- /dev/null +++ b/app/Libraries/ViewComponents/Helpers/viewcomponents_helper.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +if (! function_exists('flatten_attributes')) { + /** + * Stringify attributes for use in HTML tags. + * + * Helper function used to convert a string, array, or object of attributes to a string. + * + * @param mixed $attributes string, array, object + */ + function flatten_attributes($attributes, bool $js = false): string + { + $atts = ''; + + if ($attributes === null) { + return $atts; + } + + if (is_string($attributes)) { + return ' ' . $attributes; + } + + $attributes = (array) $attributes; + + foreach ($attributes as $key => $val) { + $atts .= ($js) ? $key . '=' . esc($val, 'js') . ',' : ' ' . $key . '="' . $val . '"'; + } + + return rtrim($atts, ','); + } +} diff --git a/app/Resources/js/modules/xml-editor.ts b/app/Resources/js/modules/xml-editor.ts index 81a8b254e240ff5b261f36e5899d6ebea8f31679..100a701b46dfdd351f817810475cd617c6e314b8 100644 --- a/app/Resources/js/modules/xml-editor.ts +++ b/app/Resources/js/modules/xml-editor.ts @@ -57,12 +57,17 @@ export class XMLEditor extends LitElement { static styles = css` .cm-wrap { - border: 1px solid #6b7280; + border-radius: 0.5rem; + overflow: hidden; + border: 3px solid #000000; background-color: #ffffff; } .cm-editor.cm-focused { outline: 2px solid transparent; - box-shadow: 0 0 0 1px #2563eb; + box-shadow: 0 0 0 2px #e7f9e4, 0 0 0 calc(4px) #009486; + } + .cm-gutters { + background-color: #ffffff !important; } `; diff --git a/app/Resources/styles/breadcrumb.css b/app/Resources/styles/breadcrumb.css index 0a89fe55b8f673b83698c2be32deae79a773d698..ac4a3fabb633a44b7fd2c86d1e6e5d7969be0286 100644 --- a/app/Resources/styles/breadcrumb.css +++ b/app/Resources/styles/breadcrumb.css @@ -1,5 +1,5 @@ .breadcrumb { - @apply inline-flex flex-wrap px-1 py-2 text-sm; + @apply inline-flex flex-wrap px-1 text-sm; } .breadcrumb-item + .breadcrumb-item::before { diff --git a/app/Resources/styles/choices.css b/app/Resources/styles/choices.css index 073342617d0930e70b784105d76aad6cb1216511..d9cffcb384cbb06eae28ddb49c3841b36e8263b5 100644 --- a/app/Resources/styles/choices.css +++ b/app/Resources/styles/choices.css @@ -138,8 +138,9 @@ } .choices__inner { - @apply p-2 bg-white border border-gray-700; + @apply p-2 bg-white border-black rounded-lg border-3; + box-shadow: 2px 2px 0 black; display: inline-block; vertical-align: top; width: 100%; @@ -158,11 +159,11 @@ } .is-open .choices__inner { - border-radius: 0; + @apply rounded-b-none; } .is-flipped.is-open .choices__inner { - border-radius: 0; + @apply rounded-t-none rounded-b-lg border-b-3; } .choices__list { @@ -172,9 +173,7 @@ } .choices__list--single { - @apply pr-4; - display: inline-block; - width: 100%; + @apply inline-block w-full pr-4; } [dir="rtl"] .choices__list--single { @@ -191,7 +190,7 @@ } .choices__list--multiple .choices__item { - @apply inline-block px-2 py-1 mb-1 mr-1 text-sm text-white align-middle bg-pine-600; + @apply inline-block px-2 py-1 mb-1 mr-1 text-sm text-white align-middle rounded bg-pine-500; word-break: break-all; box-sizing: border-box; @@ -216,12 +215,11 @@ } .choices__list--dropdown { + @apply z-50 border-2 border-black shadow-lg; visibility: hidden; - z-index: 1; position: absolute; width: 100%; background-color: #ffffff; - border: 1px solid #dddddd; top: 100%; margin-top: -1px; overflow: hidden; @@ -234,10 +232,11 @@ } .is-open .choices__list--dropdown { - border-color: #b7b7b7; + @apply border-t-0 rounded-b-lg; } .is-flipped .choices__list--dropdown { + @apply border-b-0 rounded-t-lg rounded-b-none border-t-3; top: auto; bottom: 100%; margin-top: 0; diff --git a/app/Resources/styles/radioBtn.css b/app/Resources/styles/radioBtn.css index 0abe1a6e6501b8d4616bff95b0d60da837223697..d882d606a918a7f80432bffcb36076f14a207025 100644 --- a/app/Resources/styles/radioBtn.css +++ b/app/Resources/styles/radioBtn.css @@ -1,26 +1,17 @@ @layer components { .form-radio-btn { - @apply absolute opacity-0; + @apply absolute mt-3 ml-3 border-black border-3 text-pine-500 focus:ring-2 focus:ring-pine-800; } .form-radio-btn:focus + label { - @apply ring; + @apply ring ring-pine-100; } .form-radio-btn + label { - @apply inline-block px-2 py-1 text-sm text-black bg-white border rounded cursor-pointer; - - &:hover { - @apply bg-pine-100; - } + @apply inline-block py-2 pl-8 pr-2 text-sm font-semibold text-gray-500 bg-white border-black rounded-lg cursor-pointer border-3; } .form-radio-btn:checked + label { - @apply text-white bg-pine-600; - - &::before { - @apply mr-2 text-pine-200; - content: "✓"; - } + @apply text-black border-pine-500; } } diff --git a/app/Resources/styles/switch.css b/app/Resources/styles/switch.css index 3d8a7f3d150ef0064e740d93871b444bee8680f5..b124f0f43f5d52ac54448bf80f8142458cc4787c 100644 --- a/app/Resources/styles/switch.css +++ b/app/Resources/styles/switch.css @@ -3,26 +3,37 @@ @apply absolute w-0 h-0 opacity-0; &:checked + .form-switch-slider { - @apply bg-pine-600; + @apply bg-pine-500; } &:focus + .form-switch-slider { - @apply ring; + @apply ring ring-offset-2 ring-pine-500 ring-offset-pine-100; } &:checked + .form-switch-slider::before { - @apply transform translate-x-5; + @apply transform translate-x-8; + } + + &:checked + .form-switch-slider::after { + @apply transform translate-x-1; + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='%23ffffff'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m10 15.172 9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z'/%3E%3C/svg%3E%0A"); } } .form-switch-slider { - @apply relative inset-0 flex-shrink-0 w-10 h-5 transition duration-200 bg-gray-400 rounded-full cursor-pointer; + @apply relative inset-0 flex-shrink-0 w-[72px] h-10 transition duration-200 bg-gray-400 border-black rounded-full cursor-pointer border-3; &::before { - @apply absolute w-4 h-4 transition duration-200 bg-white rounded-full ring-1 ring-black ring-opacity-5; + @apply absolute z-10 w-[28px] h-[28px] transition duration-200 bg-white rounded-full ring-1 ring-black ring-opacity-5 shadow; content: ""; - left: 2px; - bottom: 2px; + left: 3px; + bottom: 3px; + } + + &::after { + @apply absolute w-6 h-6 transition duration-150 transform translate-x-8 top-1 left-1; + + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24'%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3Cpath d='m12 10.586 4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z'/%3E%3C/svg%3E%0A"); } } } diff --git a/app/Views/Components/Forms/Field.php b/app/Views/Components/Forms/Field.php new file mode 100644 index 0000000000000000000000000000000000000000..10ec65da3282c87541a64fa0e08f6b2d21201291 --- /dev/null +++ b/app/Views/Components/Forms/Field.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +class Field extends FormComponent +{ + protected string $as = 'Input'; + + protected string $label = ''; + + protected ?string $helperText = null; + + protected ?string $hintText = null; + + public function render(): string + { + $helperText = $this->helperText === null ? '' : '<Forms.Helper>' . $this->helperText . '</Forms.Helper>'; + + $labelAttributes = [ + 'for' => $this->id, + 'isOptional' => $this->required ? 'false' : 'true', + ]; + if ($this->hintText) { + $labelAttributes['hint'] = $this->hintText; + } + $labelAttributes = stringify_attributes($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['helperText']); + unset($fieldComponentAttributes['hintText']); + + $fieldComponentAttributes = flatten_attributes($fieldComponentAttributes); + + return <<<HTML + <div class="flex flex-col {$this->class}"> + <Forms.Label {$labelAttributes}>{$this->label}</Forms.Label> + <Forms.{$this->as} {$fieldComponentAttributes} class="mb-1"/> + {$helperText} + </div> + HTML; + } +} diff --git a/app/Views/Components/Forms/FormComponent.php b/app/Views/Components/Forms/FormComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..785c0496255e1cba7113410a3713f51a730d8441 --- /dev/null +++ b/app/Views/Components/Forms/FormComponent.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +use ViewComponents\Component; + +class FormComponent extends Component +{ + protected ?string $id = null; + + protected string $name = ''; + + protected string $value = ''; + + protected bool $required = false; + + public function __construct($attributes) + { + parent::__construct($attributes); + + if ($this->id === null) { + $this->id = $this->name; + } + } + + public function setRequired(string $value): void + { + $this->required = $value === 'true'; + } +} diff --git a/app/Views/Components/Forms/Helper.php b/app/Views/Components/Forms/Helper.php new file mode 100644 index 0000000000000000000000000000000000000000..05174678c4f08b24689909afe8bb78c1113dcc3a --- /dev/null +++ b/app/Views/Components/Forms/Helper.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +class Helper extends FormComponent +{ + /** + * @var "default"|"error" + */ + protected string $type = 'default'; + + public function render(): string + { + $class = 'text-gray-600'; + + return <<<HTML + <small class="{$class} {$this->class}">{$this->slot}</small> + HTML; + } +} diff --git a/app/Views/Components/Forms/Input.php b/app/Views/Components/Forms/Input.php new file mode 100644 index 0000000000000000000000000000000000000000..7d771099773b23754405cfcfda7e7f6a7be4db6b --- /dev/null +++ b/app/Views/Components/Forms/Input.php @@ -0,0 +1,37 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +class Input extends FormComponent +{ + protected string $type = 'text'; + + public function render(): string + { + $class = 'px-3 py-2 rounded-lg border-3 focus:ring-2 focus:ring-pine-500 focus:ring-offset-2 focus:ring-offset-pine-100 ' . $this->class; + + if (session()->has('errors')) { + $error = session('errors')[$this->name]; + if ($error) { + $class .= ' border-red'; + } + } else { + $class .= ' border-black focus:border-black'; + } + + $data = [ + 'id' => $this->id, + 'name' => $this->name, + 'class' => $class, + 'type' => $this->type, + ]; + + if ($this->required) { + $data['required'] = 'required'; + } + + return form_input($data, old($this->name, $this->value)); + } +} diff --git a/app/Views/Components/Forms/Label.php b/app/Views/Components/Forms/Label.php index 6ff709d4633e583c489dfbea737aaf93513eaa82..609780f656a40dcf5dc00a2689ab49f3eed05595 100644 --- a/app/Views/Components/Forms/Label.php +++ b/app/Views/Components/Forms/Label.php @@ -8,16 +8,9 @@ use ViewComponents\Component; class Label extends Component { - /** - * @var array<string, string> - */ - protected array $attributes = [ - 'for' => '', - 'name' => '', - 'class' => '', - ]; + protected ?string $for = null; - protected string $hint = ''; + protected ?string $hint = null; protected bool $isOptional = false; @@ -28,14 +21,14 @@ class Label extends Component public function render(): string { - $labelClass = $this->attributes['class']; + $labelClass = 'text-sm ' . $this->attributes['class']; unset($this->attributes['class']); $attributes = stringify_attributes($this->attributes); $optionalText = $this->isOptional ? '<small class="ml-1 lowercase">(' . lang('Common.optional') . ')</small>' : ''; - $hint = $this->hint !== '' ? hint_tooltip($this->hint, 'ml-1') : ''; + $hint = $this->hint === null ? '' : hint_tooltip($this->hint, 'ml-1'); return <<<HTML <label class="{$labelClass}" {$attributes}>{$this->slot}{$optionalText}{$hint}</label> diff --git a/app/Views/Components/Forms/MarkdownEditor.php b/app/Views/Components/Forms/MarkdownEditor.php index febf2ebe044edf1a626bd7981efde116bb7bf628..4a729729c0775566ca0e2ab40dadf5fd5b5c5736 100644 --- a/app/Views/Components/Forms/MarkdownEditor.php +++ b/app/Views/Components/Forms/MarkdownEditor.php @@ -4,73 +4,68 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -use ViewComponents\Component; - -class MarkdownEditor extends Component +class MarkdownEditor extends FormComponent { public function render(): string { - $editorClass = 'w-full flex flex-col bg-white border border-gray-500 focus-within:ring-1 focus-within:ring-blue-600'; - if ($this->attributes['class'] !== '') { - $editorClass .= ' ' . $this->attributes['class']; - unset($this->attributes['class']); - } + $editorClass = 'w-full flex flex-col bg-white border-3 border-black rounded-lg overflow-hidden focus-within:ring-2 focus-within:ring-offset-2 focus-withing:ring-offset-pine-100 focus-within:ring-pine-500 ' . $this->class; + + $this->attributes['class'] = 'border-none outline-none focus:border-none focus:outline-none focus:ring-0 w-full h-full'; + $this->attributes['rows'] = 6; - $this->attributes['class'] = 'border-none outline-none focus:border-none focus:outline-none w-full h-full'; + $textarea = form_textarea($this->attributes, old($this->name, $this->value, false)); + $icons = [ + 'heading' => icon('heading'), + 'bold' => icon('bold'), + 'italic' => icon('italic'), + 'list-unordered' => icon('list-unordered'), + 'list-ordered' => icon('list-ordered'), + 'quote' => icon('quote'), + 'link' => icon('link'), + 'image-add' => icon('image-add'), + 'markdown' => icon( + 'markdown', + 'mr-1 text-lg text-gray-400' + ), + ]; + $translations = [ + 'write' => lang('Common.forms.editor.write'), + 'preview' => lang('Common.forms.editor.preview'), + 'help' => lang('Common.forms.editor.help'), + ]; - return '<div class="' . $editorClass . '">' . - '<header class="sticky top-0 z-20 flex flex-wrap justify-between bg-white border-b border-gray-500">' . - '<markdown-write-preview for="' . $this->attributes['id'] . '" class="relative inline-flex h-8">' . - '<button type="button" slot="write" class="px-2 font-semibold focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang( - 'Common.forms.editor.write' - ) . '</button>' . - '<button type="button" slot="preview" class="px-2 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">' . lang( - 'Common.forms.editor.preview' - ) . '</button>' . - '</markdown-write-preview>' . - '<markdown-toolbar for="' . $this->attributes['id'] . '" class="flex gap-4 px-2 py-1">' . - '<div class="inline-flex text-2xl gap-x-1">' . - '<md-header class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'heading' - ) . '</md-header>' . - '<md-bold class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'bold' - ) . '</md-bold>' . - '<md-italic class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'italic' - ) . '</md-italic>' . - '</div>' . - '<div class="inline-flex text-2xl gap-x-1">' . - '<md-unordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'list-unordered' - ) . '</md-unordered-list>' . - '<md-ordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'list-ordered' - ) . '</md-ordered-list>' . - '</div>' . - '<div class="inline-flex text-2xl gap-x-1">' . - '<md-quote class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'quote' - ) . '</md-quote>' . - '<md-link class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'link' - ) . '</md-link>' . - '<md-image class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">' . icon( - 'image-add' - ) . '</md-image>' . - '</div>' . - '</markdown-toolbar>' . - '</header>' . - '<div class="relative">' . - form_textarea($this->attributes, $this->slot) . - '<markdown-preview for="' . $this->attributes['id'] . '" class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white"></markdown-preview>' . - '</div>' . - '<footer class="flex px-2 py-1 bg-gray-100 border-t">' . - '<a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-gray-500 hover:text-gray-700" target="_blank" rel="noopener noreferrer">' . icon( - 'markdown', - 'mr-1 text-lg text-gray-400' - ) . lang('Common.forms.editor.help') . '</a>' . - '</footer>' . - '</div>'; + return <<<HTML + <div class="{$editorClass}"> + <header class="sticky top-0 z-20 flex flex-wrap justify-between bg-white border-b border-black"> + <markdown-write-preview for="{$this->id}" class="relative inline-flex h-8"> + <button type="button" slot="write" class="px-2 font-semibold focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">{$translations['write']}</button> + <button type="button" slot="preview" class="px-2 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-pine-600">{$translations['preview']}</button> + </markdown-write-preview> + <markdown-toolbar for=" {$this->id} " class="flex gap-4 px-2 py-1"> + <div class="inline-flex text-2xl gap-x-1"> + <md-header class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['heading']}</md-header> + <md-bold class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['bold']}</md-bold> + <md-italic class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['italic']}</md-italic> + </div> + <div class="inline-flex text-2xl gap-x-1"> + <md-unordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['list-unordered']}</md-unordered-list> + <md-ordered-list class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['list-ordered']}</md-ordered-list> + </div> + <div class="inline-flex text-2xl gap-x-1"> + <md-quote class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['quote']}</md-quote> + <md-link class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['link']}</md-link> + <md-image class="opacity-50 hover:opacity-100 focus:outline-none focus:ring-2 focus:opacity-100 focus:ring-pine-600">{$icons['image-add']}</md-image> + </div> + </markdown-toolbar> + </header> + <div class="relative"> + {$textarea} + <markdown-preview for=" {$this->id} " class="absolute top-0 left-0 hidden w-full h-full p-2 overflow-y-auto prose bg-gray-50" showClass="bg-white" /> + </div> + <footer class="flex px-2 py-1 bg-gray-100 border-t"> + <a href="https://commonmark.org/help/" class="inline-flex items-center text-xs font-semibold text-gray-500 hover:text-gray-700" target="_blank" rel="noopener noreferrer">{$icons['markdown']}{$translations['help']}</a> + </footer> + </div> + HTML; } } diff --git a/app/Views/Components/Forms/RadioButton.php b/app/Views/Components/Forms/RadioButton.php new file mode 100644 index 0000000000000000000000000000000000000000..222afa1f2776b72bd1dd78c57657c56884228f3f --- /dev/null +++ b/app/Views/Components/Forms/RadioButton.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +/** + * Form Checkbox Switch + * + * Abstracts form_label to stylize it as a switch toggle + */ +class RadioButton extends FormComponent +{ + protected bool $isChecked = false; + + public function setIsChecked(string $value): void + { + $this->isChecked = $value === 'true'; + } + + public function render(): string + { + $radioInput = form_radio( + [ + 'id' => $this->value, + 'name' => $this->name, + 'class' => 'form-radio-btn', + ], + $this->value, + old($this->name) ? old($this->name) === $this->value : $this->isChecked, + ); + + return <<<HTML + <div> + {$radioInput} + <label for="{$this->value}">{$this->slot}</label> + </div> + HTML; + } +} diff --git a/app/Views/Components/Forms/Section.php b/app/Views/Components/Forms/Section.php new file mode 100644 index 0000000000000000000000000000000000000000..38adec8703533a8c4413f6854e9e54fb47971f6f --- /dev/null +++ b/app/Views/Components/Forms/Section.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace App\Views\Components\Forms; + +class Section extends FormComponent +{ + protected string $title = ''; + + protected ?string $subtitle = null; + + public function render(): string + { + $subtitle = $this->subtitle === null ? '' : '<p class="text-sm text-gray-600 clear-left">' . $this->subtitle . '</p>'; + + return <<<HTML + <fieldset class="w-full max-w-xl p-8 bg-white border-2 border-black rounded-xl {$this->class}"> + <Heading tagName="legend" class="float-left">{$this->title}</Heading> + {$subtitle} + <div class="flex flex-col gap-4 py-4 clear-left">{$this->slot}</div> + </fieldset> + HTML; + } +} diff --git a/app/Views/Components/Forms/Select.php b/app/Views/Components/Forms/Select.php index 050c3cf334e82101a3cf971ba225cdaf124abd12..983a6be08d2d450fecab3f9bcc6b8f797a08ade4 100644 --- a/app/Views/Components/Forms/Select.php +++ b/app/Views/Components/Forms/Select.php @@ -4,28 +4,28 @@ declare(strict_types=1); namespace App\Views\Components\Forms; -use ViewComponents\Component; - -class MultiSelect extends Component +class Select extends FormComponent { /** * @var array<string, string> */ protected array $options = []; - /** - * @var string[] - */ - protected array $selected = []; + protected string $selected; + + public function setOptions(string $value): void + { + // dd(json_decode(html_entity_decode(html_entity_decode($value)), true)); + $this->options = json_decode(html_entity_decode($value), true); + } public function render(): string { $defaultAttributes = [ - 'data-class' => $this->attributes['class'], - 'multiple' => 'multiple', + 'data-class' => 'border-3 rounded-lg ' . $this->class, ]; $extra = array_merge($defaultAttributes, $this->attributes); - return form_dropdown($this->attributes['name'], $this->options, $this->selected, $extra); + return form_dropdown($this->name, $this->options, $this->selected !== '' ? [$this->selected] : [], $extra); } } diff --git a/app/Views/Components/Heading.php b/app/Views/Components/Heading.php index 8333be2c0ca25a170f33ed211c71209cb0e36a19..8cfa379208ae1f1360cd7ab0dfb415a305411b6d 100644 --- a/app/Views/Components/Heading.php +++ b/app/Views/Components/Heading.php @@ -4,12 +4,11 @@ declare(strict_types=1); namespace App\Views\Components; -use Exception; use ViewComponents\Component; class Heading extends Component { - protected string $level = ''; + protected string $tagName = 'div'; /** * @var "small"|"base"|"large" @@ -18,21 +17,16 @@ class Heading extends Component public function render(): string { - if ($this->level === '') { - throw new Exception('level property must be set for Heading component.'); - } - $sizeClasses = [ 'small' => 'tracking-wide text-base', 'base' => 'text-xl', 'large' => 'text-3xl', ]; - $class = 'relative z-10 font-bold text-pine-800 font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-pine-100 before:-z-10 ' . $sizeClasses[$this->size]; - $level = $this->level; + $class = $this->class . ' relative z-10 font-bold text-pine-800 font-display before:w-full before:absolute before:h-1/2 before:left-0 before:bottom-0 before:rounded-full before:bg-pine-100 before:-z-10 ' . $sizeClasses[$this->size]; return <<<HTML - <h{$level} class="{$class}">{$this->slot}</h{$level}> + <{$this->tagName} class="{$class}">{$this->slot}</{$this->tagName}> HTML; } } diff --git a/phpstan.neon b/phpstan.neon index 8db6aaed65e126eda28e9608a3cf8f54a5c66c27..88e1055aef8620445610761e9c1b9d49eb196315 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -33,3 +33,4 @@ parameters: paths: - app/Helpers - app/Common.php + - app/Libraries/ViewComponents/Helpers diff --git a/tailwind.config.js b/tailwind.config.js index b7cad2d8c73bdb4eb5e9694ce89516f31225a83f..cb142c44e666d00df04a72e667db274e547849ab 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -51,6 +51,12 @@ module.exports = { zIndex: { "-10": "-10", }, + borderWidth: { + 3: "3px", + }, + ringWidth: { + 3: "3px", + }, }, }, variants: {}, diff --git a/themes/cp_admin/_layout.php b/themes/cp_admin/_layout.php index 29f811561c219f1a92da0b5935490bcc3daea666..8da7748788b93eecae7e68eb2c84b6b65a044400 100644 --- a/themes/cp_admin/_layout.php +++ b/themes/cp_admin/_layout.php @@ -79,21 +79,17 @@ ]) ?> </footer> </aside> - <main class="holy-grail__main"> - <header class="bg-white"> - <div class="container flex flex-wrap items-end justify-between px-2 py-10 mx-auto md:px-12 gap-y-6 gap-x-6"> - <div class="flex flex-col"> - <?= render_breadcrumb('text-gray-800 text-xs') ?> + <main class="relative holy-grail__main"> + <header class="flex-col py-10 bg-white"> + <div class="container mx-auto"> + <?= render_breadcrumb('text-gray-800 text-xs') ?> + <div class="flex justify-between"> <div class="flex flex-wrap items-center"> - <Heading level="1" size="large"><?= $this->renderSection( - 'pageTitle', - ) ?></Heading> + <Heading tagName="h1" size="large"><?= $this->renderSection('pageTitle') ?></Heading> <?= $this->renderSection('headerLeft') ?> </div> + <div class="flex gap-1"><?= $this->renderSection('headerRight') ?></div> </div> - <div class="flex flex-wrap"><?= $this->renderSection( - 'headerRight', - ) ?></div> </div> </header> <div class="container px-2 py-8 mx-auto md:px-12"> diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index 9fc0009938ee469fa42e0ee6e71055e3ea35f614..717409bd34dee23788d5697ad7fe899faf5e205a 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -12,373 +12,216 @@ <?= lang('Podcast.edit') ?> <?= $this->endSection() ?> +<?= $this->section('headerRight') ?> +<Button variant="primary" type="submit" form="podcast-edit-form"><?= lang('Podcast.form.submit_edit') ?></Button> +<?= $this->endSection() ?> <?= $this->section('content') ?> <?= form_open_multipart((string) route_to('podcast-edit', $podcast->id), [ + 'id' => 'podcast-edit-form', 'method' => 'post', 'class' => 'flex flex-col', ]) ?> <?= csrf_field() ?> -<?= form_section( - lang('Podcast.form.identity_section_title'), - lang('Podcast.form.identity_section_subtitle'), -) ?> - -<?= form_label(lang('Podcast.form.image'), 'image') ?> -<img src="<?= $podcast->image->thumbnail_url ?>" alt="<?= $podcast->title ?>" class="object-cover w-32 h-32" /> -<?= form_input([ - 'id' => 'image', - 'name' => 'image', - 'class' => 'form-input', - 'type' => 'file', - 'accept' => '.jpg,.jpeg,.png', -]) ?> - - -<small class="mb-4 text-gray-600"><?= lang( - 'Common.forms.image_size_hint', -) ?></small> - -<?= form_label(lang('Podcast.form.title'), 'title') ?> - -<?= form_input([ - 'id' => 'title', - 'name' => 'title', - 'class' => 'form-input mb-1', - 'value' => old('title', $podcast->title), - 'required' => 'required', -]) ?> - -<span class="mb-4 text-sm"><?= $podcast->link ?></span> - -<?= form_fieldset('', [ - 'class' => 'mb-4', -]) ?> - -<legend><?= lang('Podcast.form.type.label') . - hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?> -</legend> -<?= form_radio( - [ - 'id' => 'episodic', - 'name' => 'type', - 'class' => 'form-radio-btn', - ], - 'episodic', - old('type') ? old('type') === 'episodic' : $podcast->type === 'episodic', - ) ?> -<label for="episodic"><?= lang('Podcast.form.type.episodic') ?></label> -<?= form_radio( - [ - 'id' => 'serial', - 'name' => 'type', - 'class' => 'form-radio-btn', - ], - 'serial', - old('type') ? old('type') === 'serial' : $podcast->type === 'serial', - ) ?> -<label for="serial"><?= lang('Podcast.form.type.serial') ?></label> -<?= form_fieldset_close() ?> - -<div class="mb-4"> - <Forms.Label for="description"><?= lang('Podcast.form.description') ?></Forms.Label> - <Forms.MarkdownEditor id="description" name="description" required="required"><?= old('description', $podcast->description_markdown, false) ?></Forms.MarkdownEditor> -</div> - -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.classification_section_title'), - lang('Podcast.form.classification_section_subtitle'), - ) ?> - -<?= form_label(lang('Podcast.form.language'), 'language') ?> - -<?= form_dropdown( - 'language', - $languageOptions, - [old('language', $podcast->language_code)], - [ - 'id' => 'language', - 'class' => 'form-select mb-4', - 'required' => 'required', - ], - ) ?> - -<?= form_label(lang('Podcast.form.category'), 'category') ?> -<?= form_dropdown( - 'category', - $categoryOptions, - [old('category', $podcast->category_id)], - [ - 'id' => 'category', - 'class' => 'form-select mb-4', - 'required' => 'required', - ], - ) ?> - -<?= form_label( - lang('Podcast.form.other_categories'), - 'other_categories', - [], - '', - true, - ) ?> - -<Forms.MultiSelect - id="other_categories" - name="other_categories[]" - class="mb-4" - data-max-item-count="2" - selected="<?= json_encode(old('other_categories', $podcast->other_categories_ids)) ?>" - options="<?= htmlspecialchars(json_encode($categoryOptions)) ?>" /> - -<?= form_fieldset('', [ - 'class' => 'mb-4', -]) ?> -<legend><?= lang('Podcast.form.parental_advisory.label') . - hint_tooltip(lang('Podcast.form.parental_advisory.hint'), 'ml-1') ?></legend> -<?= form_radio( - [ - 'id' => 'undefined', - 'name' => 'parental_advisory', - 'class' => 'form-radio-btn', - ], - 'undefined', - old('parental_advisory') - ? old('parental_advisory') === 'undefined' - : $podcast->parental_advisory === null, - ) ?> - -<label for="undefined"><?= lang( - 'Podcast.form.parental_advisory.undefined', - ) ?></label> -<?= form_radio( - [ - 'id' => 'clean', - 'name' => 'parental_advisory', - 'class' => 'form-radio-btn', - ], - 'clean', - old('parental_advisory') - ? old('parental_advisory') === 'clean' - : $podcast->parental_advisory === 'clean', - ) ?> - -<label for="clean"><?= lang( - 'Podcast.form.parental_advisory.clean', - ) ?></label> -<?= form_radio( - [ - 'id' => 'explicit', - 'name' => 'parental_advisory', - 'class' => 'form-radio-btn', - ], - 'explicit', - old('parental_advisory') - ? old('parental_advisory') === 'explicit' - : $podcast->parental_advisory === 'explicit', - ) ?> - -<label for="explicit"><?= lang( - 'Podcast.form.parental_advisory.explicit', - ) ?></label> -<?= form_fieldset_close() ?> -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.author_section_title'), - lang('Podcast.form.author_section_subtitle'), - ) ?> - -<?= form_label( - lang('Podcast.form.owner_name'), - 'owner_name', - [], - lang('Podcast.form.owner_name_hint'), - ) ?> - -<?= form_input([ - 'id' => 'owner_name', - 'name' => 'owner_name', - 'class' => 'form-input mb-4', - 'value' => old('owner_name', $podcast->owner_name), - 'required' => 'required', -]) ?> - -<?= form_label( - lang('Podcast.form.owner_email'), - 'owner_email', - [], - lang('Podcast.form.owner_email_hint'), -) ?> - -<?= form_input([ - 'id' => 'owner_email', - 'name' => 'owner_email', - 'class' => 'form-input mb-4', - 'value' => old('owner_email', $podcast->owner_email), - 'type' => 'email', - 'required' => 'required', -]) ?> - -<?= form_label( - lang('Podcast.form.publisher'), - 'publisher', - [], - lang('Podcast.form.publisher_hint'), - true, -) ?> - -<?= form_input([ - 'id' => 'publisher', - 'name' => 'publisher', - 'class' => 'form-input mb-4', - 'value' => old('publisher', $podcast->publisher), -]) ?> - -<?= form_label(lang('Podcast.form.copyright'), 'copyright', [], '', true) ?> - -<?= form_input([ - 'id' => 'copyright', - 'name' => 'copyright', - 'class' => 'form-input mb-4', - 'value' => old('copyright', $podcast->copyright), -]) ?> - -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.location_section_title'), - lang('Podcast.form.location_section_subtitle'), -) ?> - -<?= form_label( - lang('Podcast.form.location_name'), - 'location_name', - [], - lang('Podcast.form.location_name_hint'), - true, -) ?> - -<?= form_input([ - 'id' => 'location_name', - 'name' => 'location_name', - 'class' => 'form-input mb-4', - 'value' => old('location_name', $podcast->location_name), -]) ?> - -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.monetization_section_title'), - lang('Podcast.form.monetization_section_subtitle'), -) ?> - -<?= form_label( - lang('Podcast.form.payment_pointer'), - 'payment_pointer', - [], - lang('Podcast.form.payment_pointer_hint'), - true, -) ?> - -<?= form_input([ - 'id' => 'payment_pointer', - 'name' => 'payment_pointer', - 'class' => 'form-input mb-4', - 'value' => old('payment_pointer', $podcast->payment_pointer), -]) ?> - -<?= form_label(lang('Podcast.form.partnership')) ?> -<div class="flex flex-col mb-4 gap-x-2 gap-y-4 md:flex-row"> - <div class="flex flex-col flex-shrink w-32"> - <?= form_label( - lang('Podcast.form.partner_id'), - 'partner_id', - [], - lang('Podcast.form.partner_id_hint'), - true, -) ?> - <?= form_input([ - 'id' => 'partner_id', - 'name' => 'partner_id', - 'class' => 'form-input w-full', - 'value' => old('partner_id', $podcast->partner_id), - ]) ?> +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.identity_section_title') ?>" + subtitle="<?= lang('Podcast.form.identity_section_subtitle') ?>" > + +<Forms.Field + name="image" + label="<?= lang('Podcast.form.image') ?>" + helperText="<?= lang('Common.forms.image_size_hint') ?>" + type="file" + accept=".jpg,.jpeg,.png" /> + +<Forms.Field + name="title" + label="<?= lang('Podcast.form.title') ?>" + helperText="<?= $podcast->link ?>" + value="<?= $podcast->title ?>" + required="true" /> + +<Forms.Field + as="MarkdownEditor" + name="description" + label="<?= lang('Podcast.form.description') ?>" + value="<?= $podcast->title ?>" + required="true" /> + +<fieldset> + <legend><?= lang('Podcast.form.type.label') . + hint_tooltip(lang('Podcast.form.type.hint'), 'ml-1') ?></legend> + <div class="flex gap-2"> + <Forms.RadioButton + value="episodic" + name="type" + isChecked="<?= $podcast->type === 'episodic' ? 'true' : 'false' ?>" ><?= lang('Podcast.form.type.episodic') ?></Forms.RadioButton> + <Forms.RadioButton + value="serial" + name="type" + isChecked="<?= $podcast->type === 'serial' ? 'true' : 'false' ?>" ><?= lang('Podcast.form.type.serial') ?></Forms.RadioButton> </div> - <div class="flex flex-col flex-1"> - <?= form_label( - lang('Podcast.form.partner_link_url'), - 'partner_link_url', - [], - lang('Podcast.form.partner_link_url_hint'), - true, - ) ?> - <?= form_input([ - 'id' => 'partner_link_url', - 'name' => 'partner_link_url', - 'class' => 'form-input w-full', - 'value' => old('partner_link_url', $podcast->partner_link_url), - ]) ?> +</fieldset> + +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.classification_section_title') ?>" + subtitle="<?= lang('Podcast.form.classification_section_subtitle') ?>" > + + <Forms.Field + as="Select" + name="language" + label="<?= lang('Podcast.form.language') ?>" + selected="<?= $podcast->language_code ?>" + required="true" + options="<?= esc(json_encode($languageOptions)) ?>" /> + + <Forms.Field + as="Select" + name="category" + label="<?= lang('Podcast.form.category') ?>" + selected="<?= $podcast->category_id ?>" + required="true" + options="<?= esc(json_encode($categoryOptions)) ?>" /> + + <Forms.Field + as="MultiSelect" + name="other_categories[]" + label="<?= lang('Podcast.form.other_categories') ?>" + selected="<?= json_encode(old('other_categories', $podcast->other_categories_ids)) ?>" + data-max-item-count="2" + options="<?= esc(json_encode($categoryOptions)) ?>" /> + + <fieldset class="mb-4"> + <legend><?= lang('Podcast.form.parental_advisory.label') . + hint_tooltip(lang('Podcast.form.parental_advisory.hint'), 'ml-1') ?></legend> + <div class="flex gap-2"> + <Forms.RadioButton + value="undefined" + name="parental_advisory" + isChecked="<?= $podcast->parental_advisory === null ? 'true' : 'false' ?>" ><?= lang('Podcast.form.parental_advisory.undefined') ?></Forms.RadioButton> + <Forms.RadioButton + value="clean" + name="parental_advisory" + isChecked="<?= $podcast->parental_advisory === 'clean' ? 'true' : 'false' ?>" ><?= lang('Podcast.form.parental_advisory.clean', ) ?></Forms.RadioButton> + <Forms.RadioButton + value="explicit" + name="parental_advisory" + isChecked="<?= $podcast->parental_advisory === 'explicit' ? 'true' : 'false' ?>" ><?= lang('Podcast.form.parental_advisory.explicit', ) ?></Forms.RadioButton> + </div> + </fieldset> +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.author_section_title') ?>" + subtitle="<?= lang('Podcast.form.author_section_subtitle') ?>" > + +<Forms.Field + name="owner_name" + label="<?= lang('Podcast.form.owner_name') ?>" + value="<?= $podcast->owner_name ?>" + hintText="<?= lang('Podcast.form.owner_name_hint') ?>" + required="true" /> + +<Forms.Field + name="owner_email" + type="email" + label="<?= lang('Podcast.form.owner_email') ?>" + value="<?= $podcast->owner_email ?>" + hintText="<?= lang('Podcast.form.owner_email_hint') ?>" + required="true" /> + +<Forms.Field + name="publisher" + label="<?= lang('Podcast.form.publisher') ?>" + value="<?= $podcast->publisher ?>" + hintText="<?= lang('Podcast.form.publisher_hint') ?>" /> + +<Forms.Field + name="copyright" + label="<?= lang('Podcast.form.copyright') ?>" + value="<?= $podcast->copyright ?>" /> + +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.location_section_title') ?>" + subtitle="<?= lang('Podcast.form.location_section_subtitle') ?>" > + +<Forms.Field + name="location_name" + label="<?= lang('Podcast.form.location_name') ?>" + value="<?= $podcast->location_name ?>" + hintText="<?= lang('Podcast.form.location_name_hint') ?>" /> + +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.monetization_section_title') ?>" + subtitle="<?= lang('Podcast.form.monetization_section_subtitle') ?>" > + +<Forms.Field + name="payment_pointer" + label="<?= lang('Podcast.form.payment_pointer') ?>" + value="<?= $podcast->payment_pointer ?>" + hintText="<?= lang('Podcast.form.payment_pointer_hint') ?>" /> + +<fieldset class="flex flex-col items-start p-4 bg-gray-100 rounded"> + <Heading tagName="legend" class="float-left" size="small"><?= lang('Podcast.form.partnership') ?></Heading> + <div class="flex flex-col w-full clear-left gap-x-2 gap-y-4 md:flex-row"> + <div class="flex flex-col flex-shrink w-32"> + <Forms.Label for="partner_id" hint="<?= lang('Podcast.form.partner_id_hint') ?>" isOptional="true"><?= lang('Podcast.form.partner_id') ?></Forms.Label> + <Forms.Input name="partner_id" value="<?= $podcast->partner_id ?>" /> + </div> + <div class="flex flex-col flex-1"> + <Forms.Label for="partner_link_url" hint="<?= lang('Podcast.form.partner_link_url_hint') ?>" isOptional="true"><?= lang('Podcast.form.partner_link_url') ?></Forms.Label> + <Forms.Input name="partner_link_url" value="<?= $podcast->partner_link_url ?>" /> + </div> </div> - <div class="flex flex-col flex-1"> - <?= form_label( - lang('Podcast.form.partner_image_url'), - 'partner_image_url', - [], - lang('Podcast.form.partner_image_url_hint'), - true, - ) ?> - <?= form_input([ - 'id' => 'partner_image_url', - 'name' => 'partner_image_url', - 'class' => 'form-input w-full', - 'value' => old('partner_image_url', $podcast->partner_image_url), - ]) ?> + <div class="flex flex-col w-full mt-2"> + <Forms.Label for="partner_image_url" hint="<?= lang('Podcast.form.partner_image_url_hint') ?>" isOptional="true"><?= lang('Podcast.form.partner_image_url') ?></Forms.Label> + <Forms.Input name="partner_image_url" value="<?= $podcast->partner_image_url ?>" /> </div> -</div> -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.advanced_section_title'), - lang('Podcast.form.advanced_section_subtitle'), - ) ?> - -<?= form_label( - lang('Podcast.form.custom_rss'), - 'custom_rss', - [], - lang('Podcast.form.custom_rss_hint'), - true, - ) ?> -<Forms.XMLEditor id="custom_rss" name="custom_rss"><?= old('custom_rss', $podcast->custom_rss_string, false) ?></Forms.XMLEditor> - -<?= form_section_close() ?> - -<?= form_section( - lang('Podcast.form.status_section_title'), - lang('Podcast.form.status_section_subtitle'), - ) ?> - -<Forms.Toggler class="mb-2" id="lock" name="lock" value="yes" checked="<?= old('complete', $podcast->is_locked) ?>" hint="<?= lang('Podcast.form.lock_hint') ?>"> - <?= lang('Podcast.form.lock') ?> -</Forms.Toggler> -<Forms.Toggler class="mb-2" id="block" name="block" value="yes" checked="<?= old('complete', $podcast->is_blocked) ?>"> - <?= lang('Podcast.form.block') ?> -</Forms.Toggler> -<Forms.Toggler id="complete" name="complete" value="yes" checked="<?= old('complete', $podcast->is_completed) ?>"> - <?= lang('Podcast.form.complete') ?> -</Forms.Toggler> - -<?= form_section_close() ?> - -<Button variant="primary" type="submit" class="self-end"> -<?= lang('Podcast.form.submit_edit') ?> -</Button> +</fieldset> +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.advanced_section_title') ?>" + subtitle="<?= lang('Podcast.form.advanced_section_subtitle') ?>" > + +<Forms.Field + as="XMLEditor" + name="custom_rss" + label="<?= lang('Podcast.form.custom_rss') ?>" + value="<?= $podcast->custom_rss_string ?>" + hintText="<?= lang('Podcast.form.custom_rss_hint') ?>" /> + +</Forms.Section> + +<Forms.Section + class="mb-8" + title="<?= lang('Podcast.form.status_section_title') ?>" + subtitle="<?= lang('Podcast.form.status_section_subtitle') ?>" > + <Forms.Toggler class="mb-2" id="lock" name="lock" value="yes" checked="<?= old('complete', $podcast->is_locked) ?>" hint="<?= lang('Podcast.form.lock_hint') ?>"> + <?= lang('Podcast.form.lock') ?> + </Forms.Toggler> + <Forms.Toggler class="mb-2" id="block" name="block" value="yes" checked="<?= old('complete', $podcast->is_blocked) ?>"> + <?= lang('Podcast.form.block') ?> + </Forms.Toggler> + <Forms.Toggler id="complete" name="complete" value="yes" checked="<?= old('complete', $podcast->is_completed) ?>"> + <?= lang('Podcast.form.complete') ?> + </Forms.Toggler> +</Forms.Section> <?= form_close() ?> diff --git a/themes/cp_admin/podcast/latest_episodes.php b/themes/cp_admin/podcast/latest_episodes.php index bd4c78c5227aea2f221198a56d104cb9e1632423..540f993740a74d0d6943ef22dd0b45a803356e18 100644 --- a/themes/cp_admin/podcast/latest_episodes.php +++ b/themes/cp_admin/podcast/latest_episodes.php @@ -1,6 +1,6 @@ <section class="flex flex-col"> <header class="flex justify-between py-2"> - <Heading level="2"><?= lang('Podcast.latest_episodes') ?></Heading> + <Heading tagName="h2"><?= lang('Podcast.latest_episodes') ?></Heading> <a href="<?= route_to( 'episode-list', $podcast->id,