Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.18% covered (success)
96.18%
277 / 288
90.91% covered (success)
90.91%
20 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
Codex
96.18% covered (success)
96.18%
277 / 288
90.91% covered (success)
90.91%
20 / 22
24
0.00% covered (danger)
0.00%
0 / 1
 __construct
37.50% covered (danger)
37.50%
3 / 8
0.00% covered (danger)
0.00%
0 / 1
2.98
 getFallbackLocalizer
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 accordion
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 button
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 card
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 checkbox
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 field
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 htmlSnippet
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 infoChip
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 label
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
1
 message
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 pager
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
1
 progressBar
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 radio
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
1
 select
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 tab
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 table
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
1
 tabs
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 textArea
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 textInput
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
1
 thumbnail
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 toggleSwitch
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare( strict_types = 1 );
3
4/**
5 * Codex.php
6 *
7 * This class provides factory methods to create instances of various builders,
8 * including Accordion, Button, Card, Checkbox, and others. These builders facilitate
9 * the creation of standardized UI components adhering to the Codex design principles.
10 *
11 * Each builder follows the builder pattern, allowing for easy and fluent creation
12 * and customization of components used across Wikimedia projects.
13 *
14 * @category Utility
15 * @package  Codex\Utility
16 * @since    0.1.0
17 * @author   Doğu Abaris <abaris@null.net>
18 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
19 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
20 */
21
22namespace Wikimedia\Codex\Utility;
23
24use Krinkle\Intuition\Intuition;
25use MediaWiki\Context\RequestContext;
26use Wikimedia\Codex\Component\Accordion;
27use Wikimedia\Codex\Component\Button;
28use Wikimedia\Codex\Component\Card;
29use Wikimedia\Codex\Component\Checkbox;
30use Wikimedia\Codex\Component\Field;
31use Wikimedia\Codex\Component\HtmlSnippet;
32use Wikimedia\Codex\Component\InfoChip;
33use Wikimedia\Codex\Component\Label;
34use Wikimedia\Codex\Component\Message;
35use Wikimedia\Codex\Component\Pager;
36use Wikimedia\Codex\Component\ProgressBar;
37use Wikimedia\Codex\Component\Radio;
38use Wikimedia\Codex\Component\Select;
39use Wikimedia\Codex\Component\Tab;
40use Wikimedia\Codex\Component\Table;
41use Wikimedia\Codex\Component\Tabs;
42use Wikimedia\Codex\Component\TextArea;
43use Wikimedia\Codex\Component\TextInput;
44use Wikimedia\Codex\Component\Thumbnail;
45use Wikimedia\Codex\Component\ToggleSwitch;
46use Wikimedia\Codex\Contract\Component;
47use Wikimedia\Codex\Contract\ILocalizer;
48use Wikimedia\Codex\Infrastructure\CodexServices;
49use Wikimedia\Codex\Localization\IntuitionLocalization;
50use Wikimedia\Codex\Localization\MediaWikiLocalization;
51use Wikimedia\Codex\Renderer\AccordionRenderer;
52use Wikimedia\Codex\Renderer\ButtonRenderer;
53use Wikimedia\Codex\Renderer\CardRenderer;
54use Wikimedia\Codex\Renderer\CheckboxRenderer;
55use Wikimedia\Codex\Renderer\FieldRenderer;
56use Wikimedia\Codex\Renderer\InfoChipRenderer;
57use Wikimedia\Codex\Renderer\LabelRenderer;
58use Wikimedia\Codex\Renderer\MessageRenderer;
59use Wikimedia\Codex\Renderer\PagerRenderer;
60use Wikimedia\Codex\Renderer\ProgressBarRenderer;
61use Wikimedia\Codex\Renderer\RadioRenderer;
62use Wikimedia\Codex\Renderer\SelectRenderer;
63use Wikimedia\Codex\Renderer\TableRenderer;
64use Wikimedia\Codex\Renderer\TabsRenderer;
65use Wikimedia\Codex\Renderer\TextAreaRenderer;
66use Wikimedia\Codex\Renderer\TextInputRenderer;
67use Wikimedia\Codex\Renderer\ThumbnailRenderer;
68use Wikimedia\Codex\Renderer\ToggleSwitchRenderer;
69
70/**
71 * Codex UI
72 *
73 * This class provides methods for creating instances of various builders, each
74 * corresponding to a UI component in the Codex design system. These builders allow
75 * the creation and customization of Codex components.
76 *
77 * @category Utility
78 * @package  Codex\Utility
79 * @since    0.1.0
80 * @author   Doğu Abaris <abaris@null.net>
81 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
82 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
83 */
84class Codex {
85
86    /**
87     * The CodexServices instance that manages services.
88     */
89    private CodexServices $services;
90
91    private ILocalizer $localizer;
92
93    /**
94     * Create a new Codex instance. This object can be used to create and render Codex components.
95     *
96     * @param ILocalizer|null $localizer Localizer object for i18n.
97     *   NOTE: Omitting this parameter is deprecated and will not be supported in version 1.0.
98     */
99    public function __construct( ?ILocalizer $localizer = null ) {
100        $this->services = CodexServices::getInstance();
101        if ( $localizer === null ) {
102            trigger_error(
103                'Not passing a localizer to the Codex constructor is deprecated and will not be supported ' .
104                'in version 1.0.',
105                E_USER_DEPRECATED
106            );
107        }
108        $this->localizer = $localizer ?? $this->getFallbackLocalizer();
109    }
110
111    /**
112     * Derive a localizer from the global state, if one wasn't passed to the constructor.
113     */
114    private function getFallbackLocalizer(): ILocalizer {
115        if ( defined( 'MW_INSTALL_PATH' ) ) {
116            $messageLocalizer = RequestContext::getMain();
117            return new MediaWikiLocalization( $messageLocalizer );
118        } else {
119            $intuition = new Intuition( 'codex' );
120            $intuition->registerDomain( 'codex', __DIR__ . '/../../i18n' );
121            return new IntuitionLocalization( $intuition );
122        }
123    }
124
125    /**
126     * Build an Accordion component.
127     *
128     * @since 0.1.0
129     * @param string|HtmlSnippet $title The accordion's header title.
130     * @param string|HtmlSnippet $description Additional text under the title.
131     * @param string|HtmlSnippet $content The content shown when the accordion is expanded.
132     * @param bool $open Determines if the accordion is expanded by default.
133     * @param string $separation The visual prominence of the separation ('none', 'minimal', 'divider', 'outline').
134     * @param array $attributes Additional HTML attributes for the <details> element.
135     * @return Accordion The Accordion component instance.
136     */
137    public function accordion(
138        string|HtmlSnippet $title = '',
139        string|HtmlSnippet $description = '',
140        string|HtmlSnippet $content = '',
141        bool $open = false,
142        string $separation = 'none',
143        array $attributes = []
144    ): Accordion {
145        return new Accordion(
146            new AccordionRenderer(
147                $this->services->getService( 'Sanitizer' ),
148                $this->services->getService( 'TemplateParser' )
149            ),
150            $title,
151            $description,
152            $content,
153            $open,
154            $separation,
155            $attributes
156        );
157    }
158
159    /**
160     * Build a Button component.
161     *
162     * @since 0.1.0
163     * @param string|HtmlSnippet $label The text label displayed on the button.
164     * @param string $action The visual action style of the button ('default', 'progressive' or 'destructive')
165     * @param string $size The size of the button ('medium' or 'large').
166     * @param string $type The type of the button ('button', 'submit' or 'reset').
167     * @param string $weight The visual prominence of the button ('normal', 'primary' or 'quiet')
168     * @param string|null $iconClass The CSS class for an icon, if any.
169     * @param bool $iconOnly Indicates if the button is icon-only (no text).
170     * @param bool $disabled Indicates if the button is disabled.
171     * @param array $attributes Additional HTML attributes for the button element.
172     * @param string|null $href The URL the button should redirect to.
173     * @return Button The Button component instance.
174     */
175    public function button(
176        string|HtmlSnippet $label = '',
177        string $action = 'default',
178        string $size = 'medium',
179        string $type = 'button',
180        string $weight = 'normal',
181        ?string $iconClass = null,
182        bool $iconOnly = false,
183        bool $disabled = false,
184        array $attributes = [],
185        ?string $href = null,
186    ): Button {
187        return new Button(
188            new ButtonRenderer(
189                $this->services->getService( 'Sanitizer' ),
190                $this->services->getService( 'TemplateParser' )
191            ),
192            $label,
193            $action,
194            $size,
195            $type,
196            $weight,
197            $iconClass,
198            $iconOnly,
199            $disabled,
200            $attributes,
201            $href
202        );
203    }
204
205    /**
206     * Builds a Card component
207     *
208     * @since 0.1.0
209     * @param string|HtmlSnippet $title The title text displayed on the card.
210     * @param string|HtmlSnippet $description The description text displayed on the card.
211     * @param string|HtmlSnippet $supportingText The supporting text displayed on the card.
212     * @param string $url The URL the card links to, if clickable.
213     * @param ?string $iconClass The CSS class for an optional icon in the card.
214     * @param ?Thumbnail $thumbnail The Thumbnail object representing the card's thumbnail.
215     * @param array $attributes Additional HTML attributes for the card element.
216     * @return Card The Card component instance.
217     */
218    public function card(
219        string|HtmlSnippet $title = '',
220        string|HtmlSnippet $description = '',
221        string|HtmlSnippet $supportingText = '',
222        string $url = '',
223        ?string $iconClass = null,
224        ?Thumbnail $thumbnail = null,
225        array $attributes = [],
226    ): Card {
227        return new Card(
228            new CardRenderer(
229                $this->services->getService( 'Sanitizer' ),
230                $this->services->getService( 'TemplateParser' )
231            ),
232            $title,
233            $description,
234            $supportingText,
235            $url,
236            $iconClass,
237            $thumbnail,
238            $attributes
239        );
240    }
241
242    /**
243     * Build a Checkbox component
244     *
245     * @since 0.1.0
246     * @param string $inputId The ID for the checkbox input.
247     * @param string $name The name attribute for the checkbox input.
248     * @param ?Label $label The Label object associated with the checkbox.
249     * @param string $value The value associated with the checkbox input.
250     * @param bool $checked Indicates if the checkbox is selected by default.
251     * @param bool $disabled Indicates if the checkbox is disabled.
252     * @param bool $inline Indicates if the checkbox should be displayed inline.
253     * @param array $inputAttributes Additional HTML attributes for the input element.
254     * @param array $wrapperAttributes Additional HTML attributes for the wrapper element.
255     * @return Checkbox The Checkbox component instance.
256     */
257    public function checkbox(
258        string $inputId = '',
259        string $name = '',
260        ?Label $label = null,
261        string $value = '',
262        bool $checked = false,
263        bool $disabled = false,
264        bool $inline = false,
265        array $inputAttributes = [],
266        array $wrapperAttributes = []
267    ): Checkbox {
268        return new Checkbox(
269            new CheckboxRenderer(
270                $this->services->getService( 'Sanitizer' ),
271                $this->services->getService( 'TemplateParser' ),
272                $this->localizer
273            ),
274            $inputId,
275            $name,
276            $label,
277            $value,
278            $checked,
279            $disabled,
280            $inline,
281            $inputAttributes,
282            $wrapperAttributes
283        );
284    }
285
286    /**
287     * Build a Field component
288     *
289     * @since 0.1.0
290     * @param ?Label $label The label for the field or fieldset.
291     * @param bool $isFieldset Indicates if fields are wrapped in a fieldset.
292     * @param array<Component|HtmlSnippet|string> $fields An array of fields.
293     *   Strings are interpreted as raw HTML. Passing in strings is deprecated.
294     * @param array $attributes Additional HTML attributes for the wrapper div or fieldset.
295     * @return Field The Field component instance.
296     */
297    public function field(
298        ?Label $label = null,
299        bool $isFieldset = false,
300        array $fields = [],
301        array $attributes = []
302    ): Field {
303        return new Field(
304            new FieldRenderer(
305                $this->services->getService( 'Sanitizer' ),
306                $this->services->getService( 'TemplateParser' ),
307                $this->localizer,
308                $this
309            ),
310            $label,
311            $isFieldset,
312            $fields,
313            $attributes
314        );
315    }
316
317    /**
318     * Returns an HTMLSnippet that encapsulates the given HTML string
319     *
320     * @since 0.1.0
321     * @param string $html HTML string
322     * @return HtmlSnippet
323     */
324    public function htmlSnippet( string $html = '' ): HtmlSnippet {
325        return new HtmlSnippet( $html );
326    }
327
328    /**
329     * Build an InfoChip component
330     *
331     * @since 0.1.0
332     * @param string|HtmlSnippet $text The text displayed inside the InfoChip.
333     * @param string $status The status type of the InfoChip, which determines chip's visual style.
334     *   This can be 'notice', 'warning', 'error', or 'success'.
335     * @param ?string $icon The CSS class for a custom icon, if any.
336     *   Only used if $status is set to 'notice', ignored otherwise.
337     * @param array $attributes Additional HTML attributes for the InfoChip element.
338     * @return InfoChip The InfoChip component instance.
339     */
340    public function infoChip(
341        string|HtmlSnippet $text = '',
342        string $status = 'notice',
343        ?string $icon = null,
344        array $attributes = []
345    ): InfoChip {
346        return new InfoChip(
347            new InfoChipRenderer(
348                $this->services->getService( 'Sanitizer' ),
349                $this->services->getService( 'TemplateParser' )
350            ),
351            $text,
352            $status,
353            $icon,
354            $attributes
355        );
356    }
357
358    /**
359     * Build a Label component
360     *
361     * @since 0.1.0
362     * @param string|HtmlSnippet $labelText The text displayed inside the label.
363     * @param string $inputId The ID of the input/control this label is associated with.
364     * @param bool $optional Indicates whether the associated input field is optional.
365     * @param bool $visuallyHidden Indicates whether the label should be visually hidden.
366     * @param bool $isLegend Indicates if the label should be rendered as a `<legend>` element.
367     * @param string|HtmlSnippet $description The description text for the label.
368     * @param string|null $descriptionId The ID of the description element.
369     * @param bool $disabled Indicates whether the label is for a disabled input.
370     * @param string|null $iconClass The CSS class for an icon before the label text.
371     * @param array $attributes Additional HTML attributes for the label element.
372     * @return Label The Label component instance.
373     */
374    public function label(
375        string|HtmlSnippet $labelText = '',
376        string $inputId = '',
377        bool $optional = false,
378        bool $visuallyHidden = false,
379        bool $isLegend = false,
380        string|HtmlSnippet $description = '',
381        ?string $descriptionId = null,
382        bool $disabled = false,
383        ?string $iconClass = null,
384        array $attributes = []
385    ): Label {
386        return new Label(
387            new LabelRenderer(
388                $this->services->getService( 'Sanitizer' ),
389                $this->services->getService( 'TemplateParser' ),
390                $this->localizer
391            ),
392            $labelText,
393            $inputId,
394            $optional,
395            $visuallyHidden,
396            $isLegend,
397            $description,
398            $descriptionId,
399            $disabled,
400            $iconClass,
401            $attributes
402        );
403    }
404
405    /**
406     * Build a Message component.
407     *
408     * @since 0.1.0
409     * @param string|HtmlSnippet $content The content displayed inside the message box.
410     * @param string $type The type of the message.
411     *   This can be 'notice', 'warning', 'error', or 'success'.
412     * @param bool $inline Whether the message should be displayed inline.
413     * @param string|HtmlSnippet $heading The heading displayed at the top of the message content.
414     * @param string $iconClass The CSS class name for the icon.
415     * @param array $attributes Additional HTML attributes for the message box.
416     * @return Message The Message component instance.
417     */
418    public function message(
419        string|HtmlSnippet $content = '',
420        string $type = 'notice',
421        bool $inline = false,
422        string|HtmlSnippet $heading = '',
423        string $iconClass = '',
424        array $attributes = []
425    ): Message {
426        return new Message(
427            new MessageRenderer(
428                $this->services->getService( 'Sanitizer' ),
429                $this->services->getService( 'TemplateParser' )
430            ),
431            $content,
432            $type,
433            $inline,
434            $heading,
435            $iconClass,
436            $attributes
437        );
438    }
439
440    /**
441     * Build a Pager component
442     *
443     * @since 0.1.0
444     * @param array $paginationSizeOptions Available pagination size options.
445     * @param int $paginationSizeDefault Default pagination size.
446     * @param int $totalPages Total number of pages in the dataset.
447     * @param int $totalResults Total number of results in the dataset.
448     * @param string $position Position of the pagination controls ('top', 'bottom' or 'both').
449     * @param int $limit Number of results per page.
450     * @param int|null $currentOffset Offset of the current page.
451     * @param int|null $nextOffset Offset for the next page.
452     * @param int|null $prevOffset Offset for the previous page.
453     * @param int|null $firstOffset Offset for the first page.
454     * @param int|null $lastOffset Offset for the last page.
455     * @param int $startOrdinal Start ordinal for the current page.
456     * @param int $endOrdinal End ordinal for the current page.
457     * @return Pager The Pager component instance.
458     */
459    public function pager(
460        array $paginationSizeOptions = [
461            5,
462            10,
463            25,
464            50,
465            100,
466        ],
467        int $paginationSizeDefault = 10,
468        int $totalPages = 1,
469        int $totalResults = 0,
470        string $position = 'bottom',
471        int $limit = 10,
472        ?int $currentOffset = null,
473        ?int $nextOffset = null,
474        ?int $prevOffset = null,
475        ?int $firstOffset = null,
476        ?int $lastOffset = null,
477        int $startOrdinal = 1,
478        int $endOrdinal = 1,
479        array $attributes = [],
480    ): Pager {
481        return new Pager(
482            new PagerRenderer(
483                $this->services->getService( 'Sanitizer' ),
484                $this->services->getService( 'TemplateParser' ),
485                $this->localizer,
486                $this,
487                $this->services->getService( 'ParamValidator' ),
488                $this->services->getService( 'ParamValidatorCallbacks' )
489            ),
490            $paginationSizeOptions,
491            $paginationSizeDefault,
492            $totalPages,
493            $totalResults,
494            $position,
495            $limit,
496            $currentOffset,
497            $nextOffset,
498            $prevOffset,
499            $firstOffset,
500            $lastOffset,
501            $startOrdinal,
502            $endOrdinal,
503            $attributes,
504        );
505    }
506
507    /**
508     * Resolves and returns the ProgressBar builder.
509     *
510     * @since 0.1.0
511     * @param string $label The ARIA label for the progress bar, important for accessibility.
512     * @param bool $inline Whether the progress bar is a smaller, inline variant.
513     * @param bool $disabled Whether the progress bar is disabled.
514     * @param array $attributes Additional HTML attributes for the outer `<div>` element of the progress bar.
515     * @return ProgressBar The ProgressBar instance.
516     */
517    public function progressBar(
518        string $label = '',
519        bool $inline = false,
520        bool $disabled = false,
521        array $attributes = []
522    ): ProgressBar {
523        return new ProgressBar(
524            new ProgressBarRenderer(
525                $this->services->getService( 'Sanitizer' ),
526                $this->services->getService( 'TemplateParser' )
527            ),
528            $label,
529            $inline,
530            $disabled,
531            $attributes
532        );
533    }
534
535    /**
536     * Build a Radio component
537     *
538     * @since 0.1.0
539     * @param string $inputId The ID for the radio input.
540     * @param string $name The name attribute for the radio input.
541     * @param ?Label $label The Label for the radio.
542     * @param string $value The value associated with the radio input.
543     * @param bool $checked Indicates if the radio is selected by default.
544     * @param bool $disabled Indicates if the radio is disabled.
545     * @param bool $inline Indicates if the radio should be displayed inline.
546     * @param array $inputAttributes Additional HTML attributes for the input element.
547     * @param array $wrapperAttributes Additional HTML attributes for the wrapper element.
548     * @return Radio The Radio component instance.
549     */
550    public function radio(
551        string $inputId = '',
552        string $name = '',
553        ?Label $label = null,
554        string $value = '',
555        bool $checked = false,
556        bool $disabled = false,
557        bool $inline = false,
558        array $inputAttributes = [],
559        array $wrapperAttributes = []
560    ): Radio {
561        return new Radio(
562            new RadioRenderer(
563                $this->services->getService( 'Sanitizer' ),
564                $this->services->getService( 'TemplateParser' ),
565                $this->localizer
566            ),
567            $inputId,
568            $name,
569            $label,
570            $value,
571            $checked,
572            $disabled,
573            $inline,
574            $inputAttributes,
575            $wrapperAttributes
576        );
577    }
578
579    /**
580     * Build a Select component
581     *
582     * @since 0.1.0
583     * @param array $options An array of options for the select element.
584     * @param array $optGroups An array of optGroups for grouping options.
585     * @param string|null $selectedOption The value of the selected option, if any.
586     * @param bool $disabled Indicates whether the select element is disabled.
587     * @param array $attributes Additional HTML attributes for the select element.
588     * @return Select The Select component instance.
589     */
590    public function select(
591        array $options = [],
592        array $optGroups = [],
593        ?string $selectedOption = null,
594        bool $disabled = false,
595        array $attributes = []
596    ): Select {
597        return new Select(
598            new SelectRenderer(
599                $this->services->getService( 'Sanitizer' ),
600                $this->services->getService( 'TemplateParser' )
601            ),
602            $options,
603            $optGroups,
604            $selectedOption,
605            $disabled,
606            $attributes
607        );
608    }
609
610    /**
611     * Build a Tab component.
612     *
613     * @since 0.1.0
614     * @param string $name The unique name of the tab.
615     * @param string $label The label of the tab.
616     * @param string|HtmlSnippet $content The content of the tab.
617     * @param bool $selected Whether the tab is selected by default.
618     * @param bool $disabled Whether the tab is disabled.
619     * @return Tab The Tab instance.
620     */
621    public function tab(
622        string $name = '',
623        string $label = '',
624        string|HtmlSnippet $content = '',
625        bool $selected = false,
626        bool $disabled = false
627    ): Tab {
628        return new Tab(
629            $name,
630            $label,
631            $content,
632            $selected,
633            $disabled
634        );
635    }
636
637    /**
638     * Build a Table component
639     *
640     * @since 0.1.0
641     * @param string $caption The caption for the table. Important for accessibility.
642     * @param bool $hideCaption Whether the caption is hidden.
643     * @param array $columns Array of columns.
644     * @param array $data Array of row data.
645     * @param bool $useRowHeaders Whether to use row headers.
646     * @param string|HtmlSnippet|null $headerContent The header content.
647     * @param array $sort Array of sorting configurations.
648     * @param ?string $currentSortColumn The current sorted column.
649     * @param string $currentSortDirection The current sort direction.
650     * @param bool $showVerticalBorders Whether to show vertical borders.
651     * @param bool $paginate Whether pagination is enabled.
652     * @param int $totalRows The total number of rows.
653     * @param string $paginationPosition The pagination position ('top', 'bottom' or 'both')
654     * @param ?Pager $pager The pager for handling pagination.
655     * @param string|HtmlSnippet|null $footer The footer content.
656     * @param array $attributes Additional HTML attributes.
657     * @return Table The Table component instance.
658     */
659    public function table(
660        string $caption = '',
661        bool $hideCaption = false,
662        array $columns = [],
663        array $data = [],
664        bool $useRowHeaders = false,
665        // phpcs:ignore MediaWiki.Usage.NullableType.ExplicitNullableTypes
666        string|HtmlSnippet|null $headerContent = null,
667        array $sort = [],
668        ?string $currentSortColumn = null,
669        string $currentSortDirection = Table::SORT_ASCENDING,
670        bool $showVerticalBorders = false,
671        bool $paginate = false,
672        int $totalRows = 0,
673        string $paginationPosition = 'bottom',
674        ?Pager $pager = null,
675        // phpcs:ignore MediaWiki.Usage.NullableType.ExplicitNullableTypes
676        string|HtmlSnippet|null $footer = null,
677        array $attributes = []
678    ): Table {
679        return new Table(
680            new TableRenderer(
681                $this->services->getService( 'Sanitizer' ),
682                $this->services->getService( 'TemplateParser' ),
683                $this->localizer,
684                $this->services->getService( 'ParamValidator' ),
685                $this->services->getService( 'ParamValidatorCallbacks' )
686            ),
687            $caption,
688            $hideCaption,
689            $columns,
690            $data,
691            $useRowHeaders,
692            $headerContent,
693            $sort,
694            $currentSortColumn,
695            $currentSortDirection,
696            $showVerticalBorders,
697            $paginate,
698            $totalRows,
699            $paginationPosition,
700            $pager,
701            $footer,
702            $attributes
703        );
704    }
705
706    /**
707     * Build a Tabs component.
708     *
709     * @since 0.1.0
710     * @param Tab[] $tabs An array of Tab component objects.
711     * @param array $attributes Additional HTML attributes for the tabs component.
712     * @return Tabs The Tabs component instance.
713     */
714    public function tabs(
715        array $tabs = [],
716        array $attributes = []
717    ): Tabs {
718        return new Tabs(
719            new TabsRenderer(
720                $this->services->getService( 'Sanitizer' ),
721                $this->services->getService( 'TemplateParser' ),
722                $this->services->getService( 'ParamValidator' ),
723                $this->services->getService( 'ParamValidatorCallbacks' )
724            ),
725            $tabs,
726            $attributes
727        );
728    }
729
730    /**
731     * Build a TextArea component
732     *
733     * @since 0.1.0
734     * @param string $name The name attribute for the textarea.
735     * @param string $value The initial contents of the textarea.
736     * @param string $inputId The ID attribute for the textarea element.
737     * @param array $inputAttributes Additional HTML attributes for the textarea element.
738     * @param array $wrapperAttributes Additional HTML attributes for the wrapper element.
739     * @param bool $disabled Indicates whether the textarea is disabled.
740     * @param bool $readonly Indicates whether the textarea is read-only.
741     *   If true, the content cannot be modified but can be selected.
742     * @param bool $hasStartIcon Indicates if a start icon should be displayed.
743     * @param bool $hasEndIcon Indicates if an end icon should be displayed.
744     * @param string $startIconClass CSS class for the start icon.
745     * @param string $endIconClass CSS class for the end icon.
746     * @param string $placeholder Placeholder text for the textarea.
747     * @param string $status Validation status ('default', 'error', 'warning' or 'success')
748     * @return TextArea The TextArea component instance.
749     */
750    public function textArea(
751        string $name = '',
752        string $value = '',
753        string $inputId = '',
754        array $inputAttributes = [],
755        array $wrapperAttributes = [],
756        bool $disabled = false,
757        bool $readonly = false,
758        bool $hasStartIcon = false,
759        bool $hasEndIcon = false,
760        string $startIconClass = '',
761        string $endIconClass = '',
762        string $placeholder = '',
763        string $status = 'default'
764    ): TextArea {
765        return new TextArea(
766            new TextAreaRenderer(
767                $this->services->getService( 'Sanitizer' ),
768                $this->services->getService( 'TemplateParser' )
769            ),
770            $name,
771            $value,
772            $inputId,
773            $inputAttributes,
774            $wrapperAttributes,
775            $disabled,
776            $readonly,
777            $hasStartIcon,
778            $hasEndIcon,
779            $startIconClass,
780            $endIconClass,
781            $placeholder,
782            $status
783        );
784    }
785
786    /**
787     * Build a TextInput component
788     *
789     * @since 0.1.0
790     * @param string $type The type of the input field (e.g., 'text', 'email'; see TEXT_INPUT_TYPES).
791     * @param string $name Name attribute of the input.
792     * @param string $value Default value of the input.
793     * @param string $inputId ID attribute for the input.
794     * @param string $placeholder Placeholder text for the input.
795     * @param bool $disabled Indicates if the input is disabled.
796     * @param string $status Validation status ('default', 'error', 'warning' or 'success')
797     * @param array $inputAttributes Additional HTML attributes for the input element.
798     * @param array $wrapperAttributes Additional HTML attributes for the wrapper element.
799     * @param bool $hasStartIcon Indicates if the input has a start icon.
800     * @param bool $hasEndIcon Indicates if the input has an end icon.
801     * @param ?string $startIconClass CSS class for the start icon.
802     * @param ?string $endIconClass CSS class for the end icon.
803     * @return TextInput The TextInput component instance.
804     */
805    public function textInput(
806        string $type = 'text',
807        string $name = '',
808        string $value = '',
809        string $inputId = '',
810        string $placeholder = '',
811        bool $disabled = false,
812        string $status = 'default',
813        array $inputAttributes = [],
814        array $wrapperAttributes = [],
815        bool $hasStartIcon = false,
816        bool $hasEndIcon = false,
817        ?string $startIconClass = null,
818        ?string $endIconClass = null,
819    ): TextInput {
820        return new TextInput(
821            new TextInputRenderer(
822                $this->services->getService( 'Sanitizer' ),
823                $this->services->getService( 'TemplateParser' )
824            ),
825            $type,
826            $name,
827            $value,
828            $inputId,
829            $placeholder,
830            $disabled,
831            $status,
832            $inputAttributes,
833            $wrapperAttributes,
834            $hasStartIcon,
835            $hasEndIcon,
836            $startIconClass,
837            $endIconClass
838        );
839    }
840
841    /**
842     * Build a Thumbnail component
843     *
844     * @since 0.1.0
845     * @param ?string $backgroundImage The background image URL.
846     * @param ?string $placeholderClass The CSS class for the placeholder icon.
847     * @param array $attributes Additional HTML attributes for the thumbnail.
848     * @return Thumbnail The Thumbnail component instance.
849     */
850    public function thumbnail(
851        ?string $backgroundImage = null,
852        ?string $placeholderClass = null,
853        array $attributes = []
854    ): Thumbnail {
855        return new Thumbnail(
856            new ThumbnailRenderer(
857                $this->services->getService( 'Sanitizer' ),
858                $this->services->getService( 'TemplateParser' )
859            ),
860            $backgroundImage,
861            $placeholderClass,
862            $attributes
863        );
864    }
865
866    /**
867     * Build a ToggleSwitch component
868     *
869     * @since 0.1.0
870     * @param string $inputId The ID for the toggle switch input.
871     * @param string $name The name attribute for the toggle switch input.
872     * @param ?Label $label The label object for the toggle switch.
873     * @param string $value The value associated with the toggle switch input.
874     * @param bool $checked Whether the toggle switch is checked by default.
875     * @param bool $disabled Whether the toggle switch is disabled.
876     * @param array $inputAttributes Additional HTML attributes for the input element.
877     * @param array $wrapperAttributes Additional HTML attributes for the wrapper element.
878     * @return ToggleSwitch The ToggleSwitch component instance.
879     */
880    public function toggleSwitch(
881        string $inputId = '',
882        string $name = '',
883        ?Label $label = null,
884        string $value = '',
885        bool $checked = false,
886        bool $disabled = false,
887        array $inputAttributes = [],
888        array $wrapperAttributes = []
889    ): ToggleSwitch {
890        return new ToggleSwitch(
891            new ToggleSwitchRenderer(
892                $this->services->getService( 'Sanitizer' ),
893                $this->services->getService( 'TemplateParser' ),
894                $this->localizer
895            ),
896            $inputId,
897            $name,
898            $label,
899            $value,
900            $checked,
901            $disabled,
902            $inputAttributes,
903            $wrapperAttributes
904        );
905    }
906}