Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.89% covered (warning)
88.89%
40 / 45
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
ButtonBuilder
88.89% covered (warning)
88.89%
40 / 45
58.33% covered (warning)
58.33%
7 / 12
19.50
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setId
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLabel
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 setAction
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 setWeight
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 setSize
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 setType
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 setIconClass
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setIconOnly
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDisabled
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAttributes
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 build
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * ButtonBuilder.php
4 *
5 * This file is part of the Codex design system, the official design system
6 * for Wikimedia projects. It provides the `Button` class, a builder for constructing
7 * button elements using the Codex design system.
8 *
9 * A Button triggers an action when the user clicks or taps on it.
10 * It can be styled in various ways to reflect its importance, function, and state.
11 *
12 * @category Builder
13 * @package  Codex\Builder
14 * @since    0.1.0
15 * @author   Doğu Abaris <abaris@null.net>
16 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
17 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
18 */
19
20namespace Wikimedia\Codex\Builder;
21
22use InvalidArgumentException;
23use Wikimedia\Codex\Component\Button;
24use Wikimedia\Codex\Renderer\ButtonRenderer;
25
26/**
27 * ButtonBuilder
28 *
29 * This class implements the builder pattern to construct instances of Button.
30 * It provides a fluent interface for setting various properties and building the
31 * final immutable object with predefined configurations and immutability.
32 *
33 * @category Builder
34 * @package  Codex\Builder
35 * @since    0.1.0
36 * @author   Doğu Abaris <abaris@null.net>
37 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
38 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
39 */
40class ButtonBuilder {
41
42    /**
43     * Allowed action styles for the button.
44     */
45    private const ALLOWED_ACTIONS = [
46        'default',
47        'progressive',
48        'destructive',
49    ];
50
51    /**
52     * Allowed sizes for the button.
53     */
54    private const ALLOWED_SIZES = [
55        'medium',
56        'large',
57    ];
58
59    /**
60     * Allowed button types.
61     */
62    private const ALLOWED_TYPES = [
63        'button',
64        'submit',
65        'reset',
66    ];
67
68    /**
69     * Allowed weight styles for the button.
70     */
71    private const ALLOWED_WEIGHTS = [
72        'normal',
73        'primary',
74        'quiet',
75    ];
76
77    /**
78     * The ID for the button.
79     */
80    protected string $id = '';
81
82    /**
83     * The text label displayed on the button.
84     */
85    protected string $label = '';
86
87    /**
88     * The visual action style of the button (e.g., default, progressive, destructive).
89     */
90    protected string $action = 'default';
91
92    /**
93     * The size of the button (e.g., medium, large).
94     */
95    protected string $size = 'medium';
96
97    /**
98     * The type of the button (e.g., button, submit, reset).
99     */
100    protected string $type = 'button';
101
102    /**
103     * The visual prominence of the button (e.g., normal, primary, quiet).
104     */
105    protected string $weight = 'normal';
106
107    /**
108     * The CSS class for an icon, if the button includes one.
109     */
110    protected ?string $iconClass = null;
111
112    /**
113     * Indicates if the button is icon-only (no text).
114     */
115    protected bool $iconOnly = false;
116
117    /**
118     * Indicates if the button is disabled.
119     */
120    protected bool $disabled = false;
121
122    /**
123     * Additional HTML attributes for the button element.
124     */
125    protected array $attributes = [];
126
127    /**
128     * The renderer instance used to render the button.
129     */
130    protected ButtonRenderer $renderer;
131
132    /**
133     * Constructor for the ButtonBuilder class.
134     *
135     * @param ButtonRenderer $renderer The renderer to use for rendering the button.
136     */
137    public function __construct( ButtonRenderer $renderer ) {
138        $this->renderer = $renderer;
139    }
140
141    /**
142     * Set the button's HTML ID attribute.
143     *
144     * @since 0.1.0
145     * @param string $id The ID for the button element.
146     * @return $this
147     */
148    public function setId( string $id ): self {
149        $this->id = $id;
150
151        return $this;
152    }
153
154    /**
155     * Set the label for the button.
156     *
157     * This method defines the text that will be displayed on the button. The label is crucial for providing
158     * users with context about the button's action. In cases where the button is not icon-only, the label
159     * will be wrapped in a `<span>` element within the button.
160     *
161     * It's important to use concise and descriptive text for the label to ensure usability.
162     *
163     * @since 0.1.0
164     * @param string $label The text label displayed on the button.
165     * @return $this Returns the Button instance for method chaining.
166     */
167    public function setLabel( string $label ): self {
168        if ( trim( $label ) === '' && !$this->iconOnly ) {
169            throw new InvalidArgumentException( 'Button label cannot be empty unless the button is icon-only.' );
170        }
171        $this->label = $label;
172
173        return $this;
174    }
175
176    /**
177     * Set the action style for the button.
178     *
179     * This method determines the visual style of the button, which reflects the nature of the action
180     * it represents. The action can be one of the following:
181     * - 'default': A standard action button with no special emphasis.
182     * - 'progressive': Indicates a positive or confirmatory action, often styled with a green or blue background.
183     * - 'destructive': Used for actions that have a significant or irreversible impact, typically styled in red.
184     *
185     * The action style is applied as a CSS class (`cdx-button--action-{action}`) to the button element.
186     *
187     * @since 0.1.0
188     * @param string $action The action style for the button.
189     * @return $this Returns the Button instance for method chaining.
190     */
191    public function setAction( string $action ): self {
192        if ( !in_array( $action, self::ALLOWED_ACTIONS, true ) ) {
193            throw new InvalidArgumentException( "Invalid action: $action" );
194        }
195        $this->action = $action;
196
197        return $this;
198    }
199
200    /**
201     * Set the weight style for the button.
202     *
203     * This method sets the visual prominence of the button, which can be:
204     * - 'normal': A standard button with default emphasis.
205     * - 'primary': A high-importance button that stands out, often used for primary actions.
206     * - 'quiet': A subtle, low-emphasis button, typically used for secondary or tertiary actions.
207     *
208     * The weight style is applied as a CSS class (`cdx-button--weight-{weight}`) to the button element.
209     *
210     * @since 0.1.0
211     * @param string $weight The weight style for the button.
212     * @return $this Returns the Button instance for method chaining.
213     */
214    public function setWeight( string $weight ): self {
215        if ( !in_array( $weight, self::ALLOWED_WEIGHTS, true ) ) {
216            throw new InvalidArgumentException( "Invalid weight: $weight" );
217        }
218        $this->weight = $weight;
219
220        return $this;
221    }
222
223    /**
224     * Set the size of the button.
225     *
226     * This method defines the size of the button, which can be either:
227     * - 'medium': The default size, suitable for most use cases.
228     * - 'large': A larger button, often used to improve accessibility or to emphasize an action.
229     *
230     * The size is applied as a CSS class (`cdx-button--size-{size}`) to the button element.
231     *
232     * @since 0.1.0
233     * @param string $size The size of the button.
234     * @return $this Returns the Button instance for method chaining.
235     */
236    public function setSize( string $size ): self {
237        if ( !in_array( $size, self::ALLOWED_SIZES, true ) ) {
238            throw new InvalidArgumentException( "Invalid size: $size" );
239        }
240        $this->size = $size;
241
242        return $this;
243    }
244
245    /**
246     * Set the type of the button.
247     *
248     * This method sets the button's type attribute, which can be one of the following:
249     * - 'button': A standard clickable button.
250     * - 'submit': A button used to submit a form.
251     * - 'reset': A button used to reset form fields to their initial values.
252     *
253     * The type attribute is applied directly to the `<button>` element.
254     *
255     * @since 0.1.0
256     * @param string $type The type for the button.
257     * @return $this Returns the Button instance for method chaining.
258     */
259    public function setType( string $type ): self {
260        if ( !in_array( $type, self::ALLOWED_TYPES, true ) ) {
261            throw new InvalidArgumentException( "Invalid button type: $type" );
262        }
263        $this->type = $type;
264
265        return $this;
266    }
267
268    /**
269     * Set the icon class for the button.
270     *
271     * This method specifies a CSS class for an icon to be displayed inside the button. The icon is rendered
272     * within a `<span>` element with the class `cdx-button__icon`, and should be defined using a suitable
273     * icon font or SVG sprite.
274     *
275     * The icon enhances the button's usability by providing a visual cue regarding the button's action.
276     *
277     * @since 0.1.0
278     * @param string $iconClass The CSS class for the icon.
279     * @return $this Returns the Button instance for method chaining.
280     */
281    public function setIconClass( string $iconClass ): self {
282        $this->iconClass = $iconClass;
283
284        return $this;
285    }
286
287    /**
288     * Set whether the button should be icon-only.
289     *
290     * This method determines whether the button should display only an icon, without any text.
291     * When set to `true`, the button will only render the icon, making it useful for scenarios where
292     * space is limited, such as in toolbars or mobile interfaces.
293     *
294     * Icon-only buttons should always include an `aria-label` attribute for accessibility, ensuring that
295     * the button's purpose is clear to screen reader users.
296     *
297     * @since 0.1.0
298     * @param bool $iconOnly Whether the button is icon-only.
299     * @return $this Returns the Button instance for method chaining.
300     */
301    public function setIconOnly( bool $iconOnly ): self {
302        $this->iconOnly = $iconOnly;
303
304        return $this;
305    }
306
307    /**
308     * Set whether the button is disabled.
309     *
310     * This method disables the button, preventing any interaction.
311     * A disabled button appears inactive and cannot be clicked.
312     *
313     * Example usage:
314     *
315     *     $button->setDisabled(true);
316     *
317     * @since 0.1.0
318     * @param bool $disabled Indicates whether the button is disabled.
319     * @return $this Returns the Button instance for method chaining.
320     */
321    public function setDisabled( bool $disabled ): self {
322        $this->disabled = $disabled;
323
324        return $this;
325    }
326
327    /**
328     * Set additional HTML attributes for the button element.
329     *
330     * This method allows custom HTML attributes to be added to the button element, such as `id`, `data-*`, `aria-*`,
331     * or any other valid attributes. These attributes can be used to integrate the button with JavaScript, enhance
332     * accessibility, or provide additional metadata.
333     *
334     * The values of these attributes are automatically escaped to prevent XSS vulnerabilities.
335     *
336     * Example usage:
337     *
338     *     $button->setAttributes([
339     *         'id' => 'submit-button',
340     *         'data-toggle' => 'modal',
341     *         'aria-label' => 'Submit Form'
342     *     ]);
343     *
344     * @since 0.1.0
345     * @param array $attributes An associative array of HTML attributes.
346     * @return $this Returns the Button instance for method chaining.
347     */
348    public function setAttributes( array $attributes ): self {
349        foreach ( $attributes as $key => $value ) {
350            $this->attributes[$key] = $value;
351        }
352        return $this;
353    }
354
355    /**
356     * Build and return the Button component object.
357     * This method constructs the immutable Button object with all the properties set via the builder.
358     *
359     * @since 0.1.0
360     * @return Button The constructed Button.
361     */
362    public function build(): Button {
363        return new Button(
364            $this->id,
365            $this->label,
366            $this->action,
367            $this->size,
368            $this->type,
369            $this->weight,
370            $this->iconClass,
371            $this->iconOnly,
372            $this->disabled,
373            $this->attributes,
374            $this->renderer
375        );
376    }
377}