From 89f2ca45c451f7940999254fc3ab935d2606ebcb Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 09:22:00 +0800 Subject: [PATCH] feat(widgets): shared aksrt_icon_card/cover helpers + obs animate map Add zibll-parity icon-card and icon-cover-card renderers plus aksrt_obs_animate_attrs mapping to data-aksrt-animate scroll hooks. --- inc/bootstrap.php | 1 + inc/helpers/aksrt-icon-cards.php | 276 +++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+) create mode 100644 inc/helpers/aksrt-icon-cards.php diff --git a/inc/bootstrap.php b/inc/bootstrap.php index b19a0bea..e8ec4665 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -49,6 +49,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-header-menu-buttons.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-graphic-card.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-icon-cards.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-user-card.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php'; require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php'; diff --git a/inc/helpers/aksrt-icon-cards.php b/inc/helpers/aksrt-icon-cards.php new file mode 100644 index 00000000..79ff2824 --- /dev/null +++ b/inc/helpers/aksrt-icon-cards.php @@ -0,0 +1,276 @@ + $args + * @param bool $echo + */ + function aksrt_icon_card(array $args = [], bool $echo = false): string { + $defaults = [ + 'type' => '', + 'class' => 'box-body nopw-sm', + 'icon' => '', + 'icon_size' => 0, + 'customize_icon' => '', + 'icon_image' => '', + 'link' => [ + 'url' => '', + 'target' => '', + ], + 'icon_radius' => false, + 'icon_color' => '', + 'icon_custom_color' => '', + 'icon_class' => '', + 'title' => '', + 'desc' => '', + ]; + $args = wp_parse_args($args, $defaults); + + $icon_fa = sanitize_text_field((string) $args['icon']); + $icon_img = esc_url_raw(trim((string) ($args['icon_image'] !== '' ? $args['icon_image'] : $args['customize_icon']))); + // customize_icon may be raw HTML (img) from older data. + $custom_html = ''; + if ($icon_img === '' && is_string($args['customize_icon']) && str_contains((string) $args['customize_icon'], '<')) { + $custom_html = wp_kses((string) $args['customize_icon'], [ + 'i' => ['class' => true, 'aria-hidden' => true], + 'img' => ['src' => true, 'alt' => true, 'class' => true, 'width' => true, 'height' => true, 'loading' => true], + 'span'=> ['class' => true], + ]); + } + if ($icon_fa === '' && $icon_img === '' && $custom_html === '') { + return ''; + } + + $link = $args['link']; + if (is_string($link)) { + $link = ['url' => $link, 'target' => '']; + } + if (!is_array($link)) { + $link = ['url' => '', 'target' => '']; + } + $url = esc_url(trim((string) ($link['url'] ?? ''))); + $target = sanitize_text_field((string) ($link['target'] ?? '')); + $tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : ''; + + $icon_color = sanitize_html_class((string) $args['icon_color']); + $icon_class_extra = sanitize_html_class((string) $args['icon_class']); + $radius = !empty($args['icon_radius']); + $icon_class = $radius + ? 'card-icon toggle-radius fa-3x ' . ($icon_class_extra !== '' ? $icon_class_extra : $icon_color) + : 'card-icon fa-4x ' . $icon_color; + if ($icon_class_extra !== '' && !$radius) { + $icon_class .= ' ' . $icon_class_extra; + } + + $style = ''; + $custom_color = sanitize_hex_color((string) $args['icon_custom_color']); + if (!$radius && $custom_color) { + $style = ' style="color:' . esc_attr($custom_color) . ';"'; + } + + if ($custom_html !== '') { + $icon_inner = $custom_html; + } elseif ($icon_img !== '') { + $icon_inner = ''; + } else { + if ($icon_fa !== '' && !str_contains($icon_fa, 'fa-') && !str_contains($icon_fa, ' ')) { + $icon_fa = 'fa fa-' . $icon_fa; + } + $icon_inner = ''; + } + + $icon = '' . $icon_inner . ''; + if ($url !== '') { + $icon = '' . $icon . ''; + } + $icon_size = (int) $args['icon_size']; + if ($icon_size > 0) { + $icon = '' . $icon . ''; + } + + $title = trim((string) $args['title']); + $desc = trim((string) $args['desc']); + if ($url !== '' && $title !== '') { + $title_html = '' . esc_html($title) . ''; + } else { + $title_html = $title !== '' ? esc_html($title) : ''; + } + + $body = ''; + if ($title_html !== '') { + $body .= '
' . $title_html . '
'; + } + if ($desc !== '') { + $body .= '
' . esc_html($desc) . '
'; + } + + $class = trim((string) $args['class'] . ' ' . (string) $args['type']); + $html = '
'; + $html .= $icon; + $html .= $body !== '' ? '
' . $body . '
' : ''; + $html .= '
'; + + if ($echo) { + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return ''; + } + return $html; + } +endif; + +if (!function_exists('aksrt_icon_cover_card')) : + /** + * Horizontal icon cover card (icon left, title/desc right). + * + * @param array $args + * @param bool $echo + */ + function aksrt_icon_cover_card(array $args = [], bool $echo = false): string { + $defaults = [ + 'class' => 'zib-widget', + 'icon' => '', + 'icon_size' => 25, + 'customize_icon' => '', + 'icon_image' => '', + 'link' => [ + 'url' => '', + 'target' => '', + ], + 'icon_radius' => false, + 'icon_color' => '', + 'icon_custom_color' => '', + 'icon_class' => '', + 'title' => '', + 'desc' => '', + ]; + $args = wp_parse_args($args, $defaults); + + $icon_fa = sanitize_text_field((string) $args['icon']); + $icon_img = esc_url_raw(trim((string) ($args['icon_image'] !== '' ? $args['icon_image'] : ''))); + $custom_html = ''; + if ($icon_img === '' && is_string($args['customize_icon']) && str_contains((string) $args['customize_icon'], '<')) { + $custom_html = wp_kses((string) $args['customize_icon'], [ + 'i' => ['class' => true, 'aria-hidden' => true], + 'img' => ['src' => true, 'alt' => true, 'class' => true, 'loading' => true], + 'span'=> ['class' => true], + ]); + } elseif ($icon_img === '' && is_string($args['customize_icon']) && filter_var((string) $args['customize_icon'], FILTER_VALIDATE_URL)) { + $icon_img = esc_url_raw((string) $args['customize_icon']); + } + if ($icon_fa === '' && $icon_img === '' && $custom_html === '') { + return ''; + } + + $link = $args['link']; + if (is_string($link)) { + $link = ['url' => $link, 'target' => '']; + } + if (!is_array($link)) { + $link = ['url' => '', 'target' => '']; + } + $url = esc_url(trim((string) ($link['url'] ?? ''))); + $target = sanitize_text_field((string) ($link['target'] ?? '')); + $tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : ''; + + $icon_class = trim('icon-cover-icon badg cir ' . sanitize_html_class((string) $args['icon_class']) . ' ' . sanitize_html_class((string) $args['icon_color'])); + if (!empty($args['icon_radius'])) { + $icon_class .= ' radius4'; + } + $icon_size = max(14, min(64, (int) $args['icon_size'])); + $style = 'font-size:' . $icon_size . 'px;'; + $custom_color = sanitize_hex_color((string) $args['icon_custom_color']); + if ($custom_color) { + $style .= 'color:' . $custom_color . ';'; + } + + if ($custom_html !== '') { + $icon_inner = $custom_html; + } elseif ($icon_img !== '') { + $icon_inner = ''; + } else { + if ($icon_fa !== '' && !str_contains($icon_fa, 'fa-') && !str_contains($icon_fa, ' ')) { + $icon_fa = 'fa fa-' . $icon_fa; + } + $icon_inner = ''; + } + $icon = '
' . $icon_inner . '
'; + + $title = trim((string) $args['title']); + $desc = trim((string) $args['desc']); + $text = ''; + if ($title !== '') { + $text .= '
' . esc_html($title) . '
'; + } + if ($desc !== '') { + $text .= '
' . esc_html($desc) . '
'; + } + + $html = ''; + if ($url !== '') { + $html .= ''; + } + $html .= '
'; + $html .= $icon; + $html .= $text !== '' ? '
' . $text . '
' : ''; + $html .= '
'; + if ($url !== '') { + $html .= '
'; + } + + if ($echo) { + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return ''; + } + return $html; + } +endif; + +if (!function_exists('aksrt_obs_animate_attrs')) : + /** + * Build class + attribute string for scroll-in animation (maps to data-aksrt-animate). + * + * @param string $animation fade|slideup|slidedown|slideright|slideleft|zoomin|zoomout|'' + * @param bool $repeat + * @param float|int $delay_s + * @return array{class:string,attr:string} + */ + function aksrt_obs_animate_attrs(string $animation, bool $repeat = false, $delay_s = 0): array { + $animation = sanitize_key($animation); + $map = [ + 'fade' => 'fadeIn', + 'slideup' => 'fadeInUp', + 'slidedown' => 'fadeInDown', + 'slideright' => 'fadeInRight', + 'slideleft' => 'fadeInLeft', + 'zoomin' => 'zoomIn', + 'zoomout' => 'fadeIn', + 'fadein' => 'fadeIn', + 'fadeinup' => 'fadeInUp', + ]; + if ($animation === '' || !isset($map[$animation]) && !in_array($animation, $map, true)) { + if (!isset($map[$animation])) { + return ['class' => '', 'attr' => '']; + } + } + $aksrt = $map[$animation] ?? $animation; + $delay_ms = (int) round(((float) $delay_s) * 1000); + $class = ' obs-animate ani-' . $animation; + $attr = ' data-aksrt-animate="' . esc_attr($aksrt) . '"'; + if ($delay_ms > 0) { + $attr .= ' data-aksrt-animate-delay="' . esc_attr((string) $delay_ms) . '"'; + } + if ($repeat) { + $attr .= ' data-animation-repeat="true"'; + } + return ['class' => $class, 'attr' => $attr]; + } +endif;