diff --git a/CHANGELOG.md b/CHANGELOG.md
index 633ff315..8437ccc7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
## 1.4.1 under development
-- no changes in this release.
+- New #377: Add `Color` field (@samdark)
## 1.4.0 March 27, 2025
diff --git a/README.md b/README.md
index 7408593d..8c1a1b8a 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
The package provides a set of widgets to help with dynamic server-side generation of HTML forms.
The following widgets are available out of the box:
-- input fields: `Checkbox`, `CheckboxList`, `Date`, `DateTimeLocal`, `Email`, `File`, `Hidden`, `Image`,
+- input fields: `Checkbox`, `CheckboxList`, `Color`, `Date`, `DateTimeLocal`, `Email`, `File`, `Hidden`, `Image`,
`Number`, `Password`, `RadioList`, `Range`, `Select`, `Telephone`, `Text`, `Textarea`, `Time`, `Url`;
- buttons: `Button`, `ResetButton`, `SubmitButton`;
- group widgets: `ButtonGroup`, `Fieldset`.
@@ -65,6 +65,7 @@ use Yiisoft\Form\PureField\Field;
echo Field::text('firstName', theme: 'horizontal')->label('First Name')->autofocus();
echo Field::text('lastName', theme: 'horizontal')->label('Last Name');
+echo Field::color('favoriteColor')->label('Favorite Color')->value('#3498db');
echo Field::select('sex')->label('Sex')->optionsData(['m' => 'Male', 'f' => 'Female'])->prompt('—');
echo Field::number('age')->label('Age')->hint('Please enter your age.');
echo Field::submitButton('Submit')->buttonClass('primary');
@@ -85,6 +86,10 @@ The result of executing the code above will be:
+
+ Favorite Color
+
+
Sex
diff --git a/src/Field/Color.php b/src/Field/Color.php
new file mode 100644
index 00000000..65b6f0d8
--- /dev/null
+++ b/src/Field/Color.php
@@ -0,0 +1,171 @@
+` element of type "color" that allows the user to select a color.
+ *
+ * @link https://html.spec.whatwg.org/multipage/input.html#color-state-(type=color)
+ * @link https://developer.mozilla.org/docs/Web/HTML/Element/input/color
+ */
+final class Color extends InputField implements EnrichFromValidationRulesInterface, ValidationClassInterface
+{
+ use EnrichFromValidationRulesTrait;
+ use ValidationClassTrait;
+
+ /**
+ * @link https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
+ */
+ public function disabled(bool $disabled = true): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['disabled'] = $disabled;
+ return $new;
+ }
+
+ /**
+ * A boolean attribute that controls whether or not the user can edit the form control.
+ *
+ * @param bool $value Whether to allow the value to be edited by the user.
+ *
+ * @link https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
+ */
+ public function readonly(bool $value = true): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['readonly'] = $value;
+ return $new;
+ }
+
+ /**
+ * A boolean attribute. When specified, the element is required.
+ *
+ * @param bool $value Whether the control is required for form submission.
+ *
+ * @link https://html.spec.whatwg.org/multipage/input.html#attr-input-required
+ */
+ public function required(bool $value = true): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['required'] = $value;
+ return $new;
+ }
+
+ /**
+ * Identifies the element (or elements) that describes the object.
+ *
+ * @link https://w3c.github.io/aria/#aria-describedby
+ */
+ public function ariaDescribedBy(?string ...$value): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['aria-describedby'] = array_filter($value, static fn (?string $v): bool => $v !== null);
+ return $new;
+ }
+
+ /**
+ * Defines a string value that labels the current element.
+ *
+ * @link https://w3c.github.io/aria/#aria-label
+ */
+ public function ariaLabel(?string $value): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['aria-label'] = $value;
+ return $new;
+ }
+
+ /**
+ * Focus on the control (put cursor into it) when the page loads. Only one form element could be in focus
+ * at the same time.
+ *
+ * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-fe-autofocus
+ */
+ public function autofocus(bool $value = true): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['autofocus'] = $value;
+ return $new;
+ }
+
+ /**
+ * The `tabindex` attribute indicates that its element can be focused, and where it participates in sequential
+ * keyboard navigation (usually with the Tab key, hence the name).
+ *
+ * It accepts an integer as a value, with different results depending on the integer's value:
+ *
+ * - A negative value (usually `tabindex="-1"`) means that the element is not reachable via sequential keyboard
+ * navigation, but could be focused with Javascript or visually. It's mostly useful to create accessible widgets
+ * with JavaScript.
+ * - `tabindex="0"` means that the element should be focusable in sequential keyboard navigation, but its order is
+ * defined by the document's source order.
+ * - A positive value means the element should be focusable in sequential keyboard navigation, with its order
+ * defined by the value of the number. That is, `tabindex="4"` is focused before `tabindex="5"`, but after
+ * `tabindex="3"`.
+ *
+ * @link https://html.spec.whatwg.org/multipage/interaction.html#attr-tabindex
+ */
+ public function tabIndex(?int $value): self
+ {
+ $new = clone $this;
+ $new->inputAttributes['tabindex'] = $value;
+ return $new;
+ }
+
+ protected function beforeRender(): void
+ {
+ if ($this->enrichFromValidationRules) {
+ $this->enrichment = $this
+ ->validationRulesEnricher
+ ?->process($this, $this->getInputData()->getValidationRules())
+ ?? [];
+ }
+ }
+
+ protected function generateInput(): string
+ {
+ $value = $this->getValue();
+
+ if (!is_string($value) && $value !== null) {
+ throw new InvalidArgumentException('Color field requires a string or null value.');
+ }
+
+ /** @psalm-suppress MixedArgument We guess that enrichment contain correct values. */
+ $inputAttributes = array_merge(
+ $this->enrichment['inputAttributes'] ?? [],
+ $this->getInputAttributes()
+ );
+
+ return Html::input('color', $this->getName(), $value, $inputAttributes)->render();
+ }
+
+ protected function prepareContainerAttributes(array &$attributes): void
+ {
+ $this->addValidationClassToAttributes(
+ $attributes,
+ $this->getInputData(),
+ $this->hasCustomError() ? true : null,
+ );
+ }
+
+ protected function prepareInputAttributes(array &$attributes): void
+ {
+ $this->addInputValidationClassToAttributes(
+ $attributes,
+ $this->getInputData(),
+ $this->hasCustomError() ? true : null,
+ );
+ }
+}
diff --git a/src/PureField/Field.php b/src/PureField/Field.php
index 96dbd8a7..1110286f 100644
--- a/src/PureField/Field.php
+++ b/src/PureField/Field.php
@@ -8,6 +8,7 @@
use Yiisoft\Form\Field\ButtonGroup;
use Yiisoft\Form\Field\Checkbox;
use Yiisoft\Form\Field\CheckboxList;
+use Yiisoft\Form\Field\Color;
use Yiisoft\Form\Field\Date;
use Yiisoft\Form\Field\DateTimeLocal;
use Yiisoft\Form\Field\Email;
@@ -81,6 +82,16 @@ final public static function checkboxList(
->inputData(new InputData($name, $value));
}
+ final public static function color(
+ ?string $name = null,
+ mixed $value = null,
+ array $config = [],
+ ?string $theme = null,
+ ): Color {
+ return Color::widget(config: $config, theme: $theme ?? static::DEFAULT_THEME)
+ ->inputData(new InputData($name, $value));
+ }
+
final public static function date(
?string $name = null,
mixed $value = null,
diff --git a/src/PureField/FieldFactory.php b/src/PureField/FieldFactory.php
index aeab8762..b43e8c93 100644
--- a/src/PureField/FieldFactory.php
+++ b/src/PureField/FieldFactory.php
@@ -8,6 +8,7 @@
use Yiisoft\Form\Field\ButtonGroup;
use Yiisoft\Form\Field\Checkbox;
use Yiisoft\Form\Field\CheckboxList;
+use Yiisoft\Form\Field\Color;
use Yiisoft\Form\Field\Date;
use Yiisoft\Form\Field\DateTimeLocal;
use Yiisoft\Form\Field\Email;
@@ -80,6 +81,16 @@ final public function checkboxList(
->inputData(new InputData($name, $value));
}
+ final public function color(
+ ?string $name = null,
+ mixed $value = null,
+ array $config = [],
+ ?string $theme = null,
+ ): Color {
+ return Color::widget(config: $config, theme: $theme ?? $this->defaultTheme)
+ ->inputData(new InputData($name, $value));
+ }
+
final public function date(
?string $name = null,
mixed $value = null,
diff --git a/tests/Field/ColorTest.php b/tests/Field/ColorTest.php
new file mode 100644
index 00000000..e8e3768a
--- /dev/null
+++ b/tests/Field/ColorTest.php
@@ -0,0 +1,754 @@
+ [
+ <<
+ Background Color
+
+ Select a background color.
+
+ HTML,
+ new InputData(
+ name: 'ColorForm[bgcolor]',
+ value: null,
+ label: 'Background Color',
+ hint: 'Select a background color.',
+ id: 'colorform-bgcolor',
+ ),
+ ],
+ 'input-valid-class' => [
+ <<
+
+
+ HTML,
+ new InputData(
+ name: 'color',
+ value: null,
+ validationErrors: [],
+ ),
+ ['inputValidClass' => 'valid', 'inputInvalidClass' => 'invalid'],
+ ],
+ 'container-valid-class' => [
+ <<
+
+
+ HTML,
+ new InputData(
+ name: 'color',
+ value: null,
+ validationErrors: [],
+ ),
+ ['validClass' => 'valid', 'invalidClass' => 'invalid'],
+ ],
+ 'value' => [
+ <<
+
+
+ HTML,
+ new InputData(
+ name: 'color',
+ value: '#ff0000',
+ ),
+ ],
+ ];
+ }
+
+ #[DataProvider('dataBase')]
+ public function testBase(string $expected, InputData $inputData, array $theme = []): void
+ {
+ ThemeContainer::initialize(
+ configs: ['default' => $theme],
+ defaultConfig: 'default',
+ );
+
+ $result = Color::widget()->inputData($inputData)->render();
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testReadonly(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->readonly()
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testRequired(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->required()
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testDisabled(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->disabled()
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public static function dataAriaDescribedBy(): array
+ {
+ return [
+ 'one element' => [
+ ['hint'],
+ <<
+
+
+ HTML,
+ ],
+ 'multiple elements' => [
+ ['hint1', 'hint2'],
+ <<
+
+
+ HTML,
+ ],
+ 'null with other elements' => [
+ ['hint1', null, 'hint2', null, 'hint3'],
+ <<
+
+
+ HTML,
+ ],
+ 'only null' => [
+ [null, null],
+ <<
+
+
+ HTML,
+ ],
+ 'empty string' => [
+ [''],
+ <<
+
+
+ HTML,
+ ],
+ ];
+ }
+
+ #[DataProvider('dataAriaDescribedBy')]
+ public function testAriaDescribedBy(array $ariaDescribedBy, string $expectedHtml): void
+ {
+ $actualHtml = Color::widget()
+ ->name('test')
+ ->ariaDescribedBy(...$ariaDescribedBy)
+ ->hideLabel()
+ ->render();
+
+ $this->assertSame($expectedHtml, $actualHtml);
+ }
+
+ public function testAriaLabel(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->ariaLabel('test')
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testAutofocus(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->autofocus()
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testTabIndex(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->tabIndex(5)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testValue(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->value('#123456')
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testValueNull(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->value(null)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testInvalidValue(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Color field requires a string or null value.');
+ Color::widget()->name('test')->value(123)->render();
+ }
+
+ public function testEnrichFromValidationRules(): void
+ {
+ $result = Color::widget()
+ ->enrichFromValidationRules()
+ ->validationRulesEnricher(new RequiredValidationRulesEnricher())
+ ->inputData(new InputData('color', validationRules: [['required']]))
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testEnrichFromValidationRulesDisabled(): void
+ {
+ $result = Color::widget()
+ ->validationRulesEnricher(new RequiredValidationRulesEnricher())
+ ->inputData(new InputData('color', validationRules: [['required']]))
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testEnrichFromValidationRulesWithNullProcessResult(): void
+ {
+ $result = Color::widget()
+ ->enrichFromValidationRules()
+ ->validationRulesEnricher(new NullValidationRulesEnricher())
+ ->inputData(new InputData('color'))
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testEnrichmentInputAttributes(): void
+ {
+ $result = Color::widget()
+ ->enrichFromValidationRules()
+ ->validationRulesEnricher(
+ new StubValidationRulesEnricher(['inputAttributes' => ['data-test' => 1]])
+ )
+ ->inputData(new InputData('color'))
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testInvalidClassesFromContainer(): void
+ {
+ $inputData = new InputData('color', validationErrors: ['Value cannot be blank.']);
+
+ $result = Color::widget()
+ ->validClass('valid')
+ ->invalidClass('invalid')
+ ->inputData($inputData)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+Value cannot be blank.
+
+HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testValidClassesFromContainer(): void
+ {
+ $inputData = new InputData('color', validationErrors: []);
+
+ $result = Color::widget()
+ ->validClass('valid')
+ ->invalidClass('invalid')
+ ->inputData($inputData)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testInvalidClassesFromInput(): void
+ {
+ $inputData = new InputData('color', validationErrors: ['Value cannot be blank.']);
+
+ $result = Color::widget()
+ ->inputValidClass('valid')
+ ->inputInvalidClass('invalid')
+ ->inputData($inputData)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+Value cannot be blank.
+
+HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testValidClassesFromInput(): void
+ {
+ $inputData = new InputData('color', validationErrors: []);
+
+ $result = Color::widget()
+ ->inputValidClass('valid')
+ ->inputInvalidClass('invalid')
+ ->inputData($inputData)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testDisabledFalse(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->disabled(false)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testReadonlyFalse(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->readonly(false)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testRequiredFalse(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->required(false)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testAutofocusFalse(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->autofocus(false)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testTabIndexNull(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->tabIndex(null)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testTabIndexNegative(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->tabIndex(-1)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testAriaLabelNull(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->ariaLabel(null)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testImmutability(): void
+ {
+ $field = Color::widget();
+
+ // Test that each method returns a different instance (kills CloneRemoval mutants)
+ $this->assertNotSame($field, $field->disabled());
+ $this->assertNotSame($field, $field->readonly());
+ $this->assertNotSame($field, $field->required());
+ $this->assertNotSame($field, $field->ariaDescribedBy(null));
+ $this->assertNotSame($field, $field->ariaLabel(null));
+ $this->assertNotSame($field, $field->autofocus());
+ $this->assertNotSame($field, $field->tabIndex(null));
+
+ // Test that original instance is not modified when chaining methods
+ $original = Color::widget()->name('original');
+ $modified = $original->disabled()->readonly()->required()->autofocus();
+
+ $originalHtml = $original->hideLabel()->render();
+ $modifiedHtml = $modified->hideLabel()->render();
+
+ $expectedOriginal = <<
+
+
+ HTML;
+
+ $expectedModified = <<
+
+
+ HTML;
+
+ $this->assertSame($expectedOriginal, $originalHtml);
+ $this->assertSame($expectedModified, $modifiedHtml);
+ }
+
+ public function testEnrichmentAttributesMerge(): void
+ {
+ $result = Color::widget()
+ ->enrichFromValidationRules()
+ ->validationRulesEnricher(
+ new StubValidationRulesEnricher(['inputAttributes' => ['data-enriched' => 'from-validation']])
+ )
+ ->inputData(new InputData('color'))
+ ->disabled()
+ ->ariaLabel('test-label')
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testValidationEnrichmentDisabledByDefault(): void
+ {
+ $widget = Color::widget()
+ ->validationRulesEnricher(new RequiredValidationRulesEnricher())
+ ->inputData(new InputData('color', validationRules: [['required']]));
+
+ // Should not have 'required' attribute since enrichFromValidationRules() was not called
+ $result = $widget->hideLabel()->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testBeforeRenderEnrichmentCondition(): void
+ {
+ // Test the condition in beforeRender() that checks $this->enrichFromValidationRules
+ $widget = Color::widget()
+ ->enrichFromValidationRules()
+ ->inputData(new InputData('color', validationRules: [['required']]));
+
+ // Without enricher, should not process rules
+ $result = $widget->hideLabel()->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testInvalidValueArray(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Color field requires a string or null value.');
+ Color::widget()->name('test')->value([])->render();
+ }
+
+ public function testInvalidValueFloat(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Color field requires a string or null value.');
+ Color::widget()->name('test')->value(3.14)->render();
+ }
+
+ public function testInvalidValueBoolean(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Color field requires a string or null value.');
+ Color::widget()->name('test')->value(true)->render();
+ }
+
+ public function testInvalidValueObject(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Color field requires a string or null value.');
+ Color::widget()->name('test')->value(new \stdClass())->render();
+ }
+
+ public function testValueEmptyString(): void
+ {
+ $result = Color::widget()
+ ->name('test')
+ ->value('')
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testAriaDescribedByFilteringBehavior(): void
+ {
+ // Test that array_filter in ariaDescribedBy correctly filters out null values
+ $result = Color::widget()
+ ->name('test')
+ ->ariaDescribedBy('valid1', null, '', 'valid2', null)
+ ->hideLabel()
+ ->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+
+ public function testCustomErrorWithValidationClasses(): void
+ {
+ $inputData = new InputData('color', validationErrors: []);
+
+ $result = Color::widget()
+ ->inputData($inputData)
+ ->validClass('valid-container')
+ ->invalidClass('invalid-container')
+ ->inputValidClass('valid-input')
+ ->inputInvalidClass('invalid-input')
+ ->error('Custom error message')
+ ->hideLabel()
+ ->render();
+
+ // With custom error, should show invalid classes even if no validation errors
+ $expected = <<
+
+ Custom error message
+
+ HTML;
+
+ $this->assertSame($expected, $result);
+ }
+}
diff --git a/tests/PureField/FieldFactoryTest.php b/tests/PureField/FieldFactoryTest.php
index b526b5c9..cc0f998c 100644
--- a/tests/PureField/FieldFactoryTest.php
+++ b/tests/PureField/FieldFactoryTest.php
@@ -234,6 +234,38 @@ public function testDateTimeLocalWithTheme(): void
$this->assertSame($expected, $html);
}
+ public function testColor(): void
+ {
+ $html = (new FieldFactory())->color()->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $html);
+ }
+
+ public function testColorWithTheme(): void
+ {
+ ThemeContainer::initialize([
+ 'test' => [
+ 'containerTag' => 'span',
+ ],
+ ]);
+
+ $html = (new FieldFactory('default'))->color(theme: 'test')->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $html);
+ }
+
public function testEmail(): void
{
$html = (new FieldFactory())->email()->render();
diff --git a/tests/PureField/FieldTest.php b/tests/PureField/FieldTest.php
index 65acb951..663015bd 100644
--- a/tests/PureField/FieldTest.php
+++ b/tests/PureField/FieldTest.php
@@ -235,6 +235,38 @@ public function testDateTimeLocalWithTheme(): void
$this->assertSame($expected, $html);
}
+ public function testColor(): void
+ {
+ $html = Field::color()->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $html);
+ }
+
+ public function testColorWithTheme(): void
+ {
+ ThemeContainer::initialize([
+ 'test' => [
+ 'containerTag' => 'span',
+ ],
+ ]);
+
+ $html = ThemedField::color(theme: 'test')->render();
+
+ $expected = <<
+
+
+ HTML;
+
+ $this->assertSame($expected, $html);
+ }
+
public function testEmail(): void
{
$html = Field::email()->render();
diff --git a/themes-preview/template.php b/themes-preview/template.php
index 8c25e77f..f77e6c0b 100644
--- a/themes-preview/template.php
+++ b/themes-preview/template.php
@@ -49,6 +49,8 @@
echo Field::email()->label('Email Field')->placeholder('Placeholder');
+ echo Field::color()->label('Color Field')->value('#ff0000');
+
echo Field::time()->label('Time Field');
echo Field::date()->label('Date Field');