PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.3
Tutor LMS – eLearning and online course solution v4.0.3
4.0.3 4.0.2 4.0.1 4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / components / Popover.php
tutor / components Last commit date
Constants 3 weeks ago Accordion.php 1 week ago Alert.php 1 week ago AttachmentCard.php 1 week ago Avatar.php 1 week ago Badge.php 1 week ago BaseComponent.php 3 weeks ago Button.php 1 week ago ConfirmationModal.php 1 week ago CourseFilter.php 1 week ago DateFilter.php 1 week ago DropdownFilter.php 1 week ago EmptyState.php 1 week ago FileUploader.php 1 week ago InputField.php 1 week ago Modal.php 1 week ago Nav.php 1 week ago Pagination.php 1 week ago Popover.php 1 week ago PreviewTrigger.php 1 week ago Progress.php 1 week ago SearchFilter.php 1 week ago Sorting.php 1 week ago StarRating.php 1 week ago StarRatingInput.php 1 week ago StatusSelect.php 1 week ago SvgIcon.php 1 week ago Table.php 1 week ago Tabs.php 1 week ago Tooltip.php 1 week ago WPEditor.php 1 week ago
Popover.php
646 lines
1 <?php
2 /**
3 * Popover Component Class.
4 *
5 * @package Tutor\Components
6 * @author Themeum
7 * @link https://themeum.com
8 * @since 4.0.0
9 */
10
11 namespace Tutor\Components;
12
13 use Tutor\Components\Constants\Positions;
14 use TUTOR\Icon;
15
16 defined( 'ABSPATH' ) || exit;
17
18 /**
19 * Class Popover
20 *
21 * Responsible for rendering popover component on button
22 * at different placement positions with footer component and menu items.
23 *
24 * Example Usage:
25 * ```php
26 * // Basic popover with title and body (bottom-start by default)
27 * Popover::make()
28 * ->title( 'Information' )
29 * ->body( '<p>This is a popover component.</p>' )
30 * ->trigger(
31 * Button::make()
32 * ->label( 'Show Info' )
33 * ->attr( 'x-ref', 'trigger' )
34 * ->attr( '@click', 'toggle()' )
35 * ->variant( Variant::PRIMARY )
36 * ->get()
37 * )
38 * ->render();
39 *
40 * // Popover with close button in header
41 * Popover::make()
42 * ->title( 'Details' )
43 * ->body( '<p>Here are the full details.</p>' )
44 * ->closeable( true )
45 * ->trigger(
46 * Button::make()->label( 'Details' )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
47 * )
48 * ->render();
49 *
50 * // Popover positioned top-end
51 * Popover::make()
52 * ->title( 'Help' )
53 * ->body( '<p>Read the documentation for more help.</p>' )
54 * ->placement( Positions::TOP_END )
55 * ->trigger(
56 * Button::make()->label( '?' )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
57 * )
58 * ->render();
59 *
60 * // Popover with footer buttons
61 * Popover::make()
62 * ->title( 'Confirm' )
63 * ->body( '<p>Are you sure you want to proceed?</p>' )
64 * ->footer( array(
65 * Button::make()->label( 'Cancel' )->variant( Variant::SECONDARY )->attr( '@click', 'hide()' )->get(),
66 * Button::make()->label( 'Confirm' )->variant( Variant::PRIMARY )->get(),
67 * ) )
68 * ->footer_alignment( Positions::RIGHT )
69 * ->trigger(
70 * Button::make()->label( 'Open' )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
71 * )
72 * ->render();
73 *
74 * // Popover as a context menu (menu items)
75 * Popover::make()
76 * ->menu_item( array( 'tag' => 'button', 'content' => 'Edit', 'icon' => SvgIcon::make()->name( Icon::EDIT )->size( 16 )->get(), 'attr' => array( '@click' => 'editItem()' ) ) )
77 * ->menu_item( array( 'tag' => 'button', 'content' => 'Delete', 'icon' => SvgIcon::make()->name( Icon::DELETE_2 )->size( 16 )->get(), 'attr' => array( '@click' => 'deleteItem()', 'class' => 'tutor-text-critical' ) ) )
78 * ->menu_min_width( '140px' )
79 * ->trigger(
80 * Button::make()->icon( Icon::DOT_MENU_H )->icon_only()->variant( Variant::GHOST )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
81 * )
82 * ->render();
83 *
84 * // Menu item with right-aligned icon
85 * Popover::make()
86 * ->menu_item( array( 'tag' => 'a', 'content' => 'View Profile', 'icon' => SvgIcon::make()->name( Icon::ARROW_RIGHT )->size( 16 )->get(), 'icon_alignment' => Positions::RIGHT, 'attr' => array( 'href' => '#' ) ) )
87 * ->trigger(
88 * Button::make()->label( 'Actions' )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
89 * )
90 * ->render();
91 *
92 * // Non-dismissible popover (click outside does NOT close it)
93 * Popover::make()
94 * ->title( 'Important' )
95 * ->body( '<p>You must complete this step before continuing.</p>' )
96 * ->dismissible( false )
97 * ->trigger(
98 * Button::make()->label( 'Open' )->attr( 'x-ref', 'trigger' )->attr( '@click', 'toggle()' )->get()
99 * )
100 * ->render();
101 *
102 * // Retrieve HTML without echoing
103 * $html = Popover::make()->title( 'Tip' )->body( '<p>Hover for help.</p>' )->trigger( $btn )->get();
104 * ```
105 *
106 * @since 4.0.0
107 */
108 class Popover extends BaseComponent {
109
110 /**
111 * Transform origin map for popover transitions.
112 *
113 * @since 4.0.0
114 */
115 public const TRANSFORM_ORIGIN_MAP = array(
116 Positions::TOP => 'center.bottom',
117 Positions::TOP_START => 'left.bottom',
118 Positions::TOP_END => 'right.bottom',
119 Positions::BOTTOM => 'center.top',
120 Positions::BOTTOM_START => 'left.top',
121 Positions::BOTTOM_END => 'right.top',
122 Positions::LEFT => 'right.center',
123 Positions::LEFT_TOP => 'right.top',
124 Positions::LEFT_BOTTOM => 'right.bottom',
125 Positions::RIGHT => 'left.center',
126 Positions::RIGHT_TOP => 'left.top',
127 Positions::RIGHT_BOTTOM => 'left.bottom',
128 );
129
130 /**
131 * The popover title.
132 *
133 * @var string
134 */
135 protected $popover_title;
136
137 /**
138 * The popover body.
139 *
140 * @var string
141 */
142 protected $popover_body;
143
144 /**
145 * Callback function for popover body escaping.
146 *
147 * @var string
148 */
149 protected $popover_body_esc = 'wp_kses_post';
150
151 /**
152 * Allowed HTML tags and attributes. Keys are tag names and values are allowed attributes.
153 *
154 * @since 4.0.0
155 *
156 * @var array
157 */
158 protected $allowed_html_tags = array();
159
160 /**
161 * The popover placement location (left | right | top | bottom ).
162 *
163 * Default 'bottom-start'.
164 *
165 * @var string
166 */
167 protected $popover_placement = Positions::BOTTOM_START;
168
169 /**
170 * Popover button element.
171 *
172 * @var string
173 */
174 protected $popover_button;
175
176 /**
177 * Popover menu items list.
178 *
179 * @var array
180 */
181 protected $popover_menu_items;
182
183 /**
184 * Whether to show or hide popover close button.
185 *
186 * Default true.
187 *
188 * @var bool
189 */
190 protected $show_close_button = false;
191
192 /**
193 * Popover footer buttons.
194 *
195 * @var array
196 */
197 protected $popover_footer_buttons;
198
199 /**
200 * Popover footer button alignment class ( left | center ).
201 *
202 * Default 'right'.
203 *
204 * @var array
205 */
206 protected $popover_footer_alignment;
207
208 /**
209 * Popover menu item icon svg.
210 *
211 * @var string
212 */
213 protected $popover_menu_item_icon;
214
215 /**
216 * Popover menu item icon alignment.
217 *
218 * @var string
219 */
220 protected $popover_menu_item_icon_alignment;
221
222 /**
223 * Popover menu item custom class.
224 *
225 * @var string
226 */
227 protected $popover_menu_item_class;
228
229 /**
230 * Attributes of Popover menu items.
231 *
232 * @var array
233 */
234 protected $attributes;
235
236 /**
237 * Prevent close from outside
238 *
239 * @var bool
240 */
241 protected $popover_close_outside = true;
242
243 /**
244 * Menu min width.
245 *
246 * @var string
247 */
248 protected $menu_min_width;
249
250 /**
251 * Set Popover title
252 *
253 * @since 4.0.0
254 *
255 * @param string $popover_title the popover title.
256 *
257 * @return self
258 */
259 public function title( string $popover_title ): self {
260 $this->popover_title = $popover_title;
261 return $this;
262 }
263
264 /**
265 * Set Popover body html.
266 *
267 * @since 4.0.0
268 *
269 * @param string $popover_body the popover body html.
270 * @param array $allowed_html_tags html tags to allow.
271 *
272 * @return self
273 */
274 public function body( string $popover_body, array $allowed_html_tags = array() ): self {
275 $this->popover_body = $popover_body;
276 $this->allowed_html_tags = $allowed_html_tags;
277 return $this;
278 }
279
280 /**
281 * Set popover placement position.
282 *
283 * @since 4.0.0
284 *
285 * @param string $popover_placement the placement position.
286 *
287 * @return self
288 */
289 public function placement( string $popover_placement = 'bottom-start' ): self {
290 $placement_positions = array(
291 Positions::TOP,
292 Positions::TOP_START,
293 Positions::TOP_END,
294 Positions::LEFT,
295 Positions::LEFT_TOP,
296 Positions::LEFT_BOTTOM,
297 Positions::RIGHT,
298 Positions::RIGHT_TOP,
299 Positions::RIGHT_BOTTOM,
300 Positions::BOTTOM,
301 Positions::BOTTOM_START,
302 Positions::BOTTOM_END,
303 );
304 if ( ! in_array( $popover_placement, $placement_positions, true ) ) {
305 $this->popover_placement = Positions::BOTTOM_START;
306 return $this;
307 }
308
309 $this->popover_placement = $popover_placement;
310 return $this;
311 }
312
313 /**
314 * Popover button component.
315 *
316 * @since 4.0.0
317 *
318 * @param string $popover_button the popover button element html.
319 *
320 * @return self
321 */
322 public function trigger( string $popover_button ): self {
323 $this->popover_button = $popover_button;
324 return $this;
325 }
326
327 /**
328 * Whether to hide close button on popover.
329 *
330 * @since 4.0.0
331 *
332 * @param boolean $show_close_button determines whether to hide close button.
333 *
334 * @return self
335 */
336 public function closeable( bool $show_close_button = false ): self {
337 $this->show_close_button = $show_close_button;
338 return $this;
339 }
340
341 /**
342 * Set the popover footer alignment.
343 *
344 * @since 4.0.0
345 *
346 * @param string $popover_footer_alignment the popover footer alignment class.
347 *
348 * @return self
349 */
350 public function footer_alignment( string $popover_footer_alignment ): self {
351 $this->popover_footer_alignment = $popover_footer_alignment;
352 return $this;
353 }
354
355 /**
356 * Set popover footer buttons.
357 *
358 * @since 4.0.0
359 *
360 * @param array $popover_footer_buttons the footer button list.
361 *
362 * @return self
363 */
364 public function footer( array $popover_footer_buttons = array() ): self {
365 $this->popover_footer_buttons = $popover_footer_buttons;
366 return $this;
367 }
368
369 /**
370 * Menu items for creating popover menu.
371 *
372 * @since 4.0.0
373 *
374 * @param array $args {
375 * Array of arguments to be provided for the menu items.
376 *
377 * @type string $tag the tag of the menu item.
378 * @type string $class the class for the menu items.
379 * @type string $icon the icon svg.
380 * @type string $icon_alignment the alignment of menu item icon (left | right).
381 * @type string $content the popover menu item content.
382 * @type array $attr the popover menu item attributes.
383 * }
384 *
385 * @return self
386 */
387 public function menu_item( array $args ): self {
388 $menu_item_tag = isset( $args['tag'] ) ? $this->esc( $args['tag'], $this->popover_body_esc ) : '';
389 $this->popover_menu_item_class = $args['class'] ?? '';
390 $this->popover_menu_item_icon = $args['icon'] ?? '';
391 $this->popover_menu_item_icon_alignment = isset( $args['icon_alignment'] ) && in_array( $args['icon_alignment'], array( Positions::LEFT, Positions::RIGHT ), true ) ? $args['icon_alignment'] : Positions::LEFT;
392
393 $this->popover_menu_items[] = array(
394 'tag' => $menu_item_tag,
395 'content' => $args['content'] ?? '',
396 'class' => $this->popover_menu_item_class,
397 'icon' => $this->popover_menu_item_icon,
398 'icon_alignment' => $this->popover_menu_item_icon_alignment,
399 'attr' => $args['attr'] ?? array(),
400 );
401
402 return $this;
403 }
404
405 /**
406 * Set menu min width.
407 *
408 * @since 4.0.0
409 *
410 * @param string $width the min width value.
411 *
412 * @return self
413 */
414 public function menu_min_width( string $width ): self {
415 $this->menu_min_width = $width;
416 return $this;
417 }
418
419 /**
420 * Render popover header element.
421 *
422 * @since 4.0.0
423 *
424 * @return string
425 */
426 protected function render_header(): string {
427 if ( empty( $this->popover_title ) ) {
428 return '';
429 }
430
431 $title = sprintf( '<h3 class="tutor-popover-title">%s</h3>', esc_attr( $this->popover_title ) );
432
433 $close_button = sprintf(
434 '<button @click="hide()" class="tutor-popover-close" aria-label="%s">
435 %s
436 </button>',
437 esc_attr__( 'Close', 'tutor' ),
438 SvgIcon::make()->name( Icon::CROSS )->size( 14 )->get()
439 );
440
441 if ( $this->show_close_button ) {
442 return sprintf(
443 '<div class="tutor-popover-header">
444 %s
445 %s
446 </div>
447 ',
448 $title,
449 $close_button
450 );
451 }
452
453 return sprintf(
454 '<div class="tutor-popover-header">
455 %s
456 </div>
457 ',
458 $title,
459 );
460 }
461
462 /**
463 * Render popover body component.
464 *
465 * @since 4.0.0
466 *
467 * @return string
468 */
469 protected function render_body(): string {
470 if ( empty( $this->popover_body ) ) {
471 return '';
472 }
473
474 $body = wp_kses( $this->popover_body, $this->get_allowed_html_tags( $this->allowed_html_tags ) );
475
476 return sprintf(
477 '<div class="tutor-popover-body">
478 %s
479 </div>',
480 $body
481 );
482 }
483
484 /**
485 * Render footer component for popover.
486 *
487 * @since 4.0.0
488 *
489 * @return string
490 */
491 protected function render_footer(): string {
492 $footer_buttons = '';
493 $footer_alignment = in_array( $this->popover_footer_alignment, array( Positions::LEFT, Positions::CENTER ), true ) ? $this->popover_footer_alignment : '';
494
495 if ( ! tutor_utils()->count( $this->popover_footer_buttons ) ) {
496 return $footer_buttons;
497 }
498
499 $alignment = Positions::LEFT === $footer_alignment ? 'tutor-justify-start' : 'tutor-justify-center';
500
501 foreach ( $this->popover_footer_buttons as $button ) {
502 $footer_buttons .= $button;
503 }
504
505 if ( ! empty( $footer_alignment ) ) {
506 return sprintf(
507 '<div class="tutor-popover-footer %s">
508 %s
509 </div>',
510 $alignment,
511 $footer_buttons
512 );
513 }
514
515 return sprintf(
516 '<div class="tutor-popover-footer">
517 %s
518 </div>',
519 $footer_buttons
520 );
521 }
522
523 /**
524 * Handle closing popover on outside click.
525 *
526 * @since 4.0.0
527 *
528 * @param boolean $is_closeable whether allow to close from outside click.
529 *
530 * @return self
531 */
532 public function dismissible( bool $is_closeable ): self {
533 $this->popover_close_outside = $is_closeable;
534 return $this;
535 }
536
537 /**
538 * Render Menu for popover.
539 *
540 * @since 4.0.0
541 *
542 * @return string
543 */
544 protected function render_menu(): string {
545 $menu_items = '';
546 if ( ! tutor_utils()->count( $this->popover_menu_items ) ) {
547 return $menu_items;
548 }
549
550 foreach ( $this->popover_menu_items as $item ) {
551 $tag = $item['tag'] ?? 'div';
552 $content = isset( $item['content'] ) ? $this->esc( $item['content'], $this->popover_body_esc ) : '';
553 $class = isset( $item['class'] ) ? esc_attr( $item['class'] ) : '';
554 $icon = $item['icon'] ?? '';
555 $icon_alignment = $item['icon_alignment'] ?? Positions::LEFT;
556
557 $this->attributes = tutor_utils()->count( $item['attr'] ) ? $item['attr'] : array();
558
559 $menu_item_attr = $this->get_attributes_string();
560
561 if ( empty( $icon ) ) {
562 $menu_items .= sprintf(
563 '<%1$s class="tutor-popover-menu-item %2$s" %4$s>%3$s</%1$s>',
564 $tag,
565 $class,
566 $content,
567 $menu_item_attr
568 );
569 } elseif ( Positions::RIGHT === $icon_alignment ) {
570 $menu_items .= sprintf(
571 '<%1$s class="tutor-popover-menu-item %2$s" %5$s>%3$s%4$s</%1$s>',
572 $tag,
573 $class,
574 $content,
575 $icon,
576 $menu_item_attr
577 );
578 } else {
579 $menu_items .= sprintf(
580 '<%1$s class="tutor-popover-menu-item %2$s" %5$s>%4$s%3$s</%1$s>',
581 $tag,
582 $class,
583 $content,
584 $icon,
585 $menu_item_attr
586 );
587 }
588 }
589
590 $style = $this->menu_min_width ? " style=\"min-width: {$this->menu_min_width}\"" : '';
591 return sprintf( '<div class="tutor-popover-menu"%s>%s</div>', $style, $menu_items );
592 }
593
594 /**
595 * Get the final Popover HTML Component.
596 *
597 * @since 4.0.0
598 *
599 * @return string HTML content.
600 */
601 public function get(): string {
602
603 $header = $this->render_header();
604 $body = $this->render_body();
605 $placement_position = $this->popover_placement;
606 $button = $this->popover_button ?? '';
607 $footer = $this->render_footer();
608 $menu = $this->render_menu();
609
610 $placement_class = 'tutor-popover-' . explode( '-', $placement_position )[0];
611 $class = 'tutor-popover ' . $placement_class;
612
613 $closeable_attr = $this->popover_close_outside ? '@click.outside="handleClickOutside()"' : '';
614
615 $origin = self::TRANSFORM_ORIGIN_MAP[ $placement_position ] ?? 'center.top';
616
617 return sprintf(
618 '<div x-data="tutorPopover({ placement: \'%s\' })">
619 %s
620 <div
621 x-ref="content"
622 x-show="open"
623 x-cloak
624 x-transition.%s
625 class="%s"
626 %s
627 >
628 %s
629 %s
630 %s
631 %s
632 </div>
633 </div>',
634 $placement_position,
635 $button,
636 $origin,
637 $class,
638 $closeable_attr,
639 $header,
640 $body,
641 $footer,
642 $menu
643 );
644 }
645 }
646