Rewrite list and list-item without Tailwind
Plan step 36 (containment group, last of its four components): moves into resources/css/components/list.css (the container: display, the 2px segmented gap) and list-item.css (everything else — anatomy, states, the segmented item's own background/corner, and the row interaction pattern list.css used to carry). One commit for both: list-item.css keys heavily on list's own data-md-list="segmented" attribute and the two files are designed together. Kept, now on data-md-* hooks: selectable/selection listbox semantics (role="listbox"/"option", aria-selected, a trailing check as the second cue, C-03), a disabled item's link dropped entirely rather than left focusable (C-02), the segmented row's own state layer no longer racing its background through an unlayered escape hatch (C-08 doesn't recur — nothing here is unlayered), leading video/icon/avatar slots, three-line top alignment and 12px vertical padding (C-15), 16px leading/trailing gap (C-14), dividers inset 16px (C-16), the segmented list's surface fill (C-21) and its hover/focus/pressed/selected corner morph (C-22). The segmented icon size (20px) is now the `size` prop `<x-icon>` already takes from the parent's @aware(['segmented']), replacing the CSS override C-26's fix suggested. data-md-lines="1|2|3" replaces the old line-count classes; data-md-list-item-* hooks replace every other class list. Hooks renamed data-list -> data-md-list, data-list-item -> data-md-list-item, data-dividers -> data-md-dividers, data-selected -> data-md-selected, the generic data-list-row/-open row pattern's last consumer (list-item) moved, so list-rows.js drops the transitional dual-hook fallback the card commit added. table.css now @imports list-item.css, completing what its own comment already claimed (a row answers a pointer as a list row does) under the new per-component-import architecture. Imported from the Containment block of components.css; leaves tailwind.css, which now carries only dialog.css for this group. 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
5cb11cd127
commit
9ff5a7a809
@@ -19,6 +19,8 @@ dataset('containment components', [
|
||||
'divider',
|
||||
'collapse',
|
||||
'card',
|
||||
'list',
|
||||
'list-item',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,58 +1,60 @@
|
||||
<?php
|
||||
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
it('draws a plain list, with dividers on request', function () {
|
||||
expect((string) $this->blade('<x-list label="Files" dividers><x-list-item title="a.zip" /></x-list>'))
|
||||
->toContain('role="list"')
|
||||
->toContain('aria-label="Files"')
|
||||
->toContain('data-list="plain"')
|
||||
->toContain('data-dividers')
|
||||
->not->toContain('divide-y')
|
||||
->toContain('data-md-list="plain"')
|
||||
->toContain('data-md-dividers')
|
||||
->toContain('role="listitem"')
|
||||
->and((string) $this->blade('<x-list><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-dividers')
|
||||
->and((string) $this->blade('<x-list segmented dividers><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-dividers');
|
||||
->and((string) $this->blade('<x-list><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-md-dividers')
|
||||
->and((string) $this->blade('<x-list segmented dividers><x-list-item title="a.zip" /></x-list>'))->not->toContain('data-md-dividers');
|
||||
});
|
||||
|
||||
it('draws M3 Expressive\'s segmented list', function () {
|
||||
expect((string) $this->blade('<x-list segmented><x-list-item title="a.zip" /></x-list>'))
|
||||
->toContain('data-list="segmented"')
|
||||
->toContain('gap-0.5');
|
||||
->toContain('data-md-list="segmented"');
|
||||
});
|
||||
|
||||
it('grows an item from one to three lines, top-aligning the tallest', function () {
|
||||
expect((string) $this->blade('<x-list-item title="a.zip" />'))->toContain('items-center')->toContain('min-h-14 py-2')
|
||||
->and((string) $this->blade('<x-list-item title="a.zip" description="248 MB" />'))->toContain('items-center')->toContain('min-h-18 py-2')->toContain('248 MB')
|
||||
expect((string) $this->blade('<x-list-item title="a.zip" />'))->toContain('data-md-lines="1"')
|
||||
->and((string) $this->blade('<x-list-item title="a.zip" description="248 MB" />'))->toContain('data-md-lines="2"')->toContain('248 MB')
|
||||
->and((string) $this->blade('<x-list-item title="a.zip" overline="Protected" description="248 MB" />'))
|
||||
->toContain('items-start')
|
||||
->toContain('min-h-22 py-3')
|
||||
->toContain('Protected')
|
||||
->not->toContain('items-center');
|
||||
->toContain('data-md-lines="3"')
|
||||
->toContain('Protected');
|
||||
});
|
||||
|
||||
it('leaves 16px between the item and what leads or trails it', function () {
|
||||
expect((string) $this->blade('<x-list-item title="a.zip" icon="folder_zip" />'))->toContain('flex gap-4 px-4');
|
||||
$css = ComponentStylesheet::read('list-item');
|
||||
|
||||
expect($css->declarations('[data-md-list-item]'))
|
||||
->toHaveKey('gap', 'var(--md-sys-measurement-space200)')
|
||||
->toHaveKey('padding-inline', 'var(--md-sys-measurement-space200)');
|
||||
});
|
||||
|
||||
it('draws the expressive icon size in a segmented list', function () {
|
||||
expect((string) $this->blade('<x-list segmented><x-list-item title="a" icon="settings" icon-right="chevron_right" /></x-list>'))
|
||||
->toContain('size-5')
|
||||
->not->toContain('size-6')
|
||||
->and((string) $this->blade('<x-list><x-list-item title="a" icon="settings" /></x-list>'))->toContain('size-6');
|
||||
->toContain('style="--md-icon-size: 20px"')
|
||||
->and((string) $this->blade('<x-list><x-list-item title="a" icon="settings" /></x-list>'))
|
||||
->not->toContain('--md-icon-size: 20px');
|
||||
});
|
||||
|
||||
it('leads with an icon, an avatar or an image, and trails with text or an icon', function () {
|
||||
expect((string) $this->blade('<x-list-item title="a" icon="folder_zip" trailing="3 days" icon-right="chevron_right" />'))
|
||||
->toContain('3 days')
|
||||
->and(substr_count((string) $this->blade('<x-list-item title="a" icon="folder_zip" icon-right="chevron_right" />'), '<svg'))->toBe(2)
|
||||
->and((string) $this->blade('<x-list-item title="Anna" avatar="AM" />'))->toContain('bg-primary-container')->toContain('>AM</span>')
|
||||
->and((string) $this->blade('<x-list-item title="Anna" avatar="AM" />'))->toContain('data-md-list-item-avatar')->toContain('>AM</span>')
|
||||
->and((string) $this->blade('<x-list-item title="Anna" avatar="/anna.jpg" />'))->toContain('<img src="/anna.jpg"')
|
||||
->and((string) $this->blade('<x-list-item title="Photo" image="/p.jpg" />'))->toContain('size-14');
|
||||
->and((string) $this->blade('<x-list-item title="Photo" image="/p.jpg" />'))->toContain('data-md-list-item-image');
|
||||
});
|
||||
|
||||
it('makes a linked item a row that opens from anywhere', function () {
|
||||
expect((string) $this->blade('<x-list-item title="Settings" link="/settings" />'))
|
||||
->toContain('data-list-row')
|
||||
->toContain('data-md-list-row')
|
||||
->toContain('href="/settings"')
|
||||
->toContain('data-list-open')
|
||||
->toContain('data-md-list-open')
|
||||
->toContain('wire:navigate');
|
||||
});
|
||||
|
||||
@@ -81,32 +83,33 @@ it('draws a second cue on a selected option, which icon-right replaces', functio
|
||||
it('leaves a disabled item out of the keyboard and announces it disabled', function () {
|
||||
expect((string) $this->blade('<x-list-item title="Settings" link="/settings" icon="settings" disabled />'))
|
||||
->toContain('aria-disabled="true"')
|
||||
->toContain('pointer-events-none text-on-surface/38')
|
||||
->not->toContain('href="/settings"')
|
||||
->not->toContain('data-list-open')
|
||||
->not->toContain('data-list-row');
|
||||
->not->toContain('data-md-list-open')
|
||||
->not->toContain('data-md-list-row');
|
||||
});
|
||||
|
||||
it('marks a selected item and takes controls in its slots', function () {
|
||||
expect((string) $this->blade('<x-list-item title="Anna" :selected="true"><x-slot:leading><input type="checkbox"></x-slot:leading><x-slot:end><button>⋮</button></x-slot:end></x-list-item>'))
|
||||
->toContain('data-selected')
|
||||
->toContain('data-md-selected')
|
||||
->toContain('data-md-list-item-leading')
|
||||
->toContain('<input type="checkbox">')
|
||||
->toContain('data-md-list-item-end')
|
||||
->toContain('<button>⋮</button>');
|
||||
});
|
||||
|
||||
it('leads with M3\'s video, in the size the item\'s height calls for', function () {
|
||||
expect((string) $this->blade('<x-list-item title="Sunrise" video="/poster.jpg" />'))
|
||||
->toContain('h-14 w-25')
|
||||
->toContain('rounded-corner-sm')
|
||||
->toContain('data-md-list-item-video')
|
||||
->toContain('data-md-size="sm"')
|
||||
->toContain('<img src="/poster.jpg" alt="" />')
|
||||
->toContain('min-h-18 py-2')
|
||||
->toContain('data-md-lines="2"')
|
||||
->and((string) $this->blade('<x-list-item title="Sunrise" overline="Draft" description="7:48" video="/poster.jpg" />'))
|
||||
->toContain('h-16 w-28.5')
|
||||
->toContain('min-h-22 py-3')
|
||||
->toContain('data-md-size="lg"')
|
||||
->toContain('data-md-lines="3"')
|
||||
->and((string) $this->blade('<x-list-item title="Sunrise"><x-slot:video><video src="/clip.mp4"></video></x-slot:video></x-list-item>'))
|
||||
->toContain('<video src="/clip.mp4"></video>')
|
||||
->toContain('h-14 w-25')
|
||||
->toContain('data-md-size="sm"')
|
||||
->and((string) $this->blade('<x-list-item title="Sunrise" />'))
|
||||
->not->toContain('h-14 w-25')
|
||||
->toContain('min-h-14 py-2');
|
||||
->not->toContain('data-md-list-item-video')
|
||||
->toContain('data-md-lines="1"');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user