mirror of
https://github.com/webmin/webmin.git
synced 2026-09-23 05:50:39 +01:00
Add small and rounded badge options
This commit is contained in:
@@ -60,6 +60,24 @@ sub decode_attr {
|
||||
|
||||
my $xss = q{x"><script>alert(1)</script><b onmouseover="alert(1)};
|
||||
|
||||
# A decoded value elsewhere on the page must not double-encode the note icon.
|
||||
{
|
||||
my $decoded = 'efi';
|
||||
utf8::upgrade($decoded);
|
||||
foreach my $prefix ('efi', $decoded) {
|
||||
my $html = $prefix.main::ui_note('Note', 0);
|
||||
# Model the UTF-8 response bytes and the browser's decoding step.
|
||||
utf8::encode($html) if (utf8::is_utf8($html));
|
||||
utf8::decode($html);
|
||||
like($html, qr/(?:\x{24d8}|ⓘ|ⓘ)/i,
|
||||
'ui_note icon survives byte and decoded page strings');
|
||||
}
|
||||
my $html = main::ui_note("Note \x{20ac}", 0);
|
||||
like($html, qr/(?:\x{24d8}|ⓘ|ⓘ)/i,
|
||||
'ui_note icon survives decoded note text');
|
||||
like($html, qr/Note \x{20ac}/, 'ui_note preserves decoded note text');
|
||||
}
|
||||
|
||||
# ---- escaping contract -----------------------------------------------------
|
||||
|
||||
assert_no_handler_injection(
|
||||
@@ -71,6 +89,9 @@ assert_no_handler_injection(
|
||||
'ui_card title+desc');
|
||||
assert_no_handler_injection(main::ui_badge($xss, 'success'),
|
||||
'ui_badge text');
|
||||
assert_no_handler_injection(
|
||||
main::ui_badge('B', 'success', { 'class' => $xss, 'title' => $xss }),
|
||||
'ui_badge class+title');
|
||||
assert_no_handler_injection(main::ui_chip($xss), 'ui_chip text');
|
||||
assert_no_handler_injection(main::ui_code($xss), 'ui_code');
|
||||
assert_no_handler_injection(main::ui_tip('<b>x</b>', $xss), 'ui_tip');
|
||||
@@ -119,6 +140,19 @@ like(main::ui_badge('down', 'err'), qr/ui_badge_danger/,
|
||||
like(main::ui_badge('what', 'bogus<'), qr/ui_badge_neutral/,
|
||||
'unknown state falls back to neutral');
|
||||
|
||||
# Badges take a smaller size, rounded ends and extra class names
|
||||
{
|
||||
my $plain = main::ui_badge('B', 'info');
|
||||
unlike($plain, qr/ui_badge_small|ui_badge_rounded/,
|
||||
'a badge is full-sized and square by default');
|
||||
my $html = main::ui_badge('B', 'info',
|
||||
{ 'small' => 1, 'rounded' => 1, 'class' => 'mine' });
|
||||
like($html, qr/\bui_badge_small\b/, 'small option adds its class');
|
||||
like($html, qr/\bui_badge_rounded\b/, 'rounded option adds its class');
|
||||
like($html, qr/\bui_badge_info\b/, 'and the state class stays');
|
||||
like($html, qr/\bmine\b/, 'extra class names are passed through');
|
||||
}
|
||||
|
||||
# The scheme option stamps the wrapper for the dark or auto palette
|
||||
like(main::ui_page_start({ 'scheme' => 'auto' }),
|
||||
qr/data-ui-scheme="auto"/, 'scheme auto stamps the wrapper');
|
||||
|
||||
@@ -13,7 +13,7 @@ Every tab of `index.cgi` is built by one function in `ui-demo-pages.pl` :
|
||||
| Tab | Function | Shows |
|
||||
|------------|-----------------------|------------------------------------------------------------------|
|
||||
| Cards | `demo_cards_tab` | `ui_card` in its variations : buttons inside a card, header actions and footer, state accents with icon titles, a two-column `ui_dl`, a flush list filtered by a `ui_search` in the header, a standard table inside a card, a metric card with `ui_stat` and inline `ui_progress`, a card printed with `ui_card_start`/`ui_card_end`; `ui_stats`; `ui_grid` with the `template` option |
|
||||
| Elements | `demo_elements_tab` | a `ui_dl` with help bubbles and HTML values, `ui_stats` with icons and links, `ui_feed` with an HTML event, `ui_empty_state`, then badges with their icon, dot and title options, chips, `ui_code`, `ui_note`, `ui_help`, `ui_tip`, every `ui_progress` variation and the ring gauges, and `ui_svg_icon` |
|
||||
| Elements | `demo_elements_tab` | a `ui_dl` with help bubbles and HTML values, `ui_stats` with icons and links, `ui_feed` with an HTML event, `ui_empty_state`, then badges with their icon, dot, title, small and rounded options, chips, `ui_code`, `ui_note`, `ui_help`, `ui_tip`, every `ui_progress` variation and the ring gauges, and `ui_svg_icon` |
|
||||
| Forms | `demo_forms_tab` | the usual `ui_table_start` / `ui_table_row` form with `ui_toggle`, `ui_search`, the date chooser, password fields and a table of inputs inside one row built with `ui_columns_start` / `ui_columns_row` with the `no-hover` class, as the Nginx module's URL rewrites; a second form of choosers : `file_chooser_button` for files and directories, `ui_user_textbox`, `ui_group_textbox`, `ui_users_textbox`, `ui_groups_textbox`, and an `hlink` help link |
|
||||
| Choices | `demo_choices_tab` | `ui_choice`, `ui_select_switch` and `ui_radio_list` for backup destinations and IP addresses; `ui_multi_select_list` for backup servers with modes and child folding, secondary groups, and an empty list with a custom label |
|
||||
| Buttons | `demo_buttons_tab` | `ui_submit`, `ui_reset` and `ui_link_button` in one row, with a disabled and a confirmed one; a form ended by `ui_form_end`, one by `ui_form_grouped_buttons`, one by `ui_form_end_side_by_side` with a separate form at the right; a `ui_confirmation_form` page |
|
||||
|
||||
@@ -372,6 +372,22 @@ $rv .= ui_grid([
|
||||
ui_chip('journal'),
|
||||
ui_chip('ipv6'),
|
||||
]),
|
||||
# The small badge, which does not outweigh the
|
||||
# text beside it, and the rounded one. Only
|
||||
# badges take a rounded option; the widgets are
|
||||
# drawn square by default
|
||||
ui_cluster([
|
||||
ui_badge($text{'index_running'}, 'success',
|
||||
{ 'small' => 1 }),
|
||||
ui_badge($text{'index_stopped'}, 'danger',
|
||||
{ 'small' => 1, 'dot' => 1 }),
|
||||
ui_badge($text{'index_off'}, 'neutral',
|
||||
{ 'small' => 1, 'icon' => '' }),
|
||||
ui_badge($text{'index_running'}, 'success',
|
||||
{ 'rounded' => 1 }),
|
||||
ui_badge($text{'index_syncing'}, 'info',
|
||||
{ 'small' => 1, 'rounded' => 1 }),
|
||||
]),
|
||||
# Inline code, a note, the existing help bubble, and
|
||||
# ui_tip, which gives any HTML the same theme tooltip
|
||||
ui_cluster([
|
||||
|
||||
15
ui-lib.pl
15
ui-lib.pl
@@ -3756,8 +3756,10 @@ return &theme_ui_note(@_) if (defined(&theme_ui_note));
|
||||
my ($text, $whitespace) = @_;
|
||||
$whitespace //= 2;
|
||||
my $whitespace_str = " " x $whitespace;
|
||||
# Written as an entity, not a literal character, so the icon survives
|
||||
# whether the page is handled as bytes or as decoded text
|
||||
return "<font class='ui_note' style='font-size:92%;opacity:0.66'>".
|
||||
"${whitespace_str}ⓘ $text".
|
||||
"${whitespace_str}ⓘ $text".
|
||||
"</font>";
|
||||
}
|
||||
|
||||
@@ -4955,6 +4957,12 @@ default icon. Set to an empty string for no icon at all.
|
||||
|
||||
=item title - Tooltip text for the badge.
|
||||
|
||||
=item small - Set to 1 for a smaller badge, so it does not outweigh the text next to it. List rows already draw their badges small.
|
||||
|
||||
=item rounded - Set to 1 for rounded ends, instead of the square corners badges use by default.
|
||||
|
||||
=item class - Extra CSS class names for the badge.
|
||||
|
||||
=cut
|
||||
sub ui_badge
|
||||
{
|
||||
@@ -4974,7 +4982,10 @@ elsif ($ui_state_icons{$state}) {
|
||||
$icon = &ui_svg_icon($ui_state_icons{$state}, { 'size' => 13 });
|
||||
}
|
||||
return &ui_tag('span', $icon.&ui_tag('span', &html_escape($label)),
|
||||
&_ui_attrs({ 'class' => "ui_badge ui_badge_$state",
|
||||
&_ui_attrs({ 'class' => &_ui_class('ui_badge', 'ui_badge_'.$state,
|
||||
$opts->{'small'} ? 'ui_badge_small' : undef,
|
||||
$opts->{'rounded'} ? 'ui_badge_rounded' : undef,
|
||||
$opts->{'class'}),
|
||||
'title' => $opts->{'title'} }));
|
||||
}
|
||||
|
||||
|
||||
@@ -513,6 +513,17 @@ a.ui_stat:hover .ui_stat_value { color: var(--ui-accent); }
|
||||
border-color: var(--ui-neutral-line);
|
||||
color: var(--ui-neutral-text);
|
||||
}
|
||||
/* A smaller badge, so it does not outweigh the text next to it. The
|
||||
* icon scales with the text rather than staying at full size */
|
||||
.ui_badge_small {
|
||||
padding: 0 8px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.ui_badge_small .ui_svg_icon { width: 1em; height: 1em; }
|
||||
/* Rounded ends, only when a caller asks for them. Widgets are drawn
|
||||
* square, apart from the toggle and the badge dot, which are round */
|
||||
.ui_badge_rounded { border-radius: 999px; }
|
||||
.ui_chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -564,16 +575,17 @@ a.ui_stat:hover .ui_stat_value { color: var(--ui-accent); }
|
||||
padding-top: 1px;
|
||||
}
|
||||
.ui_list_meta { color: var(--ui-fg-muted); font-size: 0.9em; }
|
||||
/* Badges in list rows are chip-sized, so they do not outweigh the text */
|
||||
/* Badges in list rows are small, so they do not outweigh the text */
|
||||
.ui_list_item .ui_badge {
|
||||
padding: 1px 8px;
|
||||
padding: 0 8px;
|
||||
gap: 4px;
|
||||
font-size: 0.86em;
|
||||
font-size: 0.81em;
|
||||
}
|
||||
.ui_list_item .ui_badge .ui_svg_icon { width: 1em; height: 1em; }
|
||||
/* Description text is 0.93em, so badges and chips inside it are scaled
|
||||
* back by that much to match the ones next to the title */
|
||||
.ui_list_desc :is(.ui_badge, .ui_chip) { font-size: calc(0.86em / 0.93); }
|
||||
.ui_list_desc .ui_badge { font-size: calc(0.81em / 0.93); }
|
||||
.ui_list_desc .ui_chip { font-size: calc(0.86em / 0.93); }
|
||||
.ui_list_icon { padding-top: 2px; color: var(--ui-fg-muted); }
|
||||
a.ui_list_link, a.ui_list_link:hover {
|
||||
color: inherit !important;
|
||||
|
||||
Reference in New Issue
Block a user