diff --git a/src/Element.php b/src/Element.php index c5da44c..2fa8678 100644 --- a/src/Element.php +++ b/src/Element.php @@ -5,7 +5,9 @@ use Stringable; -class Element implements Stringable +use Eightfold\HtmlBuilder\PropertyInterface; + +class Element implements Stringable, PropertyInterface { /** * The following attributes will be placed in this order if used in an diff --git a/src/Forms/Select.php b/src/Forms/Select.php index 84be7e5..d9ab4db 100644 --- a/src/Forms/Select.php +++ b/src/Forms/Select.php @@ -6,16 +6,28 @@ use Stringable; use Eightfold\HtmlBuilder\Element; +use Eightfold\HtmlBuilder\PropertyInterface; use Eightfold\HtmlBuilder\Forms\SelectType; class Select implements Stringable { + // phpcs:disable + private string $warningId { + get => $this->name . '-warning'; + } + // phpcs:enable + /** * @var string[] */ private array $wrapperProperties = []; + /** + * @var string[] + */ + private array $labelProperties = []; + private SelectType $type = SelectType::Dropdown; /** @@ -26,9 +38,10 @@ public static function create( string|Stringable $label, string|Stringable $name, array $options, - string|array $selected = [] + string|array $selected = [], + string|PropertyInterface $warningMessage = '' ): self { - return new self($label, $name, $options, $selected); + return new self($label, $name, $options, $selected, $warningMessage); } /** @@ -39,7 +52,8 @@ final private function __construct( private readonly string|Stringable $label, private readonly string|Stringable $name, private readonly array $options, - private string|array $selected = [] + private readonly string|array $selected = [], + private readonly string|PropertyInterface $warningMessage = '' ) { } @@ -49,6 +63,12 @@ public function wrapperProps(string ...$properties): self return $this; } + public function labelProps(string ...$properties): self + { + $this->labelProperties = $properties; + return $this; + } + public function dropdown(): self { $this->type = SelectType::Dropdown; @@ -69,7 +89,7 @@ public function checkbox(): self private function hasSelected(): bool { - $selected = $this->selected(); + $selected = self::selected(); if (is_string($selected) and strlen($selected) > 0) { return true; @@ -100,22 +120,37 @@ private function selected(): string|array private function isSelected(string $value): bool { - if ($this->hasSelected() === false) { + if (self::hasSelected() === false) { return false; } - if (is_array($this->selected())) { - return in_array($value, $this->selected()); + if (is_array(self::selected())) { + return in_array($value, self::selected()); + } + return $value === self::selected(); + } + + private function warningMessage(): string|PropertyInterface + { + if ($this->warningMessage === '') { + return ''; + } + + if (is_string($this->warningMessage)) { + return Element::p($this->warningMessage)->props( + 'id ' . $this->warningId + ); } - return $value === $this->selected(); + + return $this->warningMessage->prop('id ' . $this->warningId); } public function __toString(): string { if ($this->type === SelectType::Dropdown) { - return (string) $this->selectDropdown(); + return (string) self::selectDropdown(); } - return (string) $this->selectOther(); + return (string) self::selectOther(); } private function selectDropdown(): Element @@ -124,19 +159,26 @@ private function selectDropdown(): Element foreach ($this->options as $value => $content) { $value = (string) $value; $option = Element::option($content)->props('value ' . $value); - if ($this->isSelected($value)) { + if (self::isSelected($value)) { $option = $option->prop('selected selected'); } $elements[] = $option; } + $input = Element::select( + ...$elements + )->props('id ' . $this->name, 'name ' . $this->name); + if (self::warningMessage() !== '') { + $input = $input->prop('aria-invalid true'); + $input = $input->prop('aria-describedby ' . $this->warningId); + } + return Element::div( Element::label( $this->label - )->props('for ' . $this->name), - Element::select( - ...$elements - )->props('id ' . $this->name, 'name ' . $this->name) + )->props('for ' . $this->name, ...$this->labelProperties), + $input, + self::warningMessage() )->props(...$this->wrapperProperties); } @@ -156,13 +198,19 @@ private function selectOther(): Element 'type ' . $type, 'value ' . $value ); - if ($this->isSelected($value)) { + + if (self::isSelected($value)) { $input = $input->prop('checked checked'); } - $elements[] = Element::div($input, $label); + + if (self::warningMessage() !== '') { + $input = $input->prop('aria-invalid true'); + $input = $input->prop('aria-describedby ' . $this->warningId); + } + $elements[] = Element::div($input, $label, self::warningMessage()); } return Element::fieldset( - Element::legend($this->label), + Element::legend($this->label)->props(...$this->labelProperties), ...$elements ); } diff --git a/src/PropertyInterface.php b/src/PropertyInterface.php new file mode 100644 index 0000000..1818e83 --- /dev/null +++ b/src/PropertyInterface.php @@ -0,0 +1,11 @@ +
Invalid value
+ html; + + $result = (string) Select::create( + label: 'Select', + name: 'select', + options: [ + 'value' => 'display', + 'value2' => 'display2' + ], + warningMessage: 'Invalid value' + ); + + parent::assertSame($expected, $result); + } + + #[Test] + public function can_add_props_to_label(): void // phpcs: ignore + { + $expected = << + html; + + $result = (string) Select::create( + label: 'Select', + name: 'select', + options: [ + 'value' => 'display', + 'value2' => 'display2' + ] + )->labelProps('is select-dropdown'); + + parent::assertSame($expected, $result); + + $expected = << + html; + + $result = (string) Select::create( + label: 'Select', + name: 'select', + options: [ + 'value' => 'display', + 'value2' => 'display2' + ] + )->checkbox()->labelProps('is checkbox'); + + parent::assertSame($expected, $result); + + $expected = << + html; + + $result = (string) Select::create( + label: 'Select', + name: 'select', + options: [ + 'value' => 'display', + 'value2' => 'display2' + ] + )->radio()->labelProps('is radio'); + + parent::assertSame($expected, $result); + } + #[Test] public function can_be_checkboxes(): void // phpcs: ignore { @@ -18,16 +111,16 @@ public function can_be_checkboxes(): void // phpcs: ignore html; $result = (string) Select::create( - 'Select your option', - 'select', - [ + label: 'Select your option', + name: 'select', + options: [ 'value' => 'display', 'value2' => 'display2' ], - ['value', 'value2'] + selected: ['value', 'value2'] )->checkbox(); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -46,7 +139,7 @@ public function can_be_radio_buttons(): void // phpcs: ignore 'value' )->radio(); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -64,7 +157,7 @@ public function dropdown_mode_is_explicit(): void // phpcs: ignore ] )->radio()->dropdown(); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -83,7 +176,7 @@ public function checkbox_mode_is_explicit(): void // phpcs: ignore 'value' )->radio()->checkbox(); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -101,7 +194,7 @@ public function can_add_properties_to_wrapper(): void // phpcs: ignore ] )->wrapperProps('id some-id', 'class some-token'); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -121,7 +214,7 @@ public function can_preselect_option(): void // phpcs:ignore 'value2' ); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -139,7 +232,7 @@ public function is_expected_base(): void // phpcs:ignore ] ); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } #[Test] @@ -158,6 +251,6 @@ public function error_is_selected_value_always_string(): void // phpcs:ignore ] ); - $this->assertSame($expected, $result); + parent::assertSame($expected, $result); } }