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
-
-Your changes have been saved.
-
-```
-
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
-
-Your session expired.
-
-
-```
-
#### 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(),
+ ];
+ }
+ }
+}