Draw the progress indicator without Tailwind
Plan step 36 (actions). <x-progress> renders data-md-progress with data-md-color and data-md-circular/-wavy/-thick, and data-md-value/-max in place of data-value/data-max; no class list. progress.css draws LinearProgressIndicatorTokens and CircularProgressIndicatorTokens' stroke, sizes and colours, and the linear indicator's unconditional right-to-left mirror. Its sizing is a default only, same as loading.css: a caller's own class, unlayered or in Tailwind's utilities layer, still outranks it, so the width/ size detection regex the view carried is gone with it. resources/js/progress.js's WATCHED array and its reads follow the renamed hooks. ProgressTest (Feature and Browser) is rewritten on the hooks and ComponentStylesheet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
ce8ac4d316
commit
15cd306aba
@@ -111,7 +111,7 @@ it('turns indeterminate when a bound value is null, and back', function () {
|
||||
progressShowcase()->assertScript(onProgress('Bound', <<<'JS'
|
||||
set(null)
|
||||
await pause(100)
|
||||
const indeterminate = !el.hasAttribute('aria-valuenow') && !el.hasAttribute('data-value')
|
||||
const indeterminate = !el.hasAttribute('aria-valuenow') && !el.hasAttribute('data-md-value')
|
||||
const first = active().getAttribute('d')
|
||||
await pause(200)
|
||||
const moving = active().getAttribute('d') !== first
|
||||
|
||||
@@ -27,6 +27,7 @@ dataset('action components', [
|
||||
'fab-menu',
|
||||
'rich-tooltip',
|
||||
'toast',
|
||||
'progress',
|
||||
]);
|
||||
|
||||
it('draws the component from a stylesheet shaped like every package stylesheet', function (string $name) {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<?php
|
||||
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
it('is a named, determinate progressbar with its value', function () {
|
||||
expect((string) $this->blade('<x-progress value="42" />'))
|
||||
->toContain('data-md-progress')
|
||||
->toContain('role="progressbar"')
|
||||
->toContain('aria-label="Progress"')
|
||||
->toContain('aria-valuemin="0"')
|
||||
->toContain('aria-valuemax="100"')
|
||||
->toContain('aria-valuenow="42"')
|
||||
->toContain('data-value="42"')
|
||||
->toContain('data-md-value="42"')
|
||||
->toContain('data-md-color="primary"')
|
||||
->toContain('x-data="materialProgress"');
|
||||
});
|
||||
|
||||
@@ -16,7 +20,7 @@ it('leaves out aria-valuenow while indeterminate', function () {
|
||||
->toContain('role="progressbar"')
|
||||
->toContain('aria-label="Uploading"')
|
||||
->not->toContain('aria-valuenow')
|
||||
->not->toContain('data-value');
|
||||
->not->toContain('data-md-value');
|
||||
});
|
||||
|
||||
it('says nothing where something else already does', function () {
|
||||
@@ -31,7 +35,7 @@ it('clamps the value into its range', function (string $template, string $now, s
|
||||
expect((string) $this->blade($template))
|
||||
->toContain("aria-valuenow=\"{$now}\"")
|
||||
->toContain("aria-valuemax=\"{$max}\"")
|
||||
->toContain("data-value=\"{$now}\"");
|
||||
->toContain("data-md-value=\"{$now}\"");
|
||||
})->with([
|
||||
'below zero' => ['<x-progress :value="-5" />', '0', '100'],
|
||||
'above the maximum' => ['<x-progress :value="250" />', '100', '100'],
|
||||
@@ -52,56 +56,74 @@ it('draws the first frame on the server, at the value', function () {
|
||||
->toContain('<path d="M36.579 27.01A18 18 0 1 1 12.99 3.421" />');
|
||||
});
|
||||
|
||||
it('draws the active indicator in the colour and the track in its container', function (string $color, string $ink, string $track) {
|
||||
it('draws the active indicator in the colour and the track in its container', function (string $color, array $declarations) {
|
||||
$css = ComponentStylesheet::read('progress');
|
||||
|
||||
expect((string) $this->blade('<x-progress value="50" :color="$color" />', ['color' => $color]))
|
||||
->toContain($ink)
|
||||
->toContain($track);
|
||||
->toContain("data-md-color=\"{$color}\"")
|
||||
->and($css->declarations("[data-md-progress][data-md-color='{$color}']"))->toBe($declarations);
|
||||
})->with([
|
||||
'primary' => ['primary', 'text-primary', 'stroke-secondary-container'],
|
||||
'secondary' => ['secondary', 'text-secondary', 'stroke-secondary-container'],
|
||||
'tertiary' => ['tertiary', 'text-tertiary', 'stroke-tertiary-container'],
|
||||
'error' => ['error', 'text-error', 'stroke-error-container'],
|
||||
'unknown, as primary' => ['purple', 'text-primary', 'stroke-secondary-container'],
|
||||
'secondary' => ['secondary', ['color' => 'var(--md-sys-color-secondary)', 'stroke' => 'var(--md-sys-color-secondary-container)']],
|
||||
'tertiary' => ['tertiary', ['color' => 'var(--md-sys-color-tertiary)', 'stroke' => 'var(--md-sys-color-tertiary-container)']],
|
||||
'error' => ['error', ['color' => 'var(--md-sys-color-error)', 'stroke' => 'var(--md-sys-color-error-container)']],
|
||||
]);
|
||||
|
||||
it('sizes each shape as M3 does', function (string $template, string $size, array $flags) {
|
||||
it('falls back to primary for an unknown colour, secondary-container track included', function () {
|
||||
$css = ComponentStylesheet::read('progress');
|
||||
|
||||
expect((string) $this->blade('<x-progress value="50" color="purple" />'))->toContain('data-md-color="primary"')
|
||||
->and($css->declarations('[data-md-progress]'))->toMatchArray([
|
||||
'color' => 'var(--md-sys-color-primary)',
|
||||
'stroke' => 'var(--md-sys-color-secondary-container)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('sizes each shape as M3 does', function (string $template, array $flags, string $selector, array $declarations) {
|
||||
$html = (string) $this->blade($template);
|
||||
|
||||
expect($html)->toContain($size);
|
||||
|
||||
foreach (['data-circular', 'data-wavy', 'data-thick'] as $flag) {
|
||||
foreach (['data-md-circular', 'data-md-wavy', 'data-md-thick'] as $flag) {
|
||||
in_array($flag, $flags, true)
|
||||
? expect($html)->toContain($flag)
|
||||
: expect($html)->not->toContain($flag);
|
||||
}
|
||||
|
||||
expect(ComponentStylesheet::read('progress')->declarations($selector))->toMatchArray($declarations);
|
||||
})->with([
|
||||
'linear' => ['<x-progress />', 'w-full h-1 ', []],
|
||||
'linear thick' => ['<x-progress thick />', 'h-2 ', ['data-thick']],
|
||||
'linear wavy' => ['<x-progress wavy />', 'h-2.5 ', ['data-wavy']],
|
||||
'linear wavy thick' => ['<x-progress wavy thick />', 'h-3.5 ', ['data-wavy', 'data-thick']],
|
||||
'circular' => ['<x-progress circular />', 'size-10 ', ['data-circular']],
|
||||
'circular thick' => ['<x-progress circular thick />', 'size-11 ', ['data-circular', 'data-thick']],
|
||||
'circular wavy' => ['<x-progress circular wavy />', 'size-12 ', ['data-circular', 'data-wavy']],
|
||||
'circular wavy thick' => ['<x-progress circular wavy thick />', 'size-13 ', ['data-circular', 'data-wavy', 'data-thick']],
|
||||
'linear' => ['<x-progress />', [], '[data-md-progress]:not([data-md-circular])', ['height' => '4px']],
|
||||
'linear thick' => ['<x-progress thick />', ['data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-thick]', ['height' => '8px']],
|
||||
'linear wavy' => ['<x-progress wavy />', ['data-md-wavy'], '[data-md-progress]:not([data-md-circular])[data-md-wavy]', ['height' => '10px']],
|
||||
'linear wavy thick' => ['<x-progress wavy thick />', ['data-md-wavy', 'data-md-thick'], '[data-md-progress]:not([data-md-circular])[data-md-wavy][data-md-thick]', ['height' => '14px']],
|
||||
'circular' => ['<x-progress circular />', ['data-md-circular'], '[data-md-progress][data-md-circular]', ['width' => '40px', 'height' => '40px']],
|
||||
'circular thick' => ['<x-progress circular thick />', ['data-md-circular', 'data-md-thick'], '[data-md-progress][data-md-circular][data-md-thick]', ['width' => '44px', 'height' => '44px']],
|
||||
'circular wavy' => ['<x-progress circular wavy />', ['data-md-circular', 'data-md-wavy'], '[data-md-progress][data-md-circular][data-md-wavy]', ['width' => '48px', 'height' => '48px']],
|
||||
'circular wavy thick' => ['<x-progress circular wavy thick />', ['data-md-circular', 'data-md-wavy', 'data-md-thick'], '[data-md-progress][data-md-circular][data-md-wavy][data-md-thick]', ['width' => '52px', 'height' => '52px']],
|
||||
]);
|
||||
|
||||
it('takes the caller\'s size instead of its own', function () {
|
||||
it('takes the caller\'s size instead of its own, its class landing on the root untouched', function () {
|
||||
expect((string) $this->blade('<x-progress circular wavy class="size-24" />'))
|
||||
->toContain('size-24')
|
||||
->not->toContain('size-12');
|
||||
->toContain('class="size-24"')
|
||||
->toContain('data-md-circular')
|
||||
->toContain('data-md-wavy');
|
||||
|
||||
expect((string) $this->blade('<x-progress class="w-64" />'))
|
||||
->toContain('w-64')
|
||||
->not->toContain('w-full');
|
||||
->toContain('class="w-64"')
|
||||
->not->toContain('data-md-circular');
|
||||
});
|
||||
|
||||
it('mirrors the linear indicator in a right-to-left document, unconditionally', function () {
|
||||
$css = ComponentStylesheet::read('progress');
|
||||
|
||||
expect($css->declarations('[data-md-progress]:not([data-md-circular]):dir(rtl)'))->toBe(['rotate' => '180deg'])
|
||||
->and($css->has('[data-md-progress][data-md-circular]:dir(rtl)'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('follows a value bound in Alpine', function () {
|
||||
$html = (string) $this->blade('<x-progress bind="upload.progress" max="1" />');
|
||||
|
||||
expect($html)
|
||||
->toContain('x-bind:data-value="upload.progress"')
|
||||
->toContain('x-bind:data-md-value="upload.progress"')
|
||||
->toContain('x-bind:aria-valuenow="((v) => v === null')
|
||||
->toContain('Math.min(Math.max(Number(v), 0), 1))(upload.progress)')
|
||||
->toContain('data-max="1"')
|
||||
->not->toContain(' data-value=');
|
||||
->toContain('data-md-max="1"')
|
||||
->not->toContain(' data-md-value=');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user