A status label came tonal (the colour's container) or outlined; a label that has to stand out in the colour itself fell back to the filled count, which is 16px and hidden from screen readers. solid draws the label shape in the colour's filled pair and is spoken like the others. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
89 lines
4.7 KiB
PHP
89 lines
4.7 KiB
PHP
{{-- A badge: M3's count or dot on an icon, or a short status label.
|
|
|
|
M3's two badges (BadgeTokens, androidx Compose Material 3, Apache-2.0):
|
|
- `<x-badge />` — the small badge, a 6px dot: something new, no number.
|
|
- `<x-badge value="3" />` — the large badge, 16px tall, label-small: a count. `max` caps what is
|
|
shown ("99+").
|
|
Both are `error` by default, as M3 draws them. `floating` pins one to the top-end corner of a
|
|
`relative` parent — an icon or an icon button:
|
|
|
|
<span class="relative inline-flex"><x-icon name="notifications" /><x-badge value="4" floating /></span>
|
|
|
|
The status label is not an M3 badge but every app needs one: `tonal` draws the value in the
|
|
colour's container ("Expired" in error-container), `solid` in the colour itself (a label that
|
|
has to stand out, "Built in" in primary), `outline` in a neutral edge. `color` (alias
|
|
`tone`): `error` (the default), `primary`, `secondary`, `tertiary`, `success`, `warning`, `info`,
|
|
and two without a hue of their own:
|
|
- `neutral` — neutral ink on every variant: on-surface-variant with surface text as a dot or
|
|
count (the ink an outline badge already writes in), surface-container-high with
|
|
on-surface-variant text when `tonal`, the outline-variant edge when `outline`.
|
|
- `plain` — no background, text or border colour at all, only shape, size and type, so the
|
|
caller's classes paint it: `<x-badge value="Run" tonal color="plain" class="bg-tertiary-container text-on-tertiary-container" />`.
|
|
|
|
The value is `value`, or the slot, which renders as HTML — an icon beside the word:
|
|
`<x-badge tonal><x-icon name="bolt" class="size-3" /> Pro</x-badge>`. With neither it is a dot.
|
|
|
|
A count or dot says nothing to a screen reader on its own: give the icon's control a label
|
|
that includes it ("Notifications, 4 new"), or pass `label` here. --}}
|
|
|
|
@props([
|
|
'value' => null,
|
|
'max' => null,
|
|
'color' => null,
|
|
'tone' => null,
|
|
'tonal' => false,
|
|
'solid' => false,
|
|
'outline' => false,
|
|
'floating' => false,
|
|
'label' => null,
|
|
])
|
|
|
|
@php
|
|
$color = in_array($color ?? $tone, ['primary', 'secondary', 'tertiary', 'error', 'success', 'warning', 'info', 'neutral', 'plain'], true) ? ($color ?? $tone) : 'error';
|
|
// hasActualContent(): a slot holding only a comment, or an empty @foreach, is still a dot.
|
|
$markup = $value === null && $slot->hasActualContent();
|
|
$text = $value ?? ($markup ? trim((string) $slot) : null);
|
|
$dot = blank($text);
|
|
$status = ($tonal || $solid || $outline) && ! $dot;
|
|
|
|
if (! $dot && $max !== null && is_numeric($text) && (int) $text > (int) $max) {
|
|
$text = $max.'+';
|
|
$markup = false;
|
|
}
|
|
|
|
$filled = [
|
|
'primary' => 'bg-primary text-on-primary', 'secondary' => 'bg-secondary text-on-secondary', 'tertiary' => 'bg-tertiary text-on-tertiary',
|
|
'error' => 'bg-error text-on-error', 'success' => 'bg-success text-on-success', 'warning' => 'bg-warning text-on-warning', 'info' => 'bg-info text-on-info',
|
|
'neutral' => 'bg-on-surface-variant text-surface',
|
|
];
|
|
$container = [
|
|
'primary' => 'bg-primary-container text-on-primary-container', 'secondary' => 'bg-secondary-container text-on-secondary-container', 'tertiary' => 'bg-tertiary-container text-on-tertiary-container',
|
|
'error' => 'bg-error-container text-on-error-container', 'success' => 'bg-success-container text-on-success-container', 'warning' => 'bg-warning-container text-on-warning-container', 'info' => 'bg-info-container text-on-info-container',
|
|
'neutral' => 'bg-surface-container-high text-on-surface-variant',
|
|
];
|
|
$paint = match (true) {
|
|
$color === 'plain' => '',
|
|
! $status, $solid => $filled[$color],
|
|
$tonal => $container[$color],
|
|
default => 'border-outline-variant text-on-surface-variant',
|
|
};
|
|
|
|
$attributes = $attributes
|
|
->class([
|
|
'inline-flex shrink-0 items-center justify-center whitespace-nowrap',
|
|
'size-1.5 rounded-corner-full' => $dot,
|
|
'h-4 min-w-4 rounded-corner-full px-1 type-label-sm tabular-nums' => ! $dot && ! $status,
|
|
'h-6 gap-1 rounded-corner-sm px-2 type-label-md' => $status,
|
|
'border' => $outline && ! $tonal && ! $solid && ! $dot,
|
|
$paint => $paint !== '',
|
|
'absolute top-0.5 end-0.5' => $floating && $dot,
|
|
'absolute -top-1 start-[calc(100%-0.75rem)]' => $floating && ! $dot,
|
|
])
|
|
->merge(array_filter([
|
|
'aria-label' => $label,
|
|
'aria-hidden' => $label === null && ! $status ? 'true' : null,
|
|
]));
|
|
@endphp
|
|
|
|
<span {{ $attributes }}>@unless ($dot)@if ($markup){{ $slot }}@else{{ $text }}@endif@endunless</span>
|