Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
Button
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 23
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLabel
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAction
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWeight
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSize
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getIconClass
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isIconOnly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isDisabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAttributes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setLabel
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setAction
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setWeight
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setSize
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setIconClass
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setIconOnly
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setDisabled
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setAttributes
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setHref
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getHref
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4/**
5 * Button.php
6 *
7 * This file is part of the Codex design system, the official design system for Wikimedia projects.
8 * It contains the definition and implementation of the `Button` class, responsible for managing
9 * the behavior and properties of the corresponding component.
10 *
11 * @category Component
12 * @package  Codex\Component
13 * @since    0.1.0
14 * @author   Doğu Abaris <abaris@null.net>
15 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
16 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
17 */
18
19namespace Wikimedia\Codex\Component;
20
21use InvalidArgumentException;
22use Wikimedia\Codex\Contract\Component;
23use Wikimedia\Codex\Renderer\ButtonRenderer;
24
25/**
26 * Button
27 *
28 * @category Component
29 * @package  Codex\Component
30 * @since    0.1.0
31 * @author   Doğu Abaris <abaris@null.net>
32 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
33 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
34 */
35class Button extends Component {
36    /**
37     * Allowed action styles for the button.
38     */
39    public const ALLOWED_ACTIONS = [
40        'default',
41        'progressive',
42        'destructive',
43    ];
44
45    /**
46     * Allowed sizes for the button.
47     */
48    public const ALLOWED_SIZES = [
49        'medium',
50        'large',
51    ];
52
53    /**
54     * Allowed button types.
55     */
56    public const ALLOWED_TYPES = [
57        'button',
58        'submit',
59        'reset',
60    ];
61
62    /**
63     * Allowed weight styles for the button.
64     */
65    public const ALLOWED_WEIGHTS = [
66        'normal',
67        'primary',
68        'quiet',
69    ];
70
71    private string $id = '';
72
73    public function __construct(
74        ButtonRenderer $renderer,
75        private string|HtmlSnippet $label,
76        private string $action,
77        private string $size,
78        private string $type,
79        private string $weight,
80        private ?string $iconClass,
81        private bool $iconOnly,
82        private bool $disabled,
83        private array $attributes,
84        private ?string $href = null,
85    ) {
86        parent::__construct( $renderer );
87    }
88
89    /**
90     * Get the button's HTML ID attribute.
91     *
92     * This method returns the ID assigned to the button element.
93     * The ID is useful for targeting the button with JavaScript, CSS, or accessibility features.
94     *
95     * @since 0.1.0
96     * @return string The ID of the button element.
97     */
98    public function getId(): string {
99        return $this->id;
100    }
101
102    /**
103     * Get the label displayed on the button.
104     *
105     * This method returns the text label displayed on the button. The label provides context
106     * to users about the button's action, ensuring that it is understandable and accessible.
107     *
108     * @since 0.1.0
109     * @return string|HtmlSnippet The label of the button.
110     */
111    public function getLabel(): string|HtmlSnippet {
112        return $this->label;
113    }
114
115    /**
116     * Get the action style of the button.
117     *
118     * This method returns the action style of the button, indicating the visual style
119     * that reflects the nature of the action it represents (e.g., 'default', 'progressive', 'destructive').
120     *
121     * @since 0.1.0
122     * @return string The action style of the button.
123     */
124    public function getAction(): string {
125        return $this->action;
126    }
127
128    /**
129     * Get the weight style of the button.
130     *
131     * This method returns the weight style of the button, which indicates its visual prominence
132     * (e.g., 'normal', 'primary', 'quiet').
133     *
134     * @since 0.1.0
135     * @return string The weight style of the button.
136     */
137    public function getWeight(): string {
138        return $this->weight;
139    }
140
141    /**
142     * Get the size of the button.
143     *
144     * This method returns the size of the button, determining whether it is 'medium' or 'large'.
145     *
146     * @since 0.1.0
147     * @return string The size of the button.
148     */
149    public function getSize(): string {
150        return $this->size;
151    }
152
153    /**
154     * Get the type of the button.
155     *
156     * This method returns the type of the button, determining whether it is 'button', 'submit', or 'reset'.
157     *
158     * @since 0.1.0
159     * @return string The type of the button.
160     */
161    public function getType(): string {
162        return $this->type ?: 'button';
163    }
164
165    /**
166     * Get the icon class for the button.
167     *
168     * This method returns the CSS class used for the icon displayed inside the button.
169     * The icon is an additional visual element that can be included in the button to enhance usability.
170     *
171     * @since 0.1.0
172     * @return string|null The CSS class for the icon, or null if no icon is set.
173     */
174    public function getIconClass(): ?string {
175        return $this->iconClass;
176    }
177
178    /**
179     * Check if the button is icon-only.
180     *
181     * This method returns a boolean value indicating whether the button is icon-only (i.e., displays only an icon
182     * without any text). This is useful in scenarios where space is limited.
183     *
184     * @since 0.1.0
185     * @return bool True if the button is icon-only, false otherwise.
186     */
187    public function isIconOnly(): bool {
188        return $this->iconOnly;
189    }
190
191    /**
192     * Check if the button is disabled.
193     *
194     * This method returns a boolean value indicating whether the button is disabled.
195     *
196     * @since 0.1.0
197     * @return bool True if the button is disabled, false otherwise.
198     */
199    public function isDisabled(): bool {
200        return $this->disabled;
201    }
202
203    /**
204     * Retrieve additional HTML attributes for the button element.
205     *
206     * This method returns an associative array of additional HTML attributes that will be applied
207     * to the <button> element. These attributes can be used to enhance customization, improve accessibility,
208     * and facilitate JavaScript integration.
209     *
210     * @since 0.1.0
211     * @return array The additional attributes as an array.
212     */
213    public function getAttributes(): array {
214        return $this->attributes;
215    }
216
217    /**
218     * Set the button's HTML ID attribute.
219     *
220     * @deprecated Use setAttributes() to set the ID
221     * @since 0.1.0
222     * @param string $id The ID for the button element.
223     * @return $this
224     */
225    public function setId( string $id ): self {
226        $this->id = $id;
227
228        return $this;
229    }
230
231    /**
232     * Set the label for the button.
233     *
234     * This method defines the text that will be displayed on the button. The label is crucial for providing
235     * users with context about the button's action. In cases where the button is not icon-only, the label
236     * will be wrapped in a `<span>` element within the button.
237     *
238     * It's important to use concise and descriptive text for the label to ensure usability.
239     *
240     * @since 0.1.0
241     * @param string|HtmlSnippet $label The text label displayed on the button.
242     * @return $this Returns the Button instance for method chaining.
243     */
244    public function setLabel( string|HtmlSnippet $label ): self {
245        $this->label = $label;
246
247        return $this;
248    }
249
250    /**
251     * Set the action style for the button.
252     *
253     * This method determines the visual style of the button, which reflects the nature of the action
254     * it represents. The action can be one of the following:
255     * - 'default': A standard action button with no special emphasis.
256     * - 'progressive': Indicates a positive or confirmatory action, often styled with a green or blue background.
257     * - 'destructive': Used for actions that have a significant or irreversible impact, typically styled in red.
258     *
259     * The action style is applied as a CSS class (`cdx-button--action-{action}`) to the button element.
260     *
261     * @since 0.1.0
262     * @param string $action The action style for the button.
263     * @return $this Returns the Button instance for method chaining.
264     */
265    public function setAction( string $action ): self {
266        if ( !in_array( $action, self::ALLOWED_ACTIONS, true ) ) {
267            throw new InvalidArgumentException( "Invalid action: $action" );
268        }
269        $this->action = $action;
270
271        return $this;
272    }
273
274    /**
275     * Set the weight style for the button.
276     *
277     * This method sets the visual prominence of the button, which can be:
278     * - 'normal': A standard button with default emphasis.
279     * - 'primary': A high-importance button that stands out, often used for primary actions.
280     * - 'quiet': A subtle, low-emphasis button, typically used for secondary or tertiary actions.
281     *
282     * The weight style is applied as a CSS class (`cdx-button--weight-{weight}`) to the button element.
283     *
284     * @since 0.1.0
285     * @param string $weight The weight style for the button.
286     * @return $this Returns the Button instance for method chaining.
287     */
288    public function setWeight( string $weight ): self {
289        if ( !in_array( $weight, self::ALLOWED_WEIGHTS, true ) ) {
290            throw new InvalidArgumentException( "Invalid weight: $weight" );
291        }
292        $this->weight = $weight;
293
294        return $this;
295    }
296
297    /**
298     * Set the size of the button.
299     *
300     * This method defines the size of the button, which can be either:
301     * - 'medium': The default size, suitable for most use cases.
302     * - 'large': A larger button, often used to improve accessibility or to emphasize an action.
303     *
304     * The size is applied as a CSS class (`cdx-button--size-{size}`) to the button element.
305     *
306     * @since 0.1.0
307     * @param string $size The size of the button.
308     * @return $this Returns the Button instance for method chaining.
309     */
310    public function setSize( string $size ): self {
311        if ( !in_array( $size, self::ALLOWED_SIZES, true ) ) {
312            throw new InvalidArgumentException( "Invalid size: $size" );
313        }
314        $this->size = $size;
315
316        return $this;
317    }
318
319    /**
320     * Set the type of the button.
321     *
322     * This method sets the button's type attribute, which can be one of the following:
323     * - 'button': A standard clickable button.
324     * - 'submit': A button used to submit a form.
325     * - 'reset': A button used to reset form fields to their initial values.
326     *
327     * The type attribute is applied directly to the `<button>` element.
328     *
329     * @since 0.1.0
330     * @param string $type The type for the button.
331     * @return $this Returns the Button instance for method chaining.
332     */
333    public function setType( string $type ): self {
334        if ( !in_array( $type, self::ALLOWED_TYPES, true ) ) {
335            throw new InvalidArgumentException( "Invalid button type: $type" );
336        }
337        $this->type = $type;
338
339        return $this;
340    }
341
342    /**
343     * Set the icon class for the button.
344     *
345     * This method specifies a CSS class for an icon to be displayed inside the button. The icon is rendered
346     * within a `<span>` element with the class `cdx-button__icon`, and should be defined using a suitable
347     * icon font or SVG sprite.
348     *
349     * The icon enhances the button's usability by providing a visual cue regarding the button's action.
350     *
351     * @since 0.1.0
352     * @param string $iconClass The CSS class for the icon.
353     * @return $this Returns the Button instance for method chaining.
354     */
355    public function setIconClass( string $iconClass ): self {
356        $this->iconClass = $iconClass;
357
358        return $this;
359    }
360
361    /**
362     * Set whether the button should be icon-only.
363     *
364     * This method determines whether the button should display only an icon, without any text.
365     * When set to `true`, the button will only render the icon, making it useful for scenarios where
366     * space is limited, such as in toolbars or mobile interfaces.
367     *
368     * Icon-only buttons should always include an `aria-label` attribute for accessibility, ensuring that
369     * the button's purpose is clear to screen reader users.
370     *
371     * @since 0.1.0
372     * @param bool $iconOnly Whether the button is icon-only.
373     * @return $this Returns the Button instance for method chaining.
374     */
375    public function setIconOnly( bool $iconOnly ): self {
376        $this->iconOnly = $iconOnly;
377
378        return $this;
379    }
380
381    /**
382     * Set whether the button is disabled.
383     *
384     * This method disables the button, preventing any interaction.
385     * A disabled button appears inactive and cannot be clicked.
386     *
387     * Example usage:
388     *
389     *     $button->setDisabled(true);
390     *
391     * @since 0.1.0
392     * @param bool $disabled Indicates whether the button is disabled.
393     * @return $this Returns the Button instance for method chaining.
394     */
395    public function setDisabled( bool $disabled ): self {
396        $this->disabled = $disabled;
397
398        return $this;
399    }
400
401    /**
402     * Set additional HTML attributes for the button element.
403     *
404     * This method allows custom HTML attributes to be added to the button element, such as `id`, `data-*`, `aria-*`,
405     * or any other valid attributes. These attributes can be used to integrate the button with JavaScript, enhance
406     * accessibility, or provide additional metadata.
407     *
408     * The values of these attributes are automatically escaped to prevent XSS vulnerabilities.
409     *
410     * Example usage:
411     *
412     *     $button->setAttributes([
413     *         'id' => 'submit-button',
414     *         'data-toggle' => 'modal',
415     *         'aria-label' => 'Submit Form'
416     *     ]);
417     *
418     * @since 0.1.0
419     * @param array $attributes An associative array of HTML attributes.
420     * @return $this Returns the Button instance for method chaining.
421     */
422    public function setAttributes( array $attributes ): self {
423        foreach ( $attributes as $key => $value ) {
424            $this->attributes[$key] = $value;
425        }
426        return $this;
427    }
428
429    /**
430     * Set the href for the button.
431     *
432     * If set, the button will render as an <a> element styled like a button.
433     *
434     * @param string|null $href The href for the button link.
435     * @return $this Returns the Button instance for method chaining.
436     */
437    public function setHref( ?string $href ): self {
438        $this->href = $href;
439
440        return $this;
441    }
442
443    /**
444     * Get the href for the button.
445     *
446     * If an href is provided, the button will be rendered as an <a> element
447     * styled to look like a button instead of a native <button>.
448     *
449     * @return string|null
450     */
451    public function getHref(): ?string {
452        return $this->href;
453    }
454
455}