Plan step 36. <x-badge> renders data-md-badge with data-md-dot, data-md-variant (solid, tonal or outline, resolved in that order), data-md-color and data-md-floating; badge.css draws BadgeTokens' dot and count, the status label, each colour's pair of roles through custom properties, and M3's anchor geometry when floating. A plain badge sets no colour, so the caller's CSS paints it. NavigationBarTest (navigation group) asserts the dot by data-md-dot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
81 lines
3.9 KiB
PHP
81 lines
3.9 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, and an `outline` status label draws its edge in
|
|
`outline`, not `outline-variant`: M3 asks a badge for 3:1 against what is behind it, and
|
|
`outline-variant` is the decorative role dividers use. `floating` pins one to the top-end
|
|
corner of a `position: relative` parent, on M3's anchor geometry — the dot flush in the corner,
|
|
the count 2px above it — an icon or an icon button:
|
|
|
|
<span style="position: relative; display: 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 edge when `outline`.
|
|
- `plain` — no background, text or border colour at all, only shape, size and type, so the
|
|
caller's CSS paints it.
|
|
|
|
The value is `value`, or the slot, which renders as HTML — an icon beside the word:
|
|
`<x-badge tonal><x-icon name="bolt" size="12" /> 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.
|
|
|
|
Drawn by resources/css/components/badge.css from `data-md-dot` (the small badge),
|
|
`data-md-variant` (a status label: `solid`, `tonal` or `outline`, the first of them that is
|
|
set, in that order), `data-md-color` and `data-md-floating`. --}}
|
|
|
|
@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;
|
|
}
|
|
|
|
// `solid` wins over `tonal`, and either over `outline`, as the three have always resolved.
|
|
$variant = match (true) {
|
|
! $status => null,
|
|
(bool) $solid => 'solid',
|
|
(bool) $tonal => 'tonal',
|
|
default => 'outline',
|
|
};
|
|
|
|
$attributes = $attributes->merge(array_filter([
|
|
'data-md-badge' => true,
|
|
'data-md-dot' => $dot ? true : null,
|
|
'data-md-variant' => $variant,
|
|
'data-md-color' => $color,
|
|
'data-md-floating' => $floating ? true : null,
|
|
'aria-label' => $label,
|
|
'aria-hidden' => $label === null && ! $status ? 'true' : null,
|
|
], fn ($value): bool => $value !== null));
|
|
@endphp
|
|
|
|
<span {{ $attributes }}>@unless ($dot)@if ($markup){{ $slot }}@else{{ $text }}@endif@endunless</span>
|