return function () {
$self = url('/@object/@action/' . urlencode('html/bs5_components'));
$htmx = _class('htmx');
// the htmx button asks for a fragment; the toast travels in the HX-Trigger header (text only)
if ($htmx->_wants_fragment() && ($_GET['hx'] ?? '') === 'toast') {
$htmx->_toast('Saved at ' . date('H:i:s') . ' (an HX-Trigger toast).', ['title' => 'htmx', 'type' => 'success']);
return $htmx->_result('<span id="demo-hx-result">Last htmx answer: ' . date('H:i:s') . '</span>');
}
// the messages of this request go out as toasts (bs5); Bootstrap 3 keeps the alerts
if (isset($_GET['messages'])) {
common()->MESSAGES_AS_TOASTS = true;
common()->message_success('Saved: a success message.');
common()->message_info('An info message.');
common()->message_error('An error message stays until it is closed.');
}
asset('htmx');
$intro = '<p class="docs-demo-intro">New components of <code>html()</code> (<code>yf_html</code>, Bootstrap 5 markup in
<code>yf_html_bs5</code>). Switch the framework with <a href="' . $self . '?bs=5">?bs=5</a> / <a href="' . $self . '?bs=3">?bs=3</a>:
each one is a pure addition with a Bootstrap 3 fallback, and the existing widgets render exactly as before. Text options are
escaped; bodies (<code>body</code>, the collapse / ratio content, the toast body) are developer-supplied HTML and are not.</p>
<ul class="docs-demo-intro">
<li><code>toast($body, [\'title\', \'type\', \'autohide\' => true, \'delay\' => 5000, \'show\', \'no_close\', \'id\', \'class_add\'])</code> in
<code>toast_container([\'toasts\' => [...], \'position\' => \'top-end\'])</code> (top / middle / bottom - start / center / end);
the jQuery-free asset <code>yf_bs5_toasts</code> shows them on load and after htmx swaps. Bootstrap 3: <code>alert()</code>.</li>
<li><code>_class(\'htmx\')->_toast($text, [\'title\', \'type\', \'autohide\', \'delay\'])</code>: a toast through the <code>HX-Trigger</code>
header (event <code>yf-toast</code>, shown as text in the page\'s toast container); bs5 only.</li>
<li><code>$PROJECT_CONF[\'common\'][\'MESSAGES_AS_TOASTS\'] = true</code> (default off): <code>common()->message_*()</code> become toasts
under bs5, errors stay until closed. <a href="' . $self . '?messages=1" id="demo-messages">Show messages</a>.</li>
<li><code>spinner([\'type\' => \'border|grow\', \'size\' => \'sm\', \'color\', \'text\', \'class_add\' => \'htmx-indicator\'])</code>;
Bootstrap 3: <code>fa fa-spinner fa-spin</code>.</li>
<li><code>placeholder([\'lines\' => 3, \'widths\' => [12, 8, 6], \'glow\' => true|\'wave\', \'size\', \'color\'])</code>; Bootstrap 3: an empty div.</li>
<li><code>badge($text, [\'type\', \'pill\'])</code>: <code>badge text-bg-*</code> / Bootstrap 3 <code>label label-*</code> (a pill too: Bootstrap 3 badges have no colours).</li>
<li><code>button($text, [\'type\', \'outline\', \'size\' => \'sm|lg\', \'icon\', \'link\', \'button_type\', \'active\', \'disabled\'])</code>;
Bootstrap 3: <code>btn-default</code> for outline, <code>btn-xs</code> for sm.</li>
<li><code>offcanvas([\'id\', \'title\', \'body\', \'placement\' => \'start|end|top|bottom\', \'backdrop\', \'scroll\', \'show\'])</code> and
<code>offcanvas_button($text, $id, $button_options)</code>; Bootstrap 3: a <code>modal()</code>.</li>
<li><code>dropdown($items, [\'text\', \'split\', \'link\', \'direction\' => \'up|end|start|center\', \'align\' => \'end\', \'dark\', \'auto_close\'])</code>;
items <code>$link => $text</code>, <code>\'-\'</code>, <code>[\'header\' => ...]</code>, <code>[\'text\', \'link\', \'active\', \'disabled\', \'icon\']</code>;
Bootstrap 3: <code>dropup</code>, <code>dropdown-menu-right</code>. <code>link</code> options take trusted URLs; <code>javascript:</code>, <code>data:</code>, <code>vbscript:</code> become <code>#</code>.</li>
<li><code>collapse($body, [\'button\' => \'More filters\', \'show\', \'button_extra\', \'id\'])</code>; Bootstrap 3 collapse.</li>
<li><code>ratio($html, [\'ratio\' => \'16x9\'])</code> (4x3, 1x1, 21x9 or any W x H); Bootstrap 3: <code>embed-responsive-16by9</code>.</li>
</ul>';
$toasts = html()->toast_container([
'id' => 'demo-toasts',
// the MESSAGES_AS_TOASTS container takes the default corner (top-end); two containers in one corner overlap
'position' => 'bottom-end',
'toasts' => [
html()->toast('A toast rendered with the page; it hides after 4 seconds.', ['title' => 'Welcome', 'delay' => 4000, 'id' => 'demo-toast-auto']),
html()->toast('This one stays until it is closed.', ['type' => 'warning', 'autohide' => false, 'id' => 'demo-toast-stay']),
],
]);
$hx = '<p>' . html()->button('Save through htmx', ['type' => 'primary', 'id' => 'demo-hx-btn', 'attr' => [
'hx-get' => $self . '?hx=toast',
'hx-target' => '#demo-hx-result',
'hx-swap' => 'outerHTML',
'hx-indicator' => '#demo-hx-spinner',
]])
. ' ' . html()->spinner(['size' => 'sm', 'class_add' => 'htmx-indicator', 'id' => 'demo-hx-spinner'])
. ' <span id="demo-hx-result"></span></p>';
$spinners = '<p id="demo-spinners">' . html()->spinner() . ' ' . html()->spinner(['type' => 'grow', 'color' => 'success'])
. ' ' . html()->spinner(['size' => 'sm', 'color' => 'danger']) . ' ' . html()->spinner(['type' => 'grow', 'size' => 'sm', 'color' => 'primary']) . '</p>';
$placeholders = '<div class="row"><div class="col-sm-6">' . html()->placeholder(['id' => 'demo-placeholder']) . '</div>'
. '<div class="col-sm-6">' . html()->placeholder(['lines' => 2, 'widths' => [10, 4], 'glow' => 'wave', 'size' => 'lg', 'color' => 'primary']) . '</div></div>';
$badges = '<p id="demo-badges">';
foreach (['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'] as $type) {
$badges .= html()->badge($type, ['type' => $type]) . ' ';
}
$badges .= html()->badge('12', ['type' => 'danger', 'pill' => 1]) . ' ' . html()->badge('<b>escaped</b> {braces}', ['type' => 'info']) . '</p>';
$buttons = '<p id="demo-buttons">' . html()->button('Default') . ' ' . html()->button('Primary', ['type' => 'primary'])
. ' ' . html()->button('Outline danger', ['type' => 'danger', 'outline' => 1]) . ' ' . html()->button('Edit', ['type' => 'success', 'size' => 'sm', 'icon' => 'fa fa-pencil'])
. ' ' . html()->button('Large link', ['type' => 'info', 'size' => 'lg', 'link' => $self]) . ' ' . html()->button('Disabled', ['type' => 'secondary', 'disabled' => 1])
. ' ' . html()->button('', ['icon' => 'fa fa-trash', 'title' => 'Delete', 'type' => 'link']) . '</p>';
$offcanvas = html()->offcanvas([
'id' => 'demo-offcanvas',
'title' => 'Filters',
'body' => '<p>Side panel content: filters, a mobile menu...</p>' . html()->button('Close', ['attr' => ['data-bs-dismiss' => 'offcanvas', 'data-dismiss' => 'modal']]),
'placement' => 'end',
])
. '<p>' . html()->offcanvas_button('Open the panel', 'demo-offcanvas', ['type' => 'primary', 'id' => 'demo-offcanvas-btn']) . '</p>';
$items = [
$self . '?act=edit' => 'Edit',
$self . '?act=view' => 'View',
['text' => 'Active item', 'link' => $self . '?act=active', 'active' => 1],
['text' => 'Disabled', 'link' => $self . '?act=x', 'disabled' => 1],
'-',
['header' => 'Danger zone'],
['text' => 'Delete', 'link' => $self . '?act=delete', 'icon' => 'fa fa-trash', 'class_add' => 'text-danger'],
];
$dropdowns = '<p>' . html()->dropdown($items, ['text' => 'Actions', 'id' => 'demo-dropdown'])
. ' ' . html()->dropdown($items, ['text' => 'Split', 'split' => 1, 'link' => $self . '?act=main', 'type' => 'primary', 'id' => 'demo-dropdown-split'])
. ' ' . html()->dropdown($items, ['text' => 'Up, right-aligned', 'direction' => 'up', 'align' => 'end', 'id' => 'demo-dropdown-up'])
. ' ' . html()->dropdown($items, ['text' => 'Dark', 'dark' => 1, 'type' => 'dark', 'id' => 'demo-dropdown-dark']) . '</p>';
$collapse = html()->collapse('<div class="well card card-body">Hidden filters: shown and hidden by the button.</div>', ['button' => 'More filters', 'id' => 'demo-collapse']);
$ratio = '<div class="row"><div class="col-sm-6">' . html()->ratio('<div class="embed-responsive-item" style="background:#6c757d;color:#fff;padding:1em">16x9</div>', ['id' => 'demo-ratio']) . '</div>'
. '<div class="col-sm-3">' . html()->ratio('<div class="embed-responsive-item" style="background:#0d6efd;color:#fff;padding:1em">1x1</div>', ['ratio' => '1x1']) . '</div>'
. '<div class="col-sm-3">' . html()->ratio('<div class="embed-responsive-item" style="background:#198754;color:#fff;padding:1em">3:2</div>', ['ratio' => '3:2']) . '</div></div>';
return $intro . $toasts
. '<h3>Toasts, htmx toast, spinner as hx-indicator</h3>' . $hx
. '<h3>Spinners</h3>' . $spinners
. '<h3>Placeholders</h3>' . $placeholders
. '<h3>Badges</h3>' . $badges
. '<h3>Buttons</h3>' . $buttons
. '<h3>Offcanvas</h3>' . $offcanvas
. '<h3>Dropdowns</h3>' . $dropdowns
. '<h3>Collapse</h3>' . $collapse
. '<h3>Ratio</h3>' . $ratio;
};