Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
114 changes: 15 additions & 99 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<div class="alert alert-success" id="alert-saved" role="alert">
Your changes have been saved.
</div>
```

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
<div class="alert alert-danger alert-dismissible fade show" id="alert-session" role="alert">
Your session expired.
<button class="btn-close" type="button" aria-label="Close" data-bs-dismiss="alert">
</button>
</div>
```

#### Bootstrap 6 alpha

`Bootstrap6` targets Bootstrap **6.0.0-alpha.1**. Support is experimental and tied to that alpha, not to a stable
Expand All @@ -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(
Expand All @@ -122,42 +110,6 @@ echo NavBar::tag()
->render();
```

Output:

```text
<nav class="navbar navbar-expand bg-1">
<div class="container-fluid">
<a class="navbar-brand" href="/">
UI Awesome
</a>
<div class="flex-grow-1" id="main-menu">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">
Home
</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/docs" aria-current="page">
Docs
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/pricing">
Pricing
</a>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="/enterprise">
Enterprise
</a>
</li>
</ul>
</div>
</div>
</nav>
```

#### Navbar dropdown

A `Dropdown` nested in the navbar menu receives the theme's dropdown toggle. `toggleLabel()` replaces only the toggle
Expand All @@ -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(
Expand All @@ -188,52 +140,15 @@ echo NavBar::tag()
->render();
```

Output:

```text
<nav class="navbar navbar-expand bg-1">
<div class="container-fluid">
<a class="navbar-brand" href="/">
UI Awesome
</a>
<div class="flex-grow-1" id="main-menu">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">
Home
</a>
</li>
<li class="nav-item">
<a class="nav-link menu-toggle" href="#" role="button" aria-expanded="false" data-bs-toggle="menu">
Docs
</a>
<ul class="menu">
<li>
<a class="menu-item" href="/docs/guides">
Guides
</a>
</li>
<li>
<a class="menu-item" href="/docs/api">
API reference
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
```

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');
```

Expand All @@ -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();
```
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
21 changes: 15 additions & 6 deletions ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
],
);
75 changes: 45 additions & 30 deletions src/Bootstrap5.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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,
};

Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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');
}

Expand Down Expand Up @@ -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'),
Expand Down Expand Up @@ -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,
};
}
Expand Down
Loading
Loading