From 273e232b19ca196fa3b6d99158a11592b42e9368 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Thu, 12 Nov 2026 11:34:26 -0300 Subject: [PATCH] refactor: match `Bootstrap5` and `Bootstrap6` contexts against upstream component, field, variant and state enums; show enums in `README.md`. --- CHANGELOG.md | 1 + README.md | 114 +++---------------- composer.json | 2 +- ecs.php | 21 +++- src/Bootstrap5.php | 75 ++++++++----- src/Bootstrap6.php | 70 +++++++----- tests/EnumContextTest.php | 63 +++++++++++ tests/Provider/EnumContextProvider.php | 148 +++++++++++++++++++++++++ 8 files changed, 329 insertions(+), 165 deletions(-) create mode 100644 tests/EnumContextTest.php create mode 100644 tests/Provider/EnumContextProvider.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e227a..f10507f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 Under development - feat: initial development release. +- refactor: match `Bootstrap5` and `Bootstrap6` contexts against upstream component, field, variant and state enums; show enums in `README.md`. diff --git a/README.md b/README.md index f09ba69..deb1f94 100644 --- a/README.md +++ b/README.md @@ -44,52 +44,40 @@ composer require ui-awesome/html-theme:^0.1 ### Quick start A theme resolves configuration recipes for semantic component contexts. Pass it to components through an -application-scoped `Config`, and describe each component with a `ComponentContext`. +application-scoped `Config`, and describe each component with a `ComponentContext`. The examples name components, +variants, and states with the `UIAwesome\Html\Core\Component\Values` enums; plain strings such as `'alert'` or +`'dismissible'` remain accepted. #### Bootstrap 5 ```php use UIAwesome\Html\Core\Component\Alert; +use UIAwesome\Html\Core\Component\Values\{ComponentName, ComponentState}; use UIAwesome\Html\Core\Config\{ComponentContext, Config}; use UIAwesome\Html\Theme\Bootstrap5; $config = new Config(theme: new Bootstrap5()); echo Alert::tag() - ->config($config, new ComponentContext('alert', variant: 'success')) + ->config($config, new ComponentContext(ComponentName::ALERT, variant: 'success')) ->content('Your changes have been saved.') ->id('alert-saved') ->render(); ``` -Output: - -```text - -``` - States refine a variant. The `dismissible` state adds the Bootstrap close button: ```php echo Alert::tag() - ->config($config, new ComponentContext('alert', variant: 'danger', states: ['dismissible'])) + ->config( + $config, + new ComponentContext(ComponentName::ALERT, variant: 'danger', states: [ComponentState::DISMISSIBLE]), + ) ->content('Your session expired.') ->id('alert-session') ->render(); ``` -Output: - -```text - -``` - #### Bootstrap 6 alpha `Bootstrap6` targets Bootstrap **6.0.0-alpha.1**. Support is experimental and tied to that alpha, not to a stable @@ -105,7 +93,7 @@ use UIAwesome\Html\Theme\Bootstrap6; $config = new Config(theme: new Bootstrap6()); echo NavBar::tag() - ->config($config, new ComponentContext('navbar')) + ->config($config, new ComponentContext(ComponentName::NAVBAR)) ->brandLink('/') ->brandText('UI Awesome') ->menu( @@ -122,42 +110,6 @@ echo NavBar::tag() ->render(); ``` -Output: - -```text - -``` - #### Navbar dropdown A `Dropdown` nested in the navbar menu receives the theme's dropdown toggle. `toggleLabel()` replaces only the toggle @@ -167,7 +119,7 @@ text and keeps its classes and data attributes. use UIAwesome\Html\Core\Component\Dropdown; echo NavBar::tag() - ->config($config, new ComponentContext('navbar')) + ->config($config, new ComponentContext(ComponentName::NAVBAR)) ->brandLink('/') ->brandText('UI Awesome') ->menu( @@ -188,52 +140,15 @@ echo NavBar::tag() ->render(); ``` -Output: - -```text - -``` - To build the toggle yourself, the `menu-dropdown` toggle recipe applies the same styling without content, and the dropdown keeps a toggle set through `toggle()`: ```php use UIAwesome\Html\Core\Component\Toggle; +use UIAwesome\Html\Core\Component\Values\ToggleVariant; $toggle = Toggle::tag() - ->config($config, new ComponentContext('toggle', variant: 'menu-dropdown')) + ->config($config, new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::MENU_DROPDOWN)) ->content('Docs'); ``` @@ -260,9 +175,10 @@ both hidden with `d-none`. The package ships no JavaScript, so the application s ```php use UIAwesome\Html\Core\Component\Toggle; +use UIAwesome\Html\Core\Component\Values\ToggleVariant; echo Toggle::tag() - ->config($config, new ComponentContext('toggle', variant: 'selector-theme')) + ->config($config, new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::SELECTOR_THEME)) ->id('theme-selector') ->render(); ``` diff --git a/composer.json b/composer.json index 6819009..55081f3 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "phpstan/phpstan-strict-rules": "^2.0", "testo/bridge-infection": "^0.1.9", "testo/testo": "^0.10.56", - "ui-awesome/form-model": "^0.2@dev" + "ui-awesome/form-model": "^0.3@dev" }, "autoload": { "psr-4": { diff --git a/ecs.php b/ecs.php index e5046fe..9d958dc 100644 --- a/ecs.php +++ b/ecs.php @@ -2,12 +2,21 @@ declare(strict_types=1); +use PhpCsFixer\Fixer\Whitespace\{HeredocIndentationFixer, NoExtraBlankLinesFixer}; + /** @var \Symplify\EasyCodingStandard\Configuration\ECSConfigBuilder $builder */ $builder = require __DIR__ . '/vendor/php-forge/coding-standard/src/ecs-83.php'; -return $builder->withPaths( - [ - __DIR__ . '/src', - __DIR__ . '/tests', - ], -); +return $builder + ->withPaths( + [ + __DIR__ . '/src', + __DIR__ . '/tests', + ], + ) + ->withSkip( + [ + HeredocIndentationFixer::class, + NoExtraBlankLinesFixer::class, + ], + ); diff --git a/src/Bootstrap5.php b/src/Bootstrap5.php index d0c0277..97817de 100644 --- a/src/Bootstrap5.php +++ b/src/Bootstrap5.php @@ -5,8 +5,10 @@ namespace UIAwesome\Html\Theme; use UIAwesome\Html\Core\Component\Toggle; +use UIAwesome\Html\Core\Component\Values\{ComponentName, ComponentState, DropdownVariant, NavBarVariant, ToggleVariant}; use UIAwesome\Html\Core\Config\{Call, ComponentContext, Config, Cookbook, Recipe}; use UIAwesome\Html\Core\Theme\ThemeInterface; +use UIAwesome\Html\Field\Values\FieldComponentName; use UIAwesome\Html\Interop\{Block, Inline}; use UIAwesome\Html\Svg\Svg; @@ -17,7 +19,9 @@ * * Dispatch is explicit: {@see getRecipes()} matches `$context->component`, then narrows on `$context->variant` and * `$context->states` where the framework offers more than one presentation. A context the theme does not recognize - * yields no recipe, so the host component keeps its own defaults. + * yields no recipe, so the host component keeps its own defaults. Recognized names are the backing values of + * {@see ComponentName}, {@see FieldComponentName}, and the upstream variant and state enums, so a context built from an + * enum case or from its string value resolves the same recipe. * * Every alert requires a contextual variant, and the navbar collapses below the large breakpoint behind the `toggle` * recipe with the `menu` variant. Interactive alerts, dropdowns, and collapses require Bootstrap's JavaScript; the @@ -77,28 +81,36 @@ public function getName(): string public function getRecipes(ComponentContext $context): iterable { $recipe = match ($context->component) { - 'alert' => $this->alert($context), - 'breadcrumb' => $this->breadcrumb(), - 'dropdown' => $this->dropdown($context), - 'navbar' => $this->navBar($context), - 'toggle' => $this->toggle($context), - 'field' => $this->field(), - 'field.label' => $this->fieldLabel(), - 'field.control.email', - 'field.control.password', - 'field.control.text', - 'field.control.textarea' => $this->fieldControl($context->component, 'form-control'), - 'field.control.select' => $this->fieldControl($context->component, 'form-select'), - 'field.control.checkbox', - 'field.control.radio' => $this->fieldControl($context->component, 'form-check-input'), - 'field.control.checkbox-list', - 'field.control.radio-list' => $this->fieldChoiceList($context->component), - 'field.input-container.checkbox', - 'field.input-container.radio' => $this->fieldChoiceInputContainer($context->component), - 'field.label.checkbox', - 'field.label.radio' => $this->fieldChoiceLabel($context->component), - 'field.hint' => $this->fieldHint(), - 'field.error' => $this->fieldError(), + ComponentName::ALERT->value => $this->alert($context), + ComponentName::BREADCRUMB->value => $this->breadcrumb(), + ComponentName::DROPDOWN->value => $this->dropdown($context), + ComponentName::NAVBAR->value => $this->navBar($context), + ComponentName::TOGGLE->value => $this->toggle($context), + FieldComponentName::FIELD->value => $this->field(), + FieldComponentName::FIELD_LABEL->value => $this->fieldLabel(), + FieldComponentName::FIELD_CONTROL_EMAIL->value, + FieldComponentName::FIELD_CONTROL_PASSWORD->value, + FieldComponentName::FIELD_CONTROL_TEXT->value, + FieldComponentName::FIELD_CONTROL_TEXTAREA->value => $this->fieldControl( + $context->component, + 'form-control', + ), + FieldComponentName::FIELD_CONTROL_SELECT->value => $this->fieldControl($context->component, 'form-select'), + FieldComponentName::FIELD_CONTROL_CHECKBOX->value, + FieldComponentName::FIELD_CONTROL_RADIO->value => $this->fieldControl( + $context->component, + 'form-check-input', + ), + FieldComponentName::FIELD_CONTROL_CHECKBOX_LIST->value, + FieldComponentName::FIELD_CONTROL_RADIO_LIST->value => $this->fieldChoiceList($context->component), + FieldComponentName::FIELD_INPUT_CONTAINER_CHECKBOX->value, + FieldComponentName::FIELD_INPUT_CONTAINER_RADIO->value => $this->fieldChoiceInputContainer( + $context->component, + ), + FieldComponentName::FIELD_LABEL_CHECKBOX->value, + FieldComponentName::FIELD_LABEL_RADIO->value => $this->fieldChoiceLabel($context->component), + FieldComponentName::FIELD_HINT->value => $this->fieldHint(), + FieldComponentName::FIELD_ERROR->value => $this->fieldError(), default => null, }; @@ -129,7 +141,7 @@ private function alert(ComponentContext $context): Recipe|null $class = "alert alert-{$variant}"; $suffix = "alert.{$variant}"; - if ($context->hasState('dismissible') === false) { + if ($context->hasState(ComponentState::DISMISSIBLE) === false) { return $this->recipe($suffix, new Call('class', $class)); } @@ -198,7 +210,7 @@ private function dropdown(ComponentContext $context): Recipe|null ); } - if ($context->variant === 'language') { + if ($context->variant === DropdownVariant::LANGUAGE->value) { return $this->recipe( 'dropdown.language', new Call('containerClass', 'btn-group dropup ms-3'), @@ -381,7 +393,10 @@ private function languageSelectorToggle(): Toggle private function menuDropdownToggle(): Toggle { return Toggle::tag() - ->config(new Config($this), new ComponentContext('toggle', variant: 'menu-dropdown')) + ->config( + new Config($this), + new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::MENU_DROPDOWN), + ) ->content('Dropdown'); } @@ -456,7 +471,7 @@ private function navBar(ComponentContext $context): Recipe|null ); } - if ($context->variant === 'align-right') { + if ($context->variant === NavBarVariant::ALIGN_RIGHT->value) { return $this->recipe( 'navbar.align-right', new Call('brandLinkClass', 'navbar-brand'), @@ -567,9 +582,9 @@ private function themeSelectorToggle(): Recipe private function toggle(ComponentContext $context): Recipe|null { return match ($context->variant) { - 'menu' => $this->menuToggle(), - 'menu-dropdown' => $this->menuDropdownToggleRecipe(), - 'selector-theme' => $this->themeSelectorToggle(), + ToggleVariant::MENU->value => $this->menuToggle(), + ToggleVariant::MENU_DROPDOWN->value => $this->menuDropdownToggleRecipe(), + ToggleVariant::SELECTOR_THEME->value => $this->themeSelectorToggle(), default => null, }; } diff --git a/src/Bootstrap6.php b/src/Bootstrap6.php index d26508c..764be3d 100644 --- a/src/Bootstrap6.php +++ b/src/Bootstrap6.php @@ -5,8 +5,10 @@ namespace UIAwesome\Html\Theme; use UIAwesome\Html\Core\Component\Toggle; +use UIAwesome\Html\Core\Component\Values\{ComponentName, ComponentState, DropdownVariant, NavBarVariant, ToggleVariant}; use UIAwesome\Html\Core\Config\{Call, ComponentContext, Config, Cookbook, Recipe}; use UIAwesome\Html\Core\Theme\ThemeInterface; +use UIAwesome\Html\Field\Values\FieldComponentName; use UIAwesome\Html\Interop\Block; use UIAwesome\Html\Svg\Svg; @@ -17,7 +19,9 @@ * * Dispatch is explicit: {@see getRecipes()} matches `$context->component`, then narrows on `$context->variant` and * `$context->states` where the framework offers more than one presentation. A context the theme does not recognize - * yields no recipe, so the host component keeps its own defaults. + * yields no recipe, so the host component keeps its own defaults. Recognized names are the backing values of + * {@see ComponentName}, {@see FieldComponentName}, and the upstream variant and state enums, so a context built from an + * enum case or from its string value resolves the same recipe. * * Navbar recipes are always expanded: the core menu cannot render Bootstrap 6's native dialog drawer structure, so no * menu-toggle recipe is supplied. Interactive alerts and menus require Bootstrap's JavaScript ES module; the theme @@ -77,28 +81,33 @@ public function getName(): string public function getRecipes(ComponentContext $context): iterable { $recipe = match ($context->component) { - 'alert' => $this->alert($context), - 'breadcrumb' => $this->breadcrumb(), - 'dropdown' => $this->dropdown($context), - 'navbar' => $this->navBar($context), - 'toggle' => $this->toggle($context), - 'field' => $this->field(), - 'field.label' => $this->fieldLabel(), - 'field.control.email', - 'field.control.password', - 'field.control.text', - 'field.control.textarea', - 'field.control.select' => $this->fieldControl($context->component, 'form-control'), - 'field.control.checkbox' => $this->fieldControl($context->component, 'check'), - 'field.control.radio' => $this->fieldControl($context->component, 'radio'), - 'field.control.checkbox-list' => $this->fieldChoiceList($context->component, 'check'), - 'field.control.radio-list' => $this->fieldChoiceList($context->component, 'radio'), - 'field.input-container.checkbox', - 'field.input-container.radio' => $this->fieldChoiceInputContainer($context->component), - 'field.label.checkbox', - 'field.label.radio' => $this->fieldChoiceLabel($context->component), - 'field.hint' => $this->fieldHint(), - 'field.error' => $this->fieldError(), + ComponentName::ALERT->value => $this->alert($context), + ComponentName::BREADCRUMB->value => $this->breadcrumb(), + ComponentName::DROPDOWN->value => $this->dropdown($context), + ComponentName::NAVBAR->value => $this->navBar($context), + ComponentName::TOGGLE->value => $this->toggle($context), + FieldComponentName::FIELD->value => $this->field(), + FieldComponentName::FIELD_LABEL->value => $this->fieldLabel(), + FieldComponentName::FIELD_CONTROL_EMAIL->value, + FieldComponentName::FIELD_CONTROL_PASSWORD->value, + FieldComponentName::FIELD_CONTROL_TEXT->value, + FieldComponentName::FIELD_CONTROL_TEXTAREA->value, + FieldComponentName::FIELD_CONTROL_SELECT->value => $this->fieldControl($context->component, 'form-control'), + FieldComponentName::FIELD_CONTROL_CHECKBOX->value => $this->fieldControl($context->component, 'check'), + FieldComponentName::FIELD_CONTROL_RADIO->value => $this->fieldControl($context->component, 'radio'), + FieldComponentName::FIELD_CONTROL_CHECKBOX_LIST->value => $this->fieldChoiceList( + $context->component, + 'check', + ), + FieldComponentName::FIELD_CONTROL_RADIO_LIST->value => $this->fieldChoiceList($context->component, 'radio'), + FieldComponentName::FIELD_INPUT_CONTAINER_CHECKBOX->value, + FieldComponentName::FIELD_INPUT_CONTAINER_RADIO->value => $this->fieldChoiceInputContainer( + $context->component, + ), + FieldComponentName::FIELD_LABEL_CHECKBOX->value, + FieldComponentName::FIELD_LABEL_RADIO->value => $this->fieldChoiceLabel($context->component), + FieldComponentName::FIELD_HINT->value => $this->fieldHint(), + FieldComponentName::FIELD_ERROR->value => $this->fieldError(), default => null, }; @@ -131,7 +140,7 @@ private function alert(ComponentContext $context): Recipe|null $class = $variant === null ? 'alert' : "alert theme-{$variant}"; $suffix = 'alert' . ($variant === null ? '' : ".{$variant}"); - if ($context->hasState('dismissible') === false) { + if ($context->hasState(ComponentState::DISMISSIBLE) === false) { return $this->recipe($suffix, new Call('class', $class), new Call('containerTag', Block::DIV)); } @@ -211,7 +220,7 @@ private function dropdown(ComponentContext $context): Recipe|null ); } - if ($context->variant === 'language') { + if ($context->variant === DropdownVariant::LANGUAGE->value) { return $this->recipe( 'dropdown.language', new Call('containerClass', 'ms-5'), @@ -401,7 +410,10 @@ private function languageSelectorToggle(): Toggle private function menuDropdownToggle(): Toggle { return Toggle::tag() - ->config(new Config($this), new ComponentContext('toggle', variant: 'menu-dropdown')) + ->config( + new Config($this), + new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::MENU_DROPDOWN), + ) ->content('Dropdown'); } @@ -454,7 +466,7 @@ private function navBar(ComponentContext $context): Recipe|null ); } - if ($context->variant === 'align-right') { + if ($context->variant === NavBarVariant::ALIGN_RIGHT->value) { return $this->recipe( 'navbar.align-right', new Call('brandLinkClass', 'navbar-brand'), @@ -563,8 +575,8 @@ private function themeSelectorToggle(): Recipe private function toggle(ComponentContext $context): Recipe|null { return match ($context->variant) { - 'menu-dropdown' => $this->menuDropdownToggleRecipe(), - 'selector-theme' => $this->themeSelectorToggle(), + ToggleVariant::MENU_DROPDOWN->value => $this->menuDropdownToggleRecipe(), + ToggleVariant::SELECTOR_THEME->value => $this->themeSelectorToggle(), default => null, }; } diff --git a/tests/EnumContextTest.php b/tests/EnumContextTest.php new file mode 100644 index 0000000..2b651ce --- /dev/null +++ b/tests/EnumContextTest.php @@ -0,0 +1,63 @@ + $expected Recipe names both contexts must resolve, in application order. + * @param Closure(Config, ComponentContext): string $render + */ + #[DataProvider(EnumContextProvider::class . '::contexts')] + public function enumContextResolvesLikeStringContext( + ThemeInterface $theme, + ComponentContext $enumContext, + ComponentContext $stringContext, + array $expected, + Closure $render, + ): void { + Assert::same( + array_map( + static fn(Recipe $recipe): string => $recipe->name, + iterator_to_array($theme->getRecipes($enumContext), false), + ), + $expected, + 'Enum context must resolve the expected recipes.', + ); + Assert::same( + array_map( + static fn(Recipe $recipe): string => $recipe->name, + iterator_to_array($theme->getRecipes($stringContext), false), + ), + $expected, + 'String context must resolve the expected recipes.', + ); + Assert::same( + $render(new Config($theme), $enumContext), + $render(new Config($theme), $stringContext), + 'Rendered markup must not depend on how the context was built.', + ); + } +} diff --git a/tests/Provider/EnumContextProvider.php b/tests/Provider/EnumContextProvider.php new file mode 100644 index 0000000..3f7c47e --- /dev/null +++ b/tests/Provider/EnumContextProvider.php @@ -0,0 +1,148 @@ + [Bootstrap5::class, 'bootstrap5', ['bootstrap5.toggle.menu']], + 'Bootstrap 6' => [Bootstrap6::class, 'bootstrap6', []], + ]; + + /** + * @return iterable< + * string, + * array{ + * ThemeInterface, + * ComponentContext, + * ComponentContext, + * list, + * Closure(Config, ComponentContext): string, + * }, + * > + */ + public static function contexts(): iterable + { + foreach (self::THEMES as $label => [$themeClass, $name, $menuToggleRecipes]) { + $theme = new $themeClass(); + + yield "{$label}: Alert (state: ComponentState::DISMISSIBLE)" => [ + $theme, + new ComponentContext(ComponentName::ALERT, variant: 'danger', states: [ComponentState::DISMISSIBLE]), + new ComponentContext('alert', variant: 'danger', states: ['dismissible']), + ["{$name}.alert.danger.dismissible"], + static fn(Config $config, ComponentContext $context): string => Alert::tag() + ->config($config, $context) + ->content('Your session expired.') + ->id('alert-session') + ->render(), + ]; + yield "{$label}: Dropdown (variant: DropdownVariant::LANGUAGE)" => [ + $theme, + new ComponentContext(ComponentName::DROPDOWN, variant: DropdownVariant::LANGUAGE), + new ComponentContext('dropdown', variant: 'language'), + ["{$name}.dropdown.language"], + static fn(Config $config, ComponentContext $context): string => Dropdown::tag() + ->config($config, $context) + ->id('selector-language') + ->items( + Item::tag() + ->label('English') + ->link('/language/en') + ->active(), + ) + ->render(), + ]; + yield "{$label}: Field (control: FieldComponentName::FIELD_CONTROL_EMAIL)" => [ + $theme, + new ComponentContext(FieldComponentName::FIELD_CONTROL_EMAIL), + new ComponentContext('field.control.email'), + ["{$name}.field.control.email"], + static fn(Config $config, ComponentContext $context): string => InputEmail::tag() + ->config($config, $context) + ->id('email') + ->render(), + ]; + yield "{$label}: NavBar (variant: NavBarVariant::ALIGN_RIGHT)" => [ + $theme, + new ComponentContext(ComponentName::NAVBAR, variant: NavBarVariant::ALIGN_RIGHT), + new ComponentContext('navbar', variant: 'align-right'), + ["{$name}.navbar.align-right"], + static fn(Config $config, ComponentContext $context): string => NavBar::tag() + ->config($config, $context) + ->brandLink('/') + ->brandText('UI Awesome') + ->menu( + Menu::tag() + ->id('main-menu') + ->items( + Item::tag() + ->label('Home') + ->link('/'), + Dropdown::tag() + ->containerTag(false) + ->id(null) + ->items( + Item::tag() + ->label('Action') + ->link('#'), + ), + ), + ) + ->render(), + ]; + yield "{$label}: Toggle (variant: ToggleVariant::MENU)" => [ + $theme, + new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::MENU), + new ComponentContext('toggle', variant: 'menu'), + $menuToggleRecipes, + static fn(Config $config, ComponentContext $context): string => Toggle::tag() + ->config($config, $context) + ->dataAttributes(['bs-target' => '#main-menu']) + ->render(), + ]; + yield "{$label}: Toggle (variant: ToggleVariant::MENU_DROPDOWN)" => [ + $theme, + new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::MENU_DROPDOWN), + new ComponentContext('toggle', variant: 'menu-dropdown'), + ["{$name}.toggle.menu-dropdown"], + static fn(Config $config, ComponentContext $context): string => Toggle::tag() + ->config($config, $context) + ->content('Docs') + ->render(), + ]; + yield "{$label}: Toggle (variant: ToggleVariant::SELECTOR_THEME)" => [ + $theme, + new ComponentContext(ComponentName::TOGGLE, variant: ToggleVariant::SELECTOR_THEME), + new ComponentContext('toggle', variant: 'selector-theme'), + ["{$name}.toggle.selector-theme"], + static fn(Config $config, ComponentContext $context): string => Toggle::tag() + ->config($config, $context) + ->id('theme-selector') + ->render(), + ]; + } + } +}