yf_html_bs5 classBootstrap 5.3 markup of the yf_html widgets, used by yf_html when conf('css_framework') is bs5 (yf_html::_bs5()). Same signatures and options as the bs3 methods; ids come from the calling yf_html's counters, so they stay the bs3 ids.
| File | plugins/html/classes/html/yf_html_bs5.class.php |
|---|---|
| Use | _class('html_bs5', 'classes/html/') |
A site sets them in its config with $PROJECT_CONF['html_bs5']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$CLASS_LABEL_BTNstring |
'btn btn-outline-primary' |
label class of button_radio_box() / button_check_box() items |
$CLASS_SWITCHstring |
'form-check form-switch' |
wrapper of a check_box() with the `switch` option |
$POPOVER_LEGACY_DATAstring |
|
bs3 data-* popover options tooltip() accepts and renames to data-bs-* |
$_parentyf_html |
null |
set by yf_html::_bs5() |
public function accordion($data = [], $extra = [])
public function accordion($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('accordion');
$parent_attr = ! empty($extra['always_open']) ? '' : ' data-bs-parent="#' . $extra['id'] . '"';
$items = [];
$i = 0;
foreach ((array) $data as $k => $v) {
if ( ! is_array($v)) {
$content = $v;
$v = [];
} else {
$content = $v['body'] ?? '';
}
$name = ($v['name'] ?? null) ?: $k;
$desc = ($v['desc'] ?? null) ?: ucfirst(str_replace('_', ' ', $name));
$id = ($v['id'] ?? null) ?: 'accordion_item_' . $k;
$is_selected = isset($extra['selected']) ? ($extra['selected'] == $k) : (++$i == 1);
$class_group = ($v['class_group'] ?? null) ?: ($extra['class_group'] ?? '');
$class_head = ($v['class_head'] ?? null) ?: ($extra['class_head'] ?? '');
$class_body = ($v['class_body'] ?? null) ?: ($extra['class_body'] ?? '');
$badge = ($v['badge'] ?? null) ? $this->_badge($v) : '';
$items[] = '<div class="' . $this->_cls('accordion-item', $class_group) . '">'
. '<h2 class="' . $this->_cls('accordion-header', $class_head) . '">'
. '<button class="' . $this->_cls('accordion-button', $is_selected ? '' : 'collapsed') . '" type="button" data-bs-toggle="collapse" data-bs-target="#' . $id . '"'
. ' aria-expanded="' . ($is_selected ? 'true' : 'false') . '" aria-controls="' . $id . '">' . $desc . $badge . '</button>'
. '</h2>'
. '<div id="' . $id . '" class="' . $this->_cls('accordion-collapse collapse', $is_selected ? 'show' : '', $class_body) . '"' . $parent_attr . '>'
. '<div class="accordion-body">' . $content . '</div>'
. '</div>'
. '</div>';
}
return '<div class="' . $this->_cls('accordion', ! empty($extra['flush']) ? 'accordion-flush' : '', $extra['class'] ?? '') . '" id="' . $extra['id'] . '"'
. $this->_parent->_theme_attr($extra['theme'] ?? '') . '>' . implode(PHP_EOL, $items) . '</div>';
}
public function alert($data = [], $extra = [])
public function alert($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('alert');
$is_arr = is_array($data);
$closable = ! ($extra['no_close'] ?? null) && ! ($is_arr && ($data['no_close'] ?? null));
$head = $is_arr ? ($data['head'] ?? '') : '';
$body = $is_arr ? ($data['body'] ?? '') : $data;
$alert_type = (($extra['alert'] ?? null) ?: ($is_arr ? ($data['alert'] ?? '') : '')) ?: 'error';
$alert_type = ['error' => 'danger'][$alert_type] ?? $alert_type;
return '<div class="' . $this->_cls('alert alert-' . $alert_type, $closable ? 'alert-dismissible' : '', 'fade show', $extra['class'] ?? '') . '" role="alert" id="' . $extra['id'] . '">'
. ($head ? '<h4 class="alert-heading">' . $head . '</h4>' : '')
. $body
. ($closable ? '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="' . t('Close') . '"></button>' : '')
. '</div>';
}
public function breadcrumbs($data = [], $extra = [])
public function button_box($name, $values = [], $selected = '', $extra = [])
public function button_check_box($name, $values = [], $selected = '')
One checkbox as a Bootstrap 5 toggle button (input.btn-check + label.btn); options of yf_html::check_box().
public function button_radio_box($name, $values = [], $selected = null, $extra = [])
Radios as a Bootstrap 5 button group (input.btn-check + label.btn); options of yf_html::radio_box().
public function button_split_box($name, $values = [], $selected = '', $extra = [])
public function carousel($data = [], $extra = [])
public function carousel($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('carousel');
$items = [];
$headers = [];
$i = 0;
$n = 0;
foreach ((array) $data as $k => $v) {
if ( ! is_array($v)) {
$img_src = $v;
$v = [];
} else {
$img_src = $v['img'] ?? '';
}
$desc = $v['desc'] ?? '';
$alt = ($v['alt'] ?? null) ?: strip_tags($desc);
$is_active = isset($extra['selected']) ? ($extra['selected'] == $k) : (++$i == 1);
$class_head = $this->_cls($is_active ? 'active' : '', ($v['class_head'] ?? null) ?: ($extra['class_head'] ?? ''));
$class_body = ($v['class_body'] ?? null) ?: ($extra['class_body'] ?? '');
$headers[] = '<button type="button" data-bs-target="#' . $extra['id'] . '" data-bs-slide-to="' . $n . '"' . ($class_head ? ' class="' . $class_head . '"' : '')
. ($is_active ? ' aria-current="true"' : '') . ' aria-label="' . t('Slide') . ' ' . ($n + 1) . '"></button>';
$items[] = '<div class="' . $this->_cls('carousel-item', $is_active ? 'active' : '', $class_body) . '"'
. (isset($v['interval']) && $this->_parent->B5_ITEM_OPTIONS ? ' data-bs-interval="' . max(0, (int) $v['interval']) . '"' : '') . '>'
. '<img src="' . $img_src . '" class="d-block w-100" alt="' . $alt . '">'
. ($desc ? '<div class="carousel-caption d-none d-md-block">' . $desc . '</div>' : '')
. '</div>';
$n++;
}
$controls = '';
if ( ! ($extra['no_controls'] ?? null)) {
foreach (['prev' => 'Previous', 'next' => 'Next'] as $dir => $text) {
$controls .= '<button class="carousel-control-' . $dir . '" type="button" data-bs-target="#' . $extra['id'] . '" data-bs-slide="' . $dir . '">'
. '<span class="carousel-control-' . $dir . '-icon" aria-hidden="true"></span><span class="visually-hidden">' . t($text) . '</span></button>';
}
}
return '<div id="' . $extra['id'] . '" class="' . $this->_cls('carousel slide', ! empty($extra['fade']) ? 'carousel-fade' : '', $extra['class'] ?? '') . '"' . $this->_carousel_attrs($extra, $data) . '>'
. '<div class="carousel-indicators">' . implode(PHP_EOL, $headers) . '</div>'
. '<div class="carousel-inner">' . implode(PHP_EOL, $items) . '</div>'
. $controls
. '</div>';
}
public function div_box($name, $values = [], $selected = '', $extra = [])
public function div_box($name, $values = [], $selected = '', $extra = [])
{
if (is_array($name)) {
$extra = (array) $extra + $name;
$name = $extra['name'] ?? '';
}
$extra = (array) $extra;
$desc = ($extra['desc'] ?? null) ?: ucfirst(str_replace('_', '', (string) $name));
$values = isset($extra['values']) ? $extra['values'] : (array) $values;
$translate = ($extra['no_translate'] ?? null) ? 0 : ($extra['translate'] ?? 0);
$selected = ($extra['selected'] ?? null) ?: $selected;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('div_box');
if ( ! $values) {
return false;
}
[$items, $selected_val] = $this->_dropdown_items($values, (string) $selected, $translate);
return implode(PHP_EOL, array_merge(
[
'<li class="dropdown" style="list-style-type:none;" id="' . $extra['id'] . '">',
'<a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' . ($selected_val ?: $desc) . '</a>',
'<ul class="dropdown-menu">',
],
$items,
['</ul>', '</li>'],
));
}
public function dropdown($items = [], $extra = [])
Options and items of yf_html::dropdown().
public function dropdown($items = [], $extra = [])
{
$p = $this->_parent;
$extra = (array) $extra;
$btn = ($extra['class'] ?? null) ?: $p->_btn_class($extra);
$icon = ($extra['icon'] ?? null) ? '<i class="' . _htmlchars(_escape_tpl_braces((string) $extra['icon'])) . '" aria-hidden="true"></i>' : '';
$text = $p->_b4_text($extra['text'] ?? '', $extra);
$text = $icon . ($icon && strlen($text) ? ' ' : '') . $text;
$auto_close = $extra['auto_close'] ?? null;
$auto_close = is_bool($auto_close) ? ($auto_close ? 'true' : 'false') : (in_array($auto_close, ['inside', 'outside'], true) ? $auto_close : '');
$toggle = ['type' => 'button', 'class' => $btn . ' dropdown-toggle', 'data-bs-toggle' => 'dropdown', 'aria-expanded' => 'false', 'data-bs-auto-close' => $auto_close];
if ( ! empty($extra['split'])) {
$link = (string) ($extra['link'] ?? '');
$body = strlen($link) ? '<a' . $p->_b4_attrs(['href' => $p->_safe_href($link), 'class' => $btn, 'role' => 'button']) . '>' . $text . '</a>' : '<button type="button" class="' . $btn . '">' . $text . '</button>';
$toggle['class'] .= ' dropdown-toggle-split';
$body .= '<button' . $p->_b4_attrs($toggle) . '><span class="visually-hidden">' . t('Toggle Dropdown') . '</span></button>';
} else {
$body = '<button' . $p->_b4_attrs($toggle) . '>' . $text . '</button>';
}
$items_html = $this->_dropdown_menu_html($items);
$dirs = ['up' => 'dropup', 'end' => 'dropend', 'start' => 'dropstart', 'center' => 'dropdown-center', 'up-center' => 'dropup dropup-center'];
$wrap = ['class' => $this->_cls('btn-group', $dirs[$extra['direction'] ?? ''] ?? '', $extra['class_add'] ?? ''), 'id' => $extra['id'] ?? ''] + $p->_b4_pass($extra);
$menu = ['class' => $this->_cls('dropdown-menu', ($extra['align'] ?? '') === 'end' ? 'dropdown-menu-end' : ''), 'data-bs-theme' => ($extra['dark'] ?? null) ? 'dark' : ''];
return '<div' . $p->_b4_attrs($wrap) . '>' . $body . '<ul' . $p->_b4_attrs($menu) . '>' . implode('', $items_html) . '</ul></div>';
}
public function grid($data = [], $extra = [])
public function grid($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('grid');
$rows = [];
foreach ((array) $data as $row) {
$row = (array) $row;
$row_col = max(1, count($row) ? (int) floor(12 / count($row)) : 1);
$items = [];
foreach ($row as $item) {
$item = is_array($item) ? $item : [$item];
$body = ($item[0] ?? null) ?: ($item['body'] ?? '');
$col = (($item[1] ?? null) ?: ($item['col'] ?? null)) ?: $row_col;
$items[] = '<div class="' . $this->_cls('col-md-' . $col, $item['class'] ?? '') . '">' . $body . '</div>';
}
$rows[] = '<div class="row">' . implode(PHP_EOL, $items) . '</div>';
}
return '<div class="' . $this->_cls('grid', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $rows) . '</div>';
}
public function jumbotron($data = [], $extra = [])
public function jumbotron($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('jumbotron');
$data = is_array($data) ? $data : ['body' => $data];
return '<div class="' . $this->_cls('p-5 mb-4 bg-body-tertiary rounded-3', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">'
. (($data['head'] ?? null) ? '<h1 class="display-5 fw-bold">' . $data['head'] . '</h1>' : '')
. ($data['body'] ?? '')
. '</div>';
}
public function list_group($data = [], $extra = [])
public function list_group($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('list_group');
$p = $this->_parent;
$has_links = $p->_list_group_has_links($data);
$items = [];
foreach ((array) $data as $v) {
if ( ! is_array($v)) {
$body = $v;
$v = [];
} else {
$body = $v['body'] ?? '';
}
$badge = (string) ($v['badge'] ?? '');
$class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
$badge_html = strlen($badge) ? '<span class="badge text-bg-primary rounded-pill float-end">' . $badge . '</span> ' : '';
$state = $p->_list_group_item_state($v, $extra, 'bs5');
if ( ! $has_links && $state === null) {
$items[] = '<li class="' . $this->_cls('list-group-item', $class_item) . '">' . $badge_html . $body . '</li>';
continue;
}
$state ??= ['link' => null, 'class' => '', 'attrs' => []];
$tag = $has_links ? ($state['link'] !== null ? 'a' : 'div') : 'li';
$a = ['href' => $state['link'] ?? '', 'class' => $this->_cls('list-group-item' . $state['class'], $class_item)] + $state['attrs'];
$items[] = '<' . $tag . $p->_b4_attrs($a) . '>' . $badge_html . $body . '</' . $tag . '>';
}
$tag = $has_links ? 'div' : ( ! empty($extra['numbered']) ? 'ol' : 'ul');
$class = $this->_cls('list-group', ! empty($extra['numbered']) ? 'list-group-numbered' : '', ! empty($extra['flush']) ? 'list-group-flush' : '', $extra['class'] ?? '');
return '<' . $tag . ' class="' . $class . '" id="' . $extra['id'] . '"' . $p->_theme_attr($extra['theme'] ?? '') . '>' . implode(PHP_EOL, $items) . '</' . $tag . '>';
}
public function media_objects($data = [], $extra = [])
public function media_objects($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('media_objects');
$data = $data ? $this->_parent->_recursive_sort_items($data) : [];
$img_class = $this->_cls($extra['img_class'] ?? '', $extra['img_class_add'] ?? '');
$keys = array_keys($data);
$keys_counter = array_flip($keys);
$items = [];
foreach ($data as $id => $item) {
$next_id = $keys[$keys_counter[$id] + 1] ?? null;
$next_item = $next_id ? $data[$next_id] : [];
$close_num_levels = 1;
if ($next_item) {
$close_num_levels = max(0, $item['level'] - $next_item['level'] + 1);
} elseif ($item['level'] > 0) {
$close_num_levels = $item['level'] + 1;
}
$link = $item['link'] ?? '';
$items[] = '<div class="d-flex mb-3">'
. '<a class="flex-shrink-0"' . ($link ? ' href="' . $link . '"' : '') . '>'
. '<img' . ($img_class ? ' class="' . $img_class . '"' : '') . ' alt="' . ($item['alt'] ?? '') . '" src="' . ($item['img'] ?? '') . '"'
. (($item['img_width'] ?? null) ? ' width="' . $item['img_width'] . '"' : '') . (($item['img_height'] ?? null) ? ' height="' . $item['img_height'] . '"' : '') . '></a>'
. '<div class="flex-grow-1 ms-3">'
. '<h5 class="mt-0">'
. ($link ? '<a href="' . $link . '">' : '') . ($item['head'] ?? '') . ($link ? '</a>' : '')
. (($item['date'] ?? null) ? ' <small class="float-end">' . _format_date($item['date'], ($extra['date_format'] ?? null) ?: 'full') . '</small>' : '')
. '</h5>'
. ($item['body'] ?? '')
. str_repeat('</div></div>', $close_num_levels);
}
return '<div class="' . $this->_cls('media-objects', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $items) . '</div>';
}
public function modal($extra = [])
public function modal($extra = [])
{
$extra = (array) $extra;
$id = ($extra['id'] ?? null) ?: $this->_id('modal');
$style = $extra['style'] ?? '';
$title_id = $id . '_title';
$inline = (bool) ($extra['inline'] ?? null);
return '<div class="' . $this->_cls('modal', $inline ? 'position-static d-block' : '', ! empty($extra['fade']) && ! $inline ? 'fade' : '') . '"' . ($style ? ' style="' . $style . '"' : '') . ' id="' . $id . '" tabindex="-1"'
. (($extra['header'] ?? null) ? ' aria-labelledby="' . $title_id . '"' : '')
. ( ! empty($extra['static']) ? ' data-bs-backdrop="static" data-bs-keyboard="false"' : '') . $this->_parent->_theme_attr($extra['theme'] ?? '') . '>'
. '<div class="' . $this->_modal_dialog_class($extra) . '"><div class="modal-content">'
. '<div class="modal-header">'
. (($extra['header'] ?? null) ? '<h5 class="modal-title" id="' . $title_id . '">' . $extra['header'] . '</h5>' : '')
. (($extra['show_close'] ?? null) ? '<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="' . t('Close') . '"></button>' : '')
. '</div>'
. '<div class="modal-body">' . ($extra['body'] ?? '') . '</div>'
. (($extra['footer'] ?? null) ? '<div class="modal-footer">' . $extra['footer'] . '</div>' : '')
. '</div></div></div>';
}
public function offcanvas($extra = [])
Options of yf_html::offcanvas(); the id comes from yf_html.
public function offcanvas($extra = [])
{
$p = $this->_parent;
$extra = (array) $extra;
$id = fix_html_attr_id((string) (($extra['id'] ?? null) ?: $this->_id('offcanvas')));
$placement = in_array($extra['placement'] ?? '', ['start', 'end', 'top', 'bottom'], true) ? $extra['placement'] : 'start';
$title = $p->_b4_text($extra['title'] ?? '', $extra);
$backdrop = $extra['backdrop'] ?? true;
$a = [
'class' => $this->_cls('offcanvas offcanvas-' . $placement, ($extra['show'] ?? null) ? 'show' : '', $extra['class_add'] ?? ''),
'tabindex' => '-1',
'id' => $id,
'aria-labelledby' => strlen($title) ? $id . '_title' : '',
'data-bs-backdrop' => $backdrop === 'static' ? 'static' : ($backdrop ? '' : 'false'),
'data-bs-scroll' => ($extra['scroll'] ?? null) ? 'true' : '',
] + $p->_b4_pass($extra);
$close = ($extra['no_close'] ?? null) ? '' : '<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="' . t('Close') . '"></button>';
$header = (strlen($title) || $close) ? '<div class="offcanvas-header">' . (strlen($title) ? '<h5 class="offcanvas-title" id="' . $id . '_title">' . $title . '</h5>' : '') . $close . '</div>' : '';
return '<div' . $p->_b4_attrs($a) . '>' . $header . '<div class="offcanvas-body">' . ($extra['body'] ?? '') . '</div></div>';
}
public function pagination($data = [], $extra = [])
public function pagination($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('pagination');
$data = (array) $data;
$prev = $data['prev'] ?? null;
$next = $data['next'] ?? null;
unset($data['prev'], $data['next']);
$html = $this->_parent ?: html();
$items = [];
if ($prev || $html->_pagination_state('prev', $extra) === 'disabled') {
$items[] = $this->_pagination_item('prev', $prev, t('Prev'), $extra);
}
foreach ($data as $page => $link) {
$items[] = $this->_pagination_item($page, $link, $page, $extra);
}
if ($next || $html->_pagination_state('next', $extra) === 'disabled') {
$items[] = $this->_pagination_item('next', $next, t('Next'), $extra);
}
[$size_class] = $html->_pagination_classes($extra['size'] ?? '', $extra['align'] ?? '', 'bs5');
return '<nav><ul class="' . $this->_cls('pagination', $size_class, $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . implode(PHP_EOL, $items) . '</ul></nav>';
}
public function panel($extra = [])
public function panel($extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('panel');
$title = (string) ($extra['title'] ?? '');
$p = $this->_parent;
$border = $p->_color_type($extra['border'] ?? '', '');
$subtitle = strlen((string) ($extra['subtitle'] ?? '')) ? '<h6 class="card-subtitle mb-2 text-body-secondary">' . $p->_b4_text($extra['subtitle'], $extra) . '</h6>' : '';
$img = strlen((string) ($extra['img_top'] ?? '')) ? '<img' . $p->_b4_attrs(['src' => $p->_safe_href($extra['img_top']), 'class' => $this->_cls('card-img-top', strlen($title) ? 'rounded-0' : '')]) . $p->_img_alt($extra) . '>' : '';
$footer = strlen((string) ($extra['footer'] ?? '')) ? '<div class="card-footer">' . $extra['footer'] . '</div>' : '';
return '<div class="' . $this->_cls(($extra['class'] ?? null) ?: 'card', $border ? 'border-' . $border : '', $extra['class_add'] ?? '') . '" id="' . $extra['id'] . '"' . $p->_theme_attr($extra['theme'] ?? '') . '>'
. (strlen($title) ? '<div class="card-header"><h5 class="card-title mb-0">' . $title . '</h5></div>' : '')
. $img
. '<div class="card-body">' . $subtitle . ($extra['body'] ?? '') . '</div>'
. $footer
. '</div>';
}
public function placeholder($extra = [])
Options of yf_html::placeholder().
public function placeholder($extra = [])
{
$p = $this->_parent;
$extra = (array) $extra;
$lines = max(1, min(50, (int) ($extra['lines'] ?? 3)));
$widths = [];
foreach ((array) ($extra['widths'] ?? [12, 8, 6]) as $w) {
(int) $w > 0 && $widths[] = min(12, (int) $w);
}
$widths = $widths ?: [12];
$glow = $extra['glow'] ?? true;
$anim = $glow === 'wave' ? 'placeholder-wave' : ($glow ? 'placeholder-glow' : '');
$size = in_array($extra['size'] ?? '', ['xs', 'sm', 'lg'], true) ? 'placeholder-' . $extra['size'] : '';
$color = $p->_color_type($extra['color'] ?? '', '');
$spans = [];
for ($i = 0; $i < $lines; $i++) {
$spans[] = '<span class="' . $this->_cls('placeholder col-' . $widths[$i % count($widths)], $size, $color ? 'bg-' . $color : '') . '"></span>';
}
$a = ['class' => $this->_cls($anim, $extra['class_add'] ?? ''), 'id' => $extra['id'] ?? '', 'aria-hidden' => 'true'];
return '<div' . $p->_b4_attrs($a) . '>' . implode('<br>', $spans) . '</div>';
}
public function progress_bar($data = [], $extra = [])
One value: a .progress with one bar; several: a .progress-stacked of one-bar .progress elements (Bootstrap 5.3 markup).
public function progress_bar($data = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('progress_bar');
$bars = [];
foreach ((array) $data as $v) {
if ( ! is_array($v)) {
$val = $v;
$v = [];
} else {
$val = $v['val'] ?? 0;
}
$type = ($v['type'] ?? null) ?: ($extra['type'] ?? '');
$class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
[$striped, $animated, $label] = $this->_parent->_progress_bar_item($v, $extra);
$bars[] = [
'val' => $val,
'aria' => ' role="progressbar"' . (strlen($label) ? ' aria-label="' . $this->_aria_text($label) . '"' : '') . ' aria-valuenow="' . $val . '" aria-valuemin="0" aria-valuemax="100"',
'class' => $this->_cls('progress-bar', $type ? 'bg-' . $type : '', ($striped || $animated) ? 'progress-bar-striped' : '', $animated ? 'progress-bar-animated' : '', $class_item),
'style' => $v['style'] ?? '',
'label' => $label,
];
}
$class = $extra['class'] ?? '';
$height = $this->_parent->_css_length($extra['height'] ?? '');
$theme = $this->_parent->_theme_attr($extra['theme'] ?? '');
if (count($bars) > 1) {
$items = [];
foreach ($bars as $bar) {
$items[] = '<div class="progress"' . $bar['aria'] . ' style="width: ' . $bar['val'] . '%' . ($height !== '' ? '; height: ' . $height : '') . '">'
. '<div class="' . $bar['class'] . '"' . ($bar['style'] ? ' style="' . $bar['style'] . '"' : '') . '>' . $bar['label'] . '</div></div>';
}
return '<div class="' . $this->_cls('progress-stacked', $class) . '" id="' . $extra['id'] . '"' . ($height !== '' ? ' style="height: ' . $height . '"' : '') . $theme . '>' . implode(PHP_EOL, $items) . '</div>';
}
$bar = $bars[0] ?? null;
return '<div class="' . $this->_cls('progress', $class) . '" id="' . $extra['id'] . '"' . ($bar ? $bar['aria'] : '') . ($height !== '' ? ' style="height: ' . $height . '"' : '') . $theme . '>'
. ($bar ? '<div class="' . $bar['class'] . '" style="width: ' . $bar['val'] . '%;' . ($bar['style'] ? ' ' . $bar['style'] : '') . '">' . $bar['label'] . '</div>' : '')
. '</div>';
}
public function switch_box($extra, $add_str = '', $translate = false)
A checkbox as a Bootstrap 5 switch: div.form-check.form-switch, the input with role="switch", then its label.
$extra array the input attributes yf_html::check_box() prepared (type, name, id, value, checked, class, desc, ...)$add_str string$translate bool public function switch_box($extra, $add_str = '', $translate = false)
{
$wrap = ['class' => $this->_cls($this->CLASS_SWITCH, $extra['class_add_label_checkbox'] ?? '')];
$extra['role'] = 'switch';
$desc = (string) ($extra['desc'] ?? '');
return '<div' . _attrs($wrap, ['class']) . '>'
. '<input' . _attrs($extra, ['type', 'name', 'id', 'value', 'checked', 'class', 'style', 'disabled', 'required', 'role']) . ($add_str ? ' ' . $add_str : '') . '>'
. (strlen($desc) ? '<label' . _attrs(['class' => 'form-check-label', 'for' => fix_html_attr_id((string) ($extra['id'] ?? ''))], ['class', 'for']) . '>' . ($translate ? t($desc) : $desc) . '</label>' : '')
. '</div>';
}
public function tabs($tabs = [], $extra = [])
public function tabs($tabs = [], $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('tabs');
$extra_by_id = (array) ($extra['by_id'] ?? []);
unset($extra['by_id']);
$links_prefix = ($extra['links_prefix'] ?? null) ?: 'tab_';
[$nav_class, $nav_toggle, $nav_vertical] = $this->_parent->_tabs_nav_options($extra, 'bs5');
$headers = [];
$items = [];
$i = 0;
foreach ((array) $tabs as $k => $v) {
if ( ! is_array($v)) {
$content = $v;
$v = [];
} else {
$content = $v['content'] ?? '';
}
$content = trim($content ?? '');
$_extra = (array) ($extra_by_id[$k] ?? []) + $extra;
if (($_extra['hide_empty'] ?? null) && ! strlen($content)) {
continue;
}
$name = ($v['name'] ?? null) ?: $k;
$desc = ($v['desc'] ?? null) ?: ( ! ($_extra['no_auto_desc'] ?? null) ? ucfirst(str_replace('_', ' ', $name)) : $name);
$id = fix_html_attr_id(preg_replace('~[^a-z0-9_-]+~i', '', ($v['id'] ?? null) ?: $links_prefix . $k));
$is_active = isset($_extra['selected']) ? ($_extra['selected'] == $k) : (++$i == 1);
if (isset($_extra['totals'][$name])) {
$v['badge'] = (int) ($_extra['totals'][$name]['total'] ?? $_extra['totals'][$name]);
}
$badge = isset($v['badge']) ? $this->_badge($v) : '';
$head_id = '';
if ( ! ($_extra['no_headers'] ?? null)) {
$class_head = ($v['class_head'] ?? null) ?: ($_extra['class_head'] ?? '');
$class_head .= ($_extra['class_add_head'] ?? null) ? ' ' . $_extra['class_add_head'] : '';
$_extra_head = (array) ($_extra['tab_head'] ?? []);
$_extra_head['class'] = ($_extra_head['class'] ?? null) ?: $this->_cls('nav-item', $class_head);
$_extra_head['attr'] = (array) ($_extra_head['attr'] ?? []) + ['role' => 'presentation'];
if ($v['disabled'] ?? null) {
$link = '<a class="nav-link disabled" aria-disabled="true">';
} else {
$head_id = $id . '-tab';
$link = '<a class="' . $this->_cls('nav-link', $is_active ? 'active' : '') . '" id="' . $head_id . '" href="#' . $id . '" data-bs-toggle="' . $nav_toggle . '" data-bs-target="#' . $id . '"'
. ' role="tab" aria-controls="' . $id . '" aria-selected="' . ($is_active ? 'true' : 'false') . '">';
}
$headers[] = '<li' . _attrs($_extra_head, ['id', 'class', 'style']) . '>' . $link . (($v['desc_raw'] ?? null) ?: t($desc)) . $badge . '</a></li>';
}
$class_body = ((($_extra['class'] ?? null) ?: ($v['class_body'] ?? null)) ?: ($_extra['class_body'] ?? null)) ?: 'tab-pane';
$class_body .= ($_extra['class_add_body'] ?? null) ? ' ' . $_extra['class_add_body'] : '';
$class_body .= ($is_active || ($_extra['show_all'] ?? null)) ? ' fade show active' : ' fade';
$_extra_body = (array) ($_extra['tab_body'] ?? []);
$_extra_body['id'] = ($_extra_body['id'] ?? null) ?: $id;
$_extra_body['class'] = ($_extra_body['class'] ?? null) ?: $class_body;
$_extra_body['attr'] = (array) ($_extra_body['attr'] ?? []) + ['role' => 'tabpanel'] + ($head_id ? ['aria-labelledby' => $head_id] : []);
$items[] = '<div' . _attrs($_extra_body, ['id', 'class', 'style']) . '>' . $content . '</div>';
}
$vertical = $nav_vertical && $headers;
$nav = $headers ? '<ul id="' . $extra['id'] . '" class="' . $this->_cls($nav_class, $vertical ? 'me-3' : '') . '" role="tablist"' . ($vertical ? ' aria-orientation="vertical"' : '') . '>' . implode(PHP_EOL, $headers) . '</ul>' : '';
$content = '<div id="' . $extra['id'] . '_content" class="' . $this->_cls('tab-content', $vertical ? 'flex-grow-1' : '') . '">' . implode(PHP_EOL, $items) . '</div>';
if ($vertical) {
return '<div class="d-flex align-items-start">' . $nav . PHP_EOL . $content . '</div>';
}
return ($headers ? $nav . PHP_EOL : '') . $content;
}
public function thumbnails($data = [], $extra = [])
public function thumbnails($data = [], $extra = [])
{
$extra = (array) $extra;
$columns = (int) ($extra['columns'] ?? 0) ?: 3;
$col_class = 'col-md-' . (int) round(12 / $columns);
$items = [];
foreach ((array) $data as $v) {
if ( ! is_array($v)) {
$img_src = $v;
$v = [];
} else {
$img_src = $v['img'] ?? '';
}
$head = $v['head'] ?? '';
$body = $v['body'] ?? '';
$class_item = ($v['class_item'] ?? null) ?: ($extra['class_item'] ?? '');
$items[] = '<div class="' . $this->_cls($col_class, $class_item) . '"' . (($v['style'] ?? null) ? ' style="' . $v['style'] . '"' : '') . '>'
. '<div class="card mb-3">'
. '<img class="card-img-top" alt="' . _prepare_html(($v['alt'] ?? null) ?: $head) . '" src="' . _prepare_html($img_src) . '">'
. (($head || $body) ? '<div class="card-body">' . ($head ? '<h5 class="card-title">' . _prepare_html($head) . '</h5>' : '') . $body . '</div>' : '')
. '</div>'
. '</div>';
}
$rows = [];
foreach (array_chunk($items, $columns) as $_items) {
$rows[] = '<div class="' . $this->_cls('row', $extra['class'] ?? '') . '" id="' . $this->_id('thumbnails') . '">' . implode(PHP_EOL, $_items) . '</div>';
}
return implode(PHP_EOL, $rows);
}
public function toast($body = '', $extra = [])
Options of yf_html::toast(). Rendered visible (`show`, also without JS); asset yf_bs5_toasts restarts it with its autohide timer.
public function toast($body = '', $extra = [])
{
$p = $this->_parent;
$extra = (array) $extra;
$type = $p->_color_type($extra['type'] ?? '', '');
$title = $p->_b4_text($extra['title'] ?? '', $extra);
$has_title = strlen($title) > 0;
$show = (bool) ($extra['show'] ?? true);
$urgent = in_array($type, ['danger', 'warning'], true);
$a = [
'class' => $this->_cls('toast', $show ? 'show' : '', $has_title ? '' : 'align-items-center', $type ? 'text-bg-' . $type : '', ! $has_title && $type ? 'border-0' : '', $extra['class_add'] ?? ''),
'id' => $extra['id'] ?? '',
'role' => $urgent ? 'alert' : 'status',
'aria-live' => $urgent ? 'assertive' : 'polite',
'aria-atomic' => 'true',
'data-bs-autohide' => (isset($extra['autohide']) && ! $extra['autohide']) ? 'false' : '',
'data-bs-delay' => isset($extra['delay']) ? (string) (int) $extra['delay'] : '',
'data-yf-toast' => $show ? 'show' : '',
] + $p->_b4_pass($extra);
// these colours have white text, and a toast without header has its close button on it
$white = ! $has_title && in_array($type, ['primary', 'secondary', 'success', 'danger', 'dark'], true);
$close = ($extra['no_close'] ?? null) ? '' : '<button type="button" class="' . $this->_cls('btn-close', $white ? 'btn-close-white' : '', $has_title ? '' : 'me-2 m-auto')
. '" data-bs-dismiss="toast" aria-label="' . t('Close') . '"></button>';
asset('yf_bs5_toasts');
$inner = $has_title
? '<div class="toast-header"><strong class="me-auto">' . $title . '</strong>' . $close . '</div><div class="toast-body">' . $body . '</div>'
: '<div class="d-flex"><div class="toast-body">' . $body . '</div>' . $close . '</div>';
return '<div' . $p->_b4_attrs($a) . '>' . $inner . '</div>';
}
public function toast_container($extra = [])
Options of yf_html::toast_container().
public function toast_container($extra = [])
{
$p = $this->_parent;
$extra = (array) $extra;
$positions = [
'top-start' => 'top-0 start-0',
'top-center' => 'top-0 start-50 translate-middle-x',
'top-end' => 'top-0 end-0',
'middle-start' => 'top-50 start-0 translate-middle-y',
'middle-center' => 'top-50 start-50 translate-middle',
'middle-end' => 'top-50 end-0 translate-middle-y',
'bottom-start' => 'bottom-0 start-0',
'bottom-center' => 'bottom-0 start-50 translate-middle-x',
'bottom-end' => 'bottom-0 end-0',
];
$position = $positions[$extra['position'] ?? ''] ?? $positions['top-end'];
$toasts = $extra['toasts'] ?? '';
asset('yf_bs5_toasts');
$a = ['class' => $this->_cls('toast-container position-fixed p-3', $position, $extra['class_add'] ?? ''), 'id' => $extra['id'] ?? '', 'data-yf-toasts' => '1'] + $p->_b4_pass($extra);
return '<div' . $p->_b4_attrs($a) . '>' . (is_array($toasts) ? implode(PHP_EOL, $toasts) : (string) $toasts) . '</div>';
}
public function tooltip($text = '', $extra = [])
public function tooltip($text = '', $extra = [])
{
if (is_array($text)) {
$extra = (array) $extra + $text;
$text = '';
}
if ( ! is_array($extra)) {
$extra = [];
}
foreach ($this->POPOVER_LEGACY_DATA as $opt) {
if (isset($extra['data-' . $opt])) {
$extra['data-bs-' . $opt] = ($extra['data-bs-' . $opt] ?? null) ?: $extra['data-' . $opt];
unset($extra['data-' . $opt]);
}
}
$extra['text'] = ($extra['text'] ?? null) ?: $text;
$icon = ($extra['icon'] ?? null) ?: 'fa fa-question-circle';
$extra['class'] = (($extra['class'] ?? null) ?: 'yf_tip') . (($extra['class_add'] ?? null) ? ' ' . $extra['class_add'] : '');
$extra['data-bs-content'] = ($extra['data-bs-content'] ?? null) ?: _prepare_html($extra['text']);
$extra['data-bs-toggle'] = ($extra['data-bs-toggle'] ?? null) ?: 'popover';
$extra['data-bs-trigger'] = ($extra['data-bs-trigger'] ?? null) ?: 'hover focus';
$extra['data-bs-container'] = ($extra['data-bs-container'] ?? null) ?: 'body';
$extra['data-bs-html'] = ($extra['data-bs-html'] ?? null) ?: 'true';
$extra['attr'] = (array) ($extra['attr'] ?? []) + ['tabindex' => '0'];
asset('yf_bs5_tooltips');
return ( ! ($extra['no_nbsp'] ?? null) ? ' ' : '') . '<span' . _attrs($extra, ['id', 'class', 'style']) . '><i class="' . $icon . '"></i></span>';
}
public function well($body = '', $extra = [])
public function well($body = '', $extra = [])
{
$extra = (array) $extra;
$extra['id'] = ($extra['id'] ?? null) ?: $this->_id('well');
return '<div class="' . $this->_cls('p-3 mb-3 bg-body-tertiary border rounded', $extra['class'] ?? '') . '" id="' . $extra['id'] . '">' . $body . '</div>';
}
_ hooks and helpers; not routable as page actionspublic function _aria_text($html)
Text of HTML for an attribute (aria-label): tags dropped, entities decoded, then escaped again with the template braces.
public function _aria_text($html)
{
return _htmlchars(_escape_tpl_braces(trim(html_entity_decode(strip_tags((string) $html), ENT_QUOTES | ENT_HTML5, 'UTF-8'))));
}
public function _badge($v)
public function _badge($v)
{
return ' <span class="badge text-bg-' . (($v['class_badge'] ?? null) ?: 'info') . '">' . $v['badge'] . '</span>';
}
public function _carousel_attrs($extra, $data)
Attributes of .carousel from data-bs-ride on: ride (`carousel` default: autoplay on load; true: after the first slide change; false: none), interval (ms or false), pause (hover or false), touch / wrap / keyboard (false), theme. A carousel with one of these options (or a slide `interval`) loads yf_bs5_carousel, which starts it again after an htmx swap.
$extra array public function _carousel_attrs($extra, $data)
{
$p = $this->_parent;
$ride = $extra['ride'] ?? 'carousel';
$a = ['data-bs-ride' => $ride === true || $ride === 'true' ? 'true' : ($ride ? 'carousel' : '')];
if (array_key_exists('interval', $extra)) {
$a['data-bs-interval'] = $extra['interval'] === false ? 'false' : (string) max(0, (int) $extra['interval']);
}
if (array_key_exists('pause', $extra)) {
$a['data-bs-pause'] = $extra['pause'] === 'hover' ? 'hover' : 'false';
}
foreach (['touch', 'wrap', 'keyboard'] as $k) {
(isset($extra[$k]) && ! $extra[$k]) && $a['data-bs-' . $k] = 'false';
}
$opts = array_intersect_key($extra, array_flip(['fade', 'ride', 'interval', 'pause', 'touch', 'wrap', 'keyboard', 'theme']));
foreach ((array) $data as $v) {
$p->B5_ITEM_OPTIONS && is_array($v) && isset($v['interval']) && $opts['slide'] = 1;
}
$opts && asset('yf_bs5_carousel');
return $p->_b4_attrs($a) . $p->_theme_attr($extra['theme'] ?? '');
}
public function _class_attr($class, &$add_str = '')
` class="$class"`, or '' with $class merged into the class attribute $add_str already has (a second class attribute would be ignored).
public function _class_attr($class, &$add_str = '')
{
$add_str = (string) $add_str;
if (preg_match('~(?:^|\s)class=(["\'])(.*?)\1~i', $add_str, $m)) {
if ( ! in_array($class, preg_split('~\s+~', $m[2]), true)) {
$add_str = str_replace($m[0], substr($m[0], 0, -strlen($m[2]) - 1) . trim($m[2] . ' ' . $class) . $m[1], $add_str);
}
return '';
}
return ' class="' . $class . '"';
}
public function _cls(...$classes)
Space-joined non-empty class names.
public function _cls(...$classes)
{
return implode(' ', array_filter(array_map('trim', array_map('strval', $classes)), 'strlen'));
}
public function _dropdown_items($values, $selected, $translate)
Dropdown menu items of div_box() / button_box() and the text of the selected one.
public function _dropdown_items($values, $selected, $translate)
{
$items = [];
$selected_val = '';
foreach ((array) $values as $key => $cur_value) {
$is_selected = $key == $selected;
$val = $translate ? t($cur_value) : $cur_value;
$items[] = '<li><a class="' . $this->_cls('dropdown-item', $is_selected ? 'active' : '') . '" role="button" tabindex="0" data-value="' . $key . '"' . ($is_selected ? ' data-selected="selected" aria-current="true"' : '') . '>' . $val . '</a></li>';
if ($is_selected) {
$selected_val = $val;
}
}
return [$items, $selected_val];
}
public function _id($name)
Next value of the shared yf_html id counter $name, as "<name>_<n>".
public function _id($name)
{
$ids = &$this->_parent->_ids;
$ids[$name] = ($ids[$name] ?? 0) + 1;
return $name . '_' . $ids[$name];
}
public function _init()
public function _init()
{
$this->_parent = _class('html');
}
public function _modal_dialog_class($extra)
Classes of .modal-dialog: size (sm, lg, xl), fullscreen (true, or a breakpoint sm...xxl: fullscreen below it), centered, scrollable.
$extra array public function _modal_dialog_class($extra)
{
$size = in_array($extra['size'] ?? '', ['sm', 'lg', 'xl'], true) ? 'modal-' . $extra['size'] : '';
$fs = $extra['fullscreen'] ?? null;
$bp = is_string($fs) ? preg_replace('~-down$~', '', $fs) : '';
$fs = in_array($bp, ['sm', 'md', 'lg', 'xl', 'xxl'], true) ? 'modal-fullscreen-' . $bp . '-down' : (in_array($fs, [true, 1, '1'], true) ? 'modal-fullscreen' : '');
return $this->_cls('modal-dialog', $size, $fs, ! empty($extra['centered']) ? 'modal-dialog-centered' : '', ! empty($extra['scrollable']) ? 'modal-dialog-scrollable' : '');
}
public function _pagination_item($key, $link, $text, $extra)
One pagination() item: a link, the current page (`current`) or a disabled entry (`disabled`).
$key int|string$link string$text string$extra array public function _pagination_item($key, $link, $text, $extra)
{
$state = ($this->_parent ?: html())->_pagination_state($key, $extra);
if ($state === 'disabled') {
return '<li class="page-item disabled"><span class="page-link">' . $text . '</span></li>';
}
if ($state === 'active') {
return '<li class="page-item active" aria-current="page"><a class="page-link" href="' . $link . '">' . $text . '</a></li>';
}
return '<li class="page-item"><a class="page-link" href="' . $link . '">' . $text . '</a></li>';
}