Rewrite the card without Tailwind
Plan step 36 (containment group): <x-card>'s class lists move into resources/css/components/card.css, keyed on data-md-card (per variant), data-md-card-figure/-body/-header/-heading/-title/-subtitle/-menu/ -content/-actions. A row (data-md-list-row) renders the shared md-state-layer and md-focus-ring classes for its tint and ring (foundation/interaction.css) instead of copying their rules; card.css adds only the per-state elevation the shared class has no opinion on (ElevatedCardTokens.kt/FilledCardTokens.kt/OutlinedCardTokens.kt), the press/hover exclusion for a card's own nested buttons, and the ring for a non-actionable row whose focus lands on its opener rather than the card. data-md-dragged is read automatically by the shared class's own 16% tint; card.css only adds its elevation levels 3/4. Hooks renamed data-list-row -> data-md-list-row, data-list-open -> data-md-list-open, data-list-actionable -> data-md-list-actionable, data-dragged -> data-md-dragged, updated in the same commit: resources/js/search.js and list-rows.js's consumers, table.css's comment, the showcase (index, data and containment sections), tests/Browser/ContainmentTest.php, and the development skill's card and table examples. Imported from the Containment block of components.css. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
76abdd6242
commit
f04e31b3aa
@@ -1,20 +1,17 @@
|
||||
<?php
|
||||
|
||||
it('draws M3\'s three cards, each naming its variant for its per-state elevation', function (string $variant, string $classes) {
|
||||
expect((string) $this->blade("<x-card variant=\"{$variant}\">Body</x-card>"))
|
||||
->toContain($classes)
|
||||
->toContain('rounded-corner-md')
|
||||
->toContain("data-card=\"{$variant}\"");
|
||||
})->with([
|
||||
'filled' => ['filled', 'bg-surface-container-highest'],
|
||||
'elevated' => ['elevated', 'bg-surface-container-low shadow-elevation-1'],
|
||||
'outlined' => ['outlined', 'border border-outline-variant bg-surface'],
|
||||
]);
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
it('draws M3\'s three cards, each naming its variant for its own colour and per-state elevation', function (string $variant) {
|
||||
$html = (string) $this->blade("<x-card variant=\"{$variant}\">Body</x-card>");
|
||||
$css = ComponentStylesheet::read('card');
|
||||
|
||||
expect($html)->toContain("data-md-card=\"{$variant}\"")
|
||||
->and($css->has("[data-md-card='{$variant}']"))->toBeTrue();
|
||||
})->with(['filled', 'elevated', 'outlined']);
|
||||
|
||||
it('is filled unless it knows the variant', function () {
|
||||
expect((string) $this->blade('<x-card variant="glass">Body</x-card>'))
|
||||
->toContain('bg-surface-container-highest')
|
||||
->toContain('data-card="filled"');
|
||||
expect((string) $this->blade('<x-card variant="glass">Body</x-card>'))->toContain('data-md-card="filled"');
|
||||
});
|
||||
|
||||
it('lays out a header, menu, media, body and actions', function () {
|
||||
@@ -28,50 +25,67 @@ it('lays out a header, menu, media, body and actions', function () {
|
||||
BLADE);
|
||||
|
||||
expect($html)
|
||||
->toContain('data-md-card-figure')
|
||||
->toContain('<img src="/x.png" alt="">')
|
||||
->toContain('<h3 class="type-title-md">holiday-photos.zip</h3>')
|
||||
->toContain('<h3 data-md-card-title>holiday-photos.zip</h3>')
|
||||
->toContain('data-md-card-menu')
|
||||
->toContain('248 MB')
|
||||
->toContain('<button>⋮</button>')
|
||||
->toContain('role="separator"')
|
||||
->toContain('data-md-card-content')
|
||||
->toContain('Expires in 3 days.')
|
||||
->toContain('data-md-card-actions')
|
||||
->toContain('<button>Copy link</button>')
|
||||
->and(strpos($html, 'Expires'))->toBeLessThan(strpos($html, 'Copy link'));
|
||||
});
|
||||
|
||||
it('passes row attributes through', function () {
|
||||
expect((string) $this->blade('<x-card data-list-row><a href="/s/1" data-list-open>Open</a></x-card>'))
|
||||
->toContain('data-list-row')
|
||||
->toContain('data-list-open');
|
||||
it('passes row attributes through and renders the shared interaction classes for a row', function () {
|
||||
$html = (string) $this->blade('<x-card data-md-list-row><a href="/s/1" data-md-list-open>Open</a></x-card>');
|
||||
|
||||
expect($html)
|
||||
->toContain('data-md-list-row')
|
||||
->toContain('data-md-list-open')
|
||||
->toContain('class="md-state-layer md-focus-ring"')
|
||||
->and((string) $this->blade('<x-card title="Share">Body</x-card>'))
|
||||
->not->toContain('md-state-layer')
|
||||
->not->toContain('md-focus-ring');
|
||||
});
|
||||
|
||||
it('makes a directly actionable card the one tab stop, named by its title', function () {
|
||||
$html = (string) $this->blade('<x-card actionable title="holiday-photos.zip"><a href="/s/1" data-list-open tabindex="-1">Open</a></x-card>');
|
||||
$html = (string) $this->blade('<x-card actionable title="holiday-photos.zip"><a href="/s/1" data-md-list-open tabindex="-1">Open</a></x-card>');
|
||||
|
||||
expect($html)
|
||||
->toContain('data-list-row')
|
||||
->toContain('data-list-actionable')
|
||||
->toContain('data-md-list-row')
|
||||
->toContain('data-md-list-actionable')
|
||||
->toContain('tabindex="0"')
|
||||
->toContain('role="button"')
|
||||
->toContain('aria-label="holiday-photos.zip"')
|
||||
->toContain('class="md-state-layer md-focus-ring"')
|
||||
->and((string) $this->blade('<x-card actionable role="link" title="Share">Body</x-card>'))
|
||||
->toContain('role="link"')
|
||||
->and(substr_count((string) $this->blade('<x-card actionable role="link" title="Share">Body</x-card>'), 'role="link"'))->toBe(1)
|
||||
->and((string) $this->blade('<x-card title="Share">Body</x-card>'))
|
||||
->not->toContain('data-list-actionable')
|
||||
->not->toContain('data-md-list-actionable')
|
||||
->not->toContain('tabindex');
|
||||
});
|
||||
|
||||
it('draws M3\'s dragged state while the application says the card is being carried', function () {
|
||||
$css = (string) file_get_contents(__DIR__.'/../../../resources/css/components/list.css');
|
||||
it('draws M3\'s per-state elevation and dragged state from the tokens, never a hand-made shadow', function () {
|
||||
$css = ComponentStylesheet::read('card');
|
||||
|
||||
expect((string) $this->blade('<x-card data-dragged variant="elevated">Body</x-card>'))
|
||||
->toContain('data-dragged')
|
||||
->and($css)
|
||||
->toContain('[data-card][data-dragged] {')
|
||||
->toContain('var(--md-sys-elevation-3),')
|
||||
->toContain("[data-card='elevated'][data-dragged] {")
|
||||
->toContain('var(--md-sys-elevation-4),')
|
||||
->toContain('calc(var(--md-sys-state-dragged-state-layer-opacity) * 100%)')
|
||||
->toContain(':not([data-dragged]):hover')
|
||||
->toContain(':not([data-dragged]):active');
|
||||
expect((string) $this->blade('<x-card data-md-dragged variant="elevated">Body</x-card>'))->toContain('data-md-dragged')
|
||||
->and($css->declarations('[data-md-card][data-md-dragged]'))->toBe(['box-shadow' => 'var(--md-sys-elevation-3)'])
|
||||
->and($css->declarations("[data-md-card='elevated'][data-md-dragged]"))->toBe(['box-shadow' => 'var(--md-sys-elevation-4)'])
|
||||
->and($css->declarations("[data-md-card='elevated']"))->toHaveKey('box-shadow', 'var(--md-sys-elevation-1)')
|
||||
->and($css->has('[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover))', ['@media (hover: hover)']))->toBeTrue()
|
||||
->and($css->declarations("[data-md-card='elevated'][data-md-list-row]:not([data-md-dragged]):hover:not(:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover))", ['@media (hover: hover)']))->toBe(['box-shadow' => 'var(--md-sys-elevation-2)'])
|
||||
->and($css->declarations('[data-md-card][data-md-list-row]:not([data-md-dragged]):hover:has(:is(a, button, input, select, textarea, label, summary):not([data-md-list-open]):hover)::before', ['@media (hover: hover)']))->toBe(['opacity' => '0']);
|
||||
});
|
||||
|
||||
it('rings the row from the card itself when actionable, or from a has() when its opener is focused', function () {
|
||||
$css = ComponentStylesheet::read('card');
|
||||
|
||||
expect($css->declarations('[data-md-card][data-md-list-row]:has([data-md-list-open]:focus-visible)'))
|
||||
->toBe(['outline' => '3px solid var(--md-sys-color-secondary)', 'outline-offset' => '2px'])
|
||||
->and($css->declarations('[data-md-card][data-md-list-row] [data-md-list-open]:focus-visible'))
|
||||
->toBe(['outline' => 'none']);
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ use NoNameWeb\LivewireMaterial\Tests\Support\ViewClasses;
|
||||
dataset('containment components', [
|
||||
'divider',
|
||||
'collapse',
|
||||
'card',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user