Skip to content

[#17] Added a toggle switch widget.#28

Merged
AlexSkrypnyk merged 3 commits into
mainfrom
feature/17-toggle-widget
Jul 10, 2026
Merged

[#17] Added a toggle switch widget.#28
AlexSkrypnyk merged 3 commits into
mainfrom
feature/17-toggle-widget

Conversation

@AlexSkrypnyk

@AlexSkrypnyk AlexSkrypnyk commented Jul 10, 2026

Copy link
Copy Markdown
Member

Closes #17

Summary

Adds a toggle widget: a compact inline switch between two labeled values, for on/off settings with custom labels (Enabled/Disabled, Public/Private) where confirm's yes/no framing doesn't fit and a full select list is more than the choice needs. It's a two-option select in its data and return type, driven by confirm-style interaction: arrows or Space flip between the two states, Enter accepts, and typing the first letter of a label jumps straight to that side. Headless collection and the generated JSON schema treat it like any other option field, so a value that isn't one of the two options gets rejected the same way a bad select answer would.

Changes

  • Widget: new ToggleWidget (two-option data model like SelectWidget, confirm-style flip interaction on Left/Right/Up/Down/Space, first-letter selection, inline rendering that reuses the existing radio() and label-highlight theme atoms - Unicode and ASCII, no new theme method).
  • Config & builder: FieldType::Toggle, PanelBuilder::toggle(), WidgetFactory wiring, and an implicit default of the first option's value when none is declared, since a toggle always holds one of its two values.
  • Validation: Form::build() asserts a toggle field declares exactly two options and that any non-closure default - of any type - is one of the option values (an invalid default fails fast rather than being silently coerced); SchemaValidator::checkOptions() rejects a headless value that isn't one of the two options, same as select/search. Option membership is compared against each Option::value, so numeric-string values like '0' and '1' are not coerced by array keys.
  • Dependencies: declared ext-mbstring in composer.json. The library already relies on mbstring across the render layer (Ansi, Box, DefaultTheme); the toggle's first-letter selection uses it too, so the requirement is now explicit.
  • Tests: new ToggleWidgetTest (flip via Space and each arrow, first-letter selection including the label-collision case, accept/cancel, default seeding, Unicode/ASCII rendering) plus additions to FormTest (two-options and invalid-default ConfigExceptions across string/bool/int/null, numeric-string options), WidgetFactoryTest, InputResolverTest (headless coercion), and SchemaGeneratorTest/SchemaValidatorTest (option membership including numeric-string values).
  • Docs & assets: README ### Toggle subsection (widget count bumped to twelve), a playground/3-widgets/widget-toggle.php demo, the montage scripts (show.php, widgets.php) and docs/util/update-assets.php updated, and regenerated widget-toggle*.svg / widgets*.svg assets.

Screenshots

N/A - terminal TUI library, no web UI. The widget's rendered appearance is captured in the committed docs/assets/widget-toggle*.svg assets and shown in the README.

Before / After

Before (eleven widget types):
  text  number  textarea  password  select  multiselect
  suggest  search  multisearch  confirm  pause

  An on/off setting with custom labels (Enabled/Disabled) needs
  either a mislabeled confirm (locked to yes/no) or a full
  select list built for just two options.

After (twelve widget types):
  text  number  textarea  password  select  multiselect
  suggest  search  multisearch  confirm  toggle  pause

  A dedicated inline toggle, flipped with arrows/Space or by
  typing a label's first letter:

    ● Enabled   ○ Disabled

Summary by CodeRabbit

  • New Features

    • Added a Toggle widget for selecting between two labeled options.
    • Added fluent form configuration support for toggle fields.
    • Supports keyboard toggling, direct selection, custom defaults, cancellation, and Unicode or ASCII rendering.
    • Added the Toggle widget to interactive showcases and playground examples.
  • Documentation

    • Updated widget documentation from eleven to twelve types.
    • Added usage instructions, PHP examples, and rendering examples for Toggle.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a two-option Toggle field and widget with keyboard interaction, defaults, validation, schema support, factory wiring, tests, playground examples, screenshot automation, and README documentation.

Changes

Toggle widget

Layer / File(s) Summary
Toggle field contracts and validation
src/Config/FieldType.php, src/Builder/..., src/Schema/SchemaValidator.php, tests/phpunit/Unit/Builder/FormTest.php, tests/phpunit/Unit/Schema/*, tests/phpunit/Unit/Resolver/InputResolverTest.php
Adds the Toggle field type, fluent builder method, first-option defaults, two-option configuration checks, default membership checks, and schema/input validation coverage.
Toggle widget runtime and factory wiring
src/Widget/ToggleWidget.php, src/Widget/WidgetFactory.php, tests/phpunit/Unit/Widget/*
Implements toggle selection, keyboard handling, rendering, acceptance, cancellation, ASCII output, factory creation, and unit tests.
Toggle examples and presentation
playground/3-widgets/*, docs/util/update-assets.php, README.md
Adds interactive examples, screenshot automation, widget sizing, and Toggle documentation with usage and rendering examples.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Terminal
  participant KeyParser
  participant ToggleWidget
  participant WidgetRunner
  Terminal->>KeyParser: read and parse keypress
  KeyParser->>ToggleWidget: dispatch parsed key
  ToggleWidget->>ToggleWidget: flip or select value
  ToggleWidget->>WidgetRunner: accept or cancel state
  WidgetRunner->>Terminal: render selected value
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR appears to satisfy the toggle widget requirements, including builder support, headless/schema handling, rendering modes, tests, and README docs.
Out of Scope Changes check ✅ Passed The changes are centered on the toggle widget and supporting docs, tests, and assets, with no clear unrelated additions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding a toggle switch widget.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/17-toggle-widget

Comment @coderabbitai help to get the list of available commands.

@AlexSkrypnyk AlexSkrypnyk added the A1 Board worker 1 label Jul 10, 2026
@github-actions

This comment has been minimized.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.03922% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.92%. Comparing base (6486a4c) to head (ec7e7bd).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/Builder/Form.php 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #28      +/-   ##
==========================================
- Coverage   98.96%   98.92%   -0.04%     
==========================================
  Files          58       59       +1     
  Lines        1445     1493      +48     
==========================================
+ Hits         1430     1477      +47     
- Misses         15       16       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Builder/Form.php (1)

292-337: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Apply toggle validation to direct Config inputs too.

Tui::__construct() accepts an already-built Config and bypasses Form::build(). Callers can therefore construct a Config containing malformed toggle fields without the two-option/default invariant being checked. Move this validation into a shared Config validation path or validate incoming Config objects at the TUI boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Builder/Form.php` around lines 292 - 337, Ensure toggle invariants are
enforced for already-built Config instances passed through Tui::__construct(),
not only during Form::build(). Move assertToggleOptions() or equivalent logic
into a shared Config validation path, or invoke it when Tui receives a Config,
while preserving the two-option count and literal-default checks; update
relevant callers/tests accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@playground/3-widgets/widget-toggle.php`:
- Line 35: Update the implode separator in the terminal rendering call to use
the project-approved newline constant or helper, such as PHP_EOL, instead of the
double-quoted "\n" string; follow the single-quote convention for any remaining
string literals.

In `@src/Builder/FieldBuilder.php`:
- Around line 368-371: Update the toggle default logic in FieldBuilder to return
the first option’s value rather than its key. Use the first element from
$this->options and preserve its original string type, avoiding array_key_first()
and numeric-string key coercion before the value reaches WidgetFactory and
schema output.
- Around line 363-366: Update the toggle-default validation used by
Form::assertToggleOptions() so every non-closure default is checked against the
toggle option keys, including null, numeric, and array values, instead of
validating only strings. Reject any unmatched default before it reaches
Engine::resolveInitial() or SchemaGenerator, while preserving closure defaults;
ensure WidgetFactory receives only valid option-key defaults.

In `@src/Builder/Form.php`:
- Around line 331-335: Update the toggle default validation in FieldBuilder to
validate every non-closure default, not just strings: treat the default as valid
only when it matches an option key, while allowing closures for runtime
resolution. Preserve the existing ConfigException details and add regression
tests covering boolean and numeric invalid defaults.

In `@src/Schema/SchemaValidator.php`:
- Around line 161-164: Update SchemaValidator::checkOptions() so numeric-string
option keys remain valid: normalize array_keys($field->options) to strings
before the strict in_array() comparison, or validate against each Option::value
while preserving string types. Keep the existing error message and behavior for
non-matching values.

In `@src/Widget/ToggleWidget.php`:
- Around line 108-113: Declare ext-mbstring as a required Composer platform
dependency in composer.json, since ToggleWidget::applyChar() uses
mb_strtolower() and mb_substr() and otherwise fails when the extension is
unavailable.

---

Outside diff comments:
In `@src/Builder/Form.php`:
- Around line 292-337: Ensure toggle invariants are enforced for already-built
Config instances passed through Tui::__construct(), not only during
Form::build(). Move assertToggleOptions() or equivalent logic into a shared
Config validation path, or invoke it when Tui receives a Config, while
preserving the two-option count and literal-default checks; update relevant
callers/tests accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: db5e71d7-647e-406a-aa22-0796a03b194e

📥 Commits

Reviewing files that changed from the base of the PR and between 6486a4c and 6a74573.

⛔ Files ignored due to path filters (8)
  • docs/assets/widget-toggle-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-toggle-ascii.svg is excluded by !**/*.svg
  • docs/assets/widget-toggle-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widget-toggle.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets-ascii.svg is excluded by !**/*.svg
  • docs/assets/widgets-no-ansi.svg is excluded by !**/*.svg
  • docs/assets/widgets.svg is excluded by !**/*.svg
📒 Files selected for processing (18)
  • README.md
  • docs/util/update-assets.php
  • playground/3-widgets/show.php
  • playground/3-widgets/widget-toggle.php
  • playground/3-widgets/widgets.php
  • src/Builder/FieldBuilder.php
  • src/Builder/Form.php
  • src/Builder/PanelBuilder.php
  • src/Config/FieldType.php
  • src/Schema/SchemaValidator.php
  • src/Widget/ToggleWidget.php
  • src/Widget/WidgetFactory.php
  • tests/phpunit/Unit/Builder/FormTest.php
  • tests/phpunit/Unit/Resolver/InputResolverTest.php
  • tests/phpunit/Unit/Schema/SchemaGeneratorTest.php
  • tests/phpunit/Unit/Schema/SchemaValidatorTest.php
  • tests/phpunit/Unit/Widget/ToggleWidgetTest.php
  • tests/phpunit/Unit/Widget/WidgetFactoryTest.php

Comment thread playground/3-widgets/widget-toggle.php
Comment thread src/Builder/FieldBuilder.php
Comment thread src/Builder/FieldBuilder.php Outdated
Comment thread src/Builder/Form.php Outdated
Comment thread src/Schema/SchemaValidator.php Outdated
Comment thread src/Widget/ToggleWidget.php
@github-actions

Copy link
Copy Markdown
Code Coverage Report:
  2026-07-10 11:26:27

 Summary:
  Classes: 88.14% (52/59)
  Methods: 96.79% (362/374)
  Lines:   98.93% (1477/1493)

DrevOps\Tui\Answers\Answer
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Answers\Answers
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Answers\SummaryFormatter
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Builder\FieldBuilder
  Methods:  88.89% (16/18)   Lines:  93.22% ( 55/ 59)
DrevOps\Tui\Builder\Form
  Methods:  92.86% (13/14)   Lines:  98.33% ( 59/ 60)
DrevOps\Tui\Builder\PanelBuilder
  Methods: 100.00% (17/17)   Lines: 100.00% ( 29/ 29)
DrevOps\Tui\Condition\CompositeCondition
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Condition\Condition
  Methods: 100.00% (10/10)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Config\Config
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 17/ 17)
DrevOps\Tui\Config\Field
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  2/  2)
DrevOps\Tui\Config\Option
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Config\Panel
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Derive\Derive
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 14/ 14)
DrevOps\Tui\Derive\Deriver
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% ( 13/ 13)
DrevOps\Tui\Derive\Transform
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Discovery\AbstractDiscover
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Discovery\Dotenv
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Discovery\JsonValue
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Discovery\PathExists
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  3/  3)
DrevOps\Tui\Discovery\Scan
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Engine\Engine
  Methods: 100.00% (12/12)   Lines: 100.00% ( 89/ 89)
DrevOps\Tui\Handler\HandlerRegistry
  Methods:  85.71% ( 6/ 7)   Lines:  95.45% ( 21/ 22)
DrevOps\Tui\Input\ArrayKeyStream
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Input\Key
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Input\KeyParser
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 68/ 68)
DrevOps\Tui\Render\Ansi
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Render\Box
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\ExternalEditor
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Render\Navigator
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 15/ 15)
DrevOps\Tui\Render\PanelController
  Methods: 100.00% (16/16)   Lines: 100.00% ( 92/ 92)
DrevOps\Tui\Render\Scroller
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 12/ 12)
DrevOps\Tui\Render\Terminal
  Methods: 100.00% (12/12)   Lines: 100.00% ( 35/ 35)
DrevOps\Tui\Render\TerminalControl
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% (  9/  9)
DrevOps\Tui\Render\Viewport
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Resolver\InputResolver
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Schema\AgentHelp
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% ( 16/ 16)
DrevOps\Tui\Schema\SchemaGenerator
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% ( 25/ 25)
DrevOps\Tui\Schema\SchemaValidator
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 40/ 40)
DrevOps\Tui\Theme\DefaultTheme
  Methods:  91.80% (56/61)   Lines:  96.77% (180/186)
DrevOps\Tui\Theme\ThemeManager
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  8/  8)
DrevOps\Tui\Tui
  Methods: 100.00% (12/12)   Lines: 100.00% ( 26/ 26)
DrevOps\Tui\Widget\AbstractWidget
  Methods: 100.00% (10/10)   Lines: 100.00% ( 20/ 20)
DrevOps\Tui\Widget\ConfirmWidget
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 23/ 23)
DrevOps\Tui\Widget\MultiSearchWidget
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  1/  1)
DrevOps\Tui\Widget\MultiSelectWidget
  Methods: 100.00% ( 9/ 9)   Lines: 100.00% ( 64/ 64)
DrevOps\Tui\Widget\NumberWidget
  Methods: 100.00% ( 2/ 2)   Lines: 100.00% (  7/  7)
DrevOps\Tui\Widget\PasswordDisplay
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)
DrevOps\Tui\Widget\PasswordWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 38/ 38)
DrevOps\Tui\Widget\PauseWidget
  Methods: 100.00% ( 3/ 3)   Lines: 100.00% (  6/  6)
DrevOps\Tui\Widget\SearchWidget
  Methods:  80.00% ( 4/ 5)   Lines:  94.59% ( 35/ 37)
DrevOps\Tui\Widget\SelectWidget
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 19/ 19)
DrevOps\Tui\Widget\SuggestWidget
  Methods: 100.00% ( 5/ 5)   Lines: 100.00% ( 37/ 37)
DrevOps\Tui\Widget\TextWidget
  Methods: 100.00% ( 6/ 6)   Lines: 100.00% ( 29/ 29)
DrevOps\Tui\Widget\TextareaWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 48/ 48)
DrevOps\Tui\Widget\ToggleWidget
  Methods: 100.00% ( 7/ 7)   Lines: 100.00% ( 30/ 30)
DrevOps\Tui\Widget\WidgetFactory
  Methods: 100.00% ( 4/ 4)   Lines: 100.00% ( 27/ 27)
DrevOps\Tui\Widget\WidgetRunner
  Methods: 100.00% ( 1/ 1)   Lines: 100.00% (  5/  5)

@AlexSkrypnyk AlexSkrypnyk merged commit db1d39d into main Jul 10, 2026
9 checks passed
@AlexSkrypnyk AlexSkrypnyk deleted the feature/17-toggle-widget branch July 10, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A1 Board worker 1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a toggle switch widget

1 participant