Draw the navigation rail without Tailwind
Plan step 36 (navigation group, second batch): <x-navigation-rail>'s, <x-navigation-rail-item>'s and <x-navigation-rail-section>'s class lists move into navigation-rail.css, navigation-rail-item.css and navigation-rail-section.css, keyed on data-md-navigation-rail (the mode, data-md-width, data-md-align, data-md-hide-when-collapsed, data-md-divider, data-md-fill, data-md-open), data-md-navigation-rail- item (data-md-active) and data-md-navigation-rail-section. Every Tailwind wrapper class in the view — the menu row's centring padding, the FAB row, the two swapped menu glyphs, the brand's visibility — becomes a hook the stylesheet draws instead; the menu button itself renders the shared md-state-layer/md-focus-ring/md-touch-target classes (N-01's pattern) since it draws its own layer on itself, not a child. `<x-icon>` and `<x-badge>` take size and floating props instead of size/position classes; a small data-md-navigation-icon hook (the shared navigation-item.css) replaces the ad hoc "relative inline-flex" wrapper a floating badge anchors to. `rail-collapsed` (a Tailwind @custom-variant, forbidden in Phase F) is reproduced as plain selectors, branch for branch: the three width- independent conditions (a fixed collapsed mode; a collapsible rail the visitor collapsed and not open; a modal rail not open) merge into one :where() group, provably the same match set as three separate rules since :where(A, B, C) on an element is true exactly when :where(A) or :where(B) or :where(C) is; the four width-gated conditions stay separate media blocks, since CSS cannot merge different `@media` queries. Every rem length becomes px, since these are dp-based M3 tokens, not a text measure (unlike <x-pane>'s rem widths). The FAB overrides for a rail's header — elevation 0 (N-03), morphing into an extended FAB instead of swapping two by display (N-23) — move from unlayered into this file's own material.components, like toolbar.css's FAB override: fab.css's `[data-md-fab]` is one attribute, so a doubled selector here always outranks it without needing to sit outside the layer. navigation.js: the arriving-indicator stylesheet and every code comment follow the new hooks; resources/css/components/navigation.css is deleted (nothing imports it any more) and its line in tailwind.css with it. Behaviour is unchanged except one thing Tailwind's `rail-collapsed:` variant could do that plain CSS in this shape cannot: it is gone for consuming applications too, since the definition lived only in the file this commit removes. The development skill's guidance for it is rewritten to point at navigation-rail.css's own selectors instead of teaching a Tailwind variant that no longer exists. 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
63d421ccb6
commit
25549374ec
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
use NoNameWeb\LivewireMaterial\Tests\Support\ComponentStylesheet;
|
||||
|
||||
it('draws each mode, falling back to collapsible', function (string $mode, string $expected) {
|
||||
expect((string) $this->blade('<x-navigation-rail :mode="$mode"><x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" /></x-navigation-rail>', ['mode' => $mode]))
|
||||
->toContain("data-navigation-rail=\"{$expected}\"")
|
||||
->toContain("data-md-navigation-rail=\"{$expected}\"")
|
||||
->toContain('<nav')
|
||||
->toContain('aria-label="Main"');
|
||||
})->with([
|
||||
@@ -19,26 +21,26 @@ it('gives the collapsible, modal and adaptive rails a menu button and the store'
|
||||
|
||||
expect($collapsible)
|
||||
->toContain('x-data="materialNavigationRail(\'collapsible\', false)"')
|
||||
->toContain('data-navigation-rail-menu')
|
||||
->toContain('data-md-navigation-rail-menu')
|
||||
->toContain('aria-label="Collapse navigation"')
|
||||
->toContain('aria-expanded="true"')
|
||||
// The 40px menu button reaches M3's 48px target through the shared utility (N-01).
|
||||
->toContain('touch-target')
|
||||
->not->toContain('data-navigation-rail-scrim')
|
||||
// The 40px menu button reaches M3's 48px target through the shared classes (N-01).
|
||||
->toContain('class="md-state-layer md-focus-ring md-touch-target"')
|
||||
->not->toContain('data-md-navigation-rail-scrim')
|
||||
->not->toContain('x-trap')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="modal" />'))
|
||||
->toContain('aria-label="Expand navigation"')
|
||||
->toContain('data-navigation-rail-scrim')
|
||||
->toContain('data-md-navigation-rail-scrim')
|
||||
->toContain('x-trap.inert.noscroll="open"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="expanded" />'))
|
||||
->not->toContain('data-navigation-rail-menu')
|
||||
->not->toContain('data-md-navigation-rail-menu')
|
||||
->not->toContain('x-data')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsible" :menu="false" />'))->not->toContain('data-navigation-rail-menu');
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsible" :menu="false" />'))->not->toContain('data-md-navigation-rail-menu');
|
||||
});
|
||||
|
||||
it('keeps the header and footer out of the scrolling destinations', function () {
|
||||
$html = (string) $this->blade(<<<'BLADE'
|
||||
<x-navigation-rail mode="expanded" width="20rem">
|
||||
<x-navigation-rail mode="expanded" width="320px">
|
||||
<x-slot:brand><span>Brand</span></x-slot:brand>
|
||||
<x-slot:header><button>FAB</button></x-slot:header>
|
||||
<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" />
|
||||
@@ -47,117 +49,163 @@ it('keeps the header and footer out of the scrolling destinations', function ()
|
||||
BLADE);
|
||||
|
||||
expect($html)
|
||||
->toContain('--navigation-rail-width: 20rem')
|
||||
->toMatch('/data-navigation-rail-header.*Brand.*FAB.*data-navigation-rail-destinations.*Inbox.*data-navigation-rail-footer.*Account/s');
|
||||
->toContain('--navigation-rail-width: 320px')
|
||||
->toMatch('/data-md-navigation-rail-header.*Brand.*FAB.*data-md-navigation-rail-destinations.*Inbox.*data-md-navigation-rail-footer.*Account/s');
|
||||
});
|
||||
|
||||
it('takes M3\'s optional divider and turns the container fill off', function () {
|
||||
expect((string) $this->blade('<x-navigation-rail mode="expanded" divider :fill="false" />'))
|
||||
->toContain('data-divider')
|
||||
->toContain('data-fill="false"')
|
||||
->toContain('data-md-divider')
|
||||
->toContain('data-md-fill="false"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="expanded" />'))
|
||||
->not->toContain('data-divider')
|
||||
->not->toContain('data-fill');
|
||||
->not->toContain('data-md-divider')
|
||||
->not->toContain('data-md-fill');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page (N-22).
|
||||
->toContain('[data-navigation-rail][data-divider]:not([data-open]) > [data-navigation-rail-panel] {')
|
||||
->toContain("[data-navigation-rail][data-fill='false']:not([data-open]) > [data-navigation-rail-panel] {")
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page (N-22).
|
||||
expect($css->declarations('[data-md-navigation-rail][data-md-divider]:not([data-md-open]) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['border-inline-end' => '1px solid var(--md-sys-color-outline-variant)'])
|
||||
->and($css->declarations('[data-md-navigation-rail][data-md-fill=\'false\']:not([data-md-open]) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['background-color' => 'transparent'])
|
||||
// A collapsible rail is held to its collapsed width where M3 asks for a bar instead (N-24).
|
||||
->toMatch("/\\[data-navigation-rail='collapsible'\\] \\{\\s+width: var\\(--navigation-rail-collapsed-width\\);/");
|
||||
->and($css->declarations('[data-md-navigation-rail=\'collapsible\']', ['@media (width < 600px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)']);
|
||||
});
|
||||
|
||||
it('takes M3\'s narrow collapsed width, icons alone but still named', function () {
|
||||
$narrow = (string) $this->blade('<x-navigation-rail width="narrow"><x-slot:header><button>FAB</button></x-slot:header><x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" /></x-navigation-rail>');
|
||||
|
||||
expect($narrow)
|
||||
->toContain('data-width="narrow"')
|
||||
// A narrow rail expands to the same 16rem; `width` names one or the other, never both.
|
||||
->toContain('--navigation-rail-width: 16rem')
|
||||
// 40px menu button and 56px FAB centred in 80px rather than in 96.
|
||||
->toContain('rail-collapsed:ps-5')
|
||||
->toContain('rail-collapsed:px-3')
|
||||
->toContain('data-md-width="narrow"')
|
||||
// A narrow rail expands to the same 256px; `width` names one or the other, never both.
|
||||
->toContain('--navigation-rail-width: 256px')
|
||||
->toContain('data-md-navigation-rail-menu-row')
|
||||
->toContain('data-md-navigation-rail-fab-row')
|
||||
// The label is still rendered: it is what names the destination.
|
||||
->toContain('<span data-navigation-label>Inbox</span>')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" width="20rem" />'))
|
||||
->not->toContain('data-width')
|
||||
->toContain('--navigation-rail-width: 20rem');
|
||||
->toContain('<span data-md-navigation-label>Inbox</span>')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" width="320px" />'))
|
||||
->not->toContain('data-md-width')
|
||||
->toContain('--navigation-rail-width: 320px');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
// NavigationRailCollapsedTokens.NarrowContainerWidth, 80dp against the default 96dp.
|
||||
->toMatch("/\\[data-navigation-rail\\]\\[data-width='narrow'\\] \\{\\s+--navigation-rail-collapsed-width: 5rem;/")
|
||||
->toMatch('/--navigation-rail-collapsed-width: 6rem;/')
|
||||
// Out of the drawing, not out of the page.
|
||||
->toContain("[data-navigation-rail][data-width='narrow'] [data-navigation-rail-item] [data-navigation-label] {");
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// NavigationRailCollapsedTokens.NarrowContainerWidth, 80dp against the default 96dp.
|
||||
expect($css->declarations('[data-md-navigation-rail][data-md-width=\'narrow\']'))
|
||||
->toBe(['--navigation-rail-collapsed-width' => '80px'])
|
||||
->and($css->declarations('[data-md-navigation-rail]'))
|
||||
->toHaveKey('--navigation-rail-collapsed-width', '96px')
|
||||
// Out of the drawing, not out of the page: clipped only while collapsed.
|
||||
->and($css->declarations("[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-item] [data-md-navigation-label]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->toHaveKey('clip-path', 'inset(50%)');
|
||||
});
|
||||
|
||||
it('centres the destinations on request, leaving the header and footer at their ends', function () {
|
||||
expect((string) $this->blade('<x-navigation-rail mode="collapsed" align="center" />'))
|
||||
->toContain('data-align="center"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" />'))->not->toContain('data-align')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" align="middle" />'))->not->toContain('data-align');
|
||||
->toContain('data-md-align="center"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" />'))->not->toContain('data-md-align')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="collapsed" align="middle" />'))->not->toContain('data-md-align');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
// Only the destinations move, and `safe` gives the top back when there are too many.
|
||||
->toMatch("/\\[data-navigation-rail\\]\\[data-align='center'\\] \\[data-navigation-rail-destinations\\] \\{\\s+justify-content: safe center;/");
|
||||
// Only the destinations move, and `safe` gives the top back when there are too many.
|
||||
expect(ComponentStylesheet::read('navigation-rail')->declarations("[data-md-navigation-rail][data-md-align='center'] [data-md-navigation-rail-destinations]"))
|
||||
->toBe(['justify-content' => 'safe center']);
|
||||
});
|
||||
|
||||
it('hides a collapsible or adaptive rail entirely when told to, and only those', function () {
|
||||
$hiding = (string) $this->blade('<x-navigation-rail hide-when-collapsed />');
|
||||
|
||||
expect($hiding)
|
||||
->toContain('data-hide-when-collapsed')
|
||||
->toContain('data-md-hide-when-collapsed')
|
||||
->toContain("x-data=\"materialNavigationRail('collapsible', true)\"")
|
||||
// It needs the scrim and the focus trap a modal rail has: it comes back over the page.
|
||||
->toContain('data-navigation-rail-scrim')
|
||||
->toContain('data-md-navigation-rail-scrim')
|
||||
->toContain('x-trap.inert.noscroll="open"')
|
||||
->and((string) $this->blade('<x-navigation-rail mode="adaptive" hide-when-collapsed />'))
|
||||
->toContain("x-data=\"materialNavigationRail('adaptive', true)\"")
|
||||
// A modal rail is already over the page; the two fixed modes mean what they say.
|
||||
->and((string) $this->blade('<x-navigation-rail mode="modal" hide-when-collapsed />'))
|
||||
->not->toContain('data-hide-when-collapsed')
|
||||
->not->toContain('data-md-hide-when-collapsed')
|
||||
->toContain("materialNavigationRail('modal', false)")
|
||||
->and((string) $this->blade('<x-navigation-rail mode="expanded" hide-when-collapsed />'))
|
||||
->not->toContain('data-hide-when-collapsed')
|
||||
->and((string) $this->blade('<x-navigation-rail />'))->not->toContain('data-hide-when-collapsed');
|
||||
->not->toContain('data-md-hide-when-collapsed')
|
||||
->and((string) $this->blade('<x-navigation-rail />'))->not->toContain('data-md-hide-when-collapsed');
|
||||
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
->toMatch('/\[data-navigation-rail\]\[data-hide-when-collapsed\] \{\s+@variant rail-collapsed \{\s+width: 0;/')
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
expect($css->declarations("[data-md-navigation-rail][data-md-hide-when-collapsed]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->toBe(['width' => '0'])
|
||||
// The two bands where the window, not the visitor, collapses a rail keep theirs.
|
||||
->toMatch("/@media \\(width < 37\\.5rem\\) \\{\\s+\\[data-navigation-rail='collapsible'\\]\\[data-hide-when-collapsed\\]/")
|
||||
->toMatch("/@media \\(37\\.5rem <= width < 52\\.5rem\\) \\{\\s+\\[data-navigation-rail='adaptive'\\]\\[data-hide-when-collapsed\\]/")
|
||||
// Every collapsed branch stops while the rail is open, or what comes back would be narrow.
|
||||
->and(substr_count(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'), ':not([data-open])'))
|
||||
->toBeGreaterThanOrEqual(12);
|
||||
->and($css->declarations("[data-md-navigation-rail='collapsible'][data-md-hide-when-collapsed]", ['@media (width < 600px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)'])
|
||||
->and($css->declarations("[data-md-navigation-rail='adaptive'][data-md-hide-when-collapsed]", ['@media (600px <= width < 840px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)']);
|
||||
});
|
||||
|
||||
/**
|
||||
* The Tailwind-era `rail-collapsed:` custom variant had seven branches for "this rail is drawn
|
||||
* collapsed"; navigation-rail.css merges the three that carry no media query into one `:where()`
|
||||
* (provably the same match set: an element matches `X:where(A, B, C)` exactly when it matches
|
||||
* `X:where(A)` or `X:where(B)` or `X:where(C)`) and keeps the four media-gated ones separate, since
|
||||
* CSS cannot merge different `@media` queries into one rule. This pins all five physical branches
|
||||
* — and so every one of the seven original conditions — for the rail's own width, the property
|
||||
* every branch sets.
|
||||
*/
|
||||
it('reproduces every branch of the old rail-collapsed variant for the rail\'s own width', function () {
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// Branches 1-3: a fixed `collapsed` mode; a `collapsible` rail the visitor collapsed and not
|
||||
// open; a `modal` rail not open.
|
||||
expect($css->declarations("[data-md-navigation-rail]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)'])
|
||||
// Branch 4: below `medium` (600px), a `collapsible` rail regardless of choice.
|
||||
->and($css->declarations("[data-md-navigation-rail]:where([data-md-navigation-rail='collapsible']:not([data-md-open]), [data-md-navigation-rail='collapsible']:not([data-md-open]) *)", ['@media (width < 600px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)'])
|
||||
// Branch 5: below `expanded` (840px), an `adaptive` rail not open.
|
||||
->and($css->declarations("[data-md-navigation-rail]:where([data-md-navigation-rail='adaptive']:not([data-md-open]), [data-md-navigation-rail='adaptive']:not([data-md-open]) *)", ['@media (width < 840px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)'])
|
||||
// Branch 6: at `expanded` (840-1199), an `adaptive` rail not open, chosen collapsed or no
|
||||
// choice yet (`data-rail-auto`).
|
||||
->and($css->declarations("[data-md-navigation-rail]:where( :is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]), :is([data-rail='collapsed'], [data-rail-auto]) [data-md-navigation-rail='adaptive']:not([data-md-open]) * )", ['@media (840px <= width < 1200px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)'])
|
||||
// Branch 7: from `large` (1200px), an `adaptive` rail not open, only when chosen collapsed —
|
||||
// M3 starts it expanded there by default.
|
||||
->and($css->declarations("[data-md-navigation-rail]:where( [data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='adaptive']:not([data-md-open]) * )", ['@media (width >= 1200px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)']);
|
||||
});
|
||||
|
||||
it('flattens a FAB nested in the rail header and morphs its label', function () {
|
||||
// Both rules hang off `data-md-fab` on <x-fab>'s root, and both are unlayered, because what they
|
||||
// beat — the FAB's shadow, its gap, an extended FAB's minimum width — are utilities.
|
||||
expect(file_get_contents(__DIR__.'/../../../resources/css/components/navigation.css'))
|
||||
// A nested FAB rests at elevation 0, not the 3 a standalone one has (N-03).
|
||||
->toContain('[data-navigation-rail-header] [data-md-fab],')
|
||||
->toContain('[data-navigation-rail-header] [data-md-fab]:hover {')
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// A nested FAB rests at elevation 0, not the 3 a standalone one has (N-03). Now layered, like
|
||||
// toolbar.css's own docked-FAB override: fab.css's `[data-md-fab]` is one attribute, so the
|
||||
// doubled selector here (two) always wins without needing to sit outside the layer.
|
||||
expect($css->declarations('[data-md-navigation-rail-header] [data-md-fab][data-md-fab], [data-md-navigation-rail-header] [data-md-fab][data-md-fab]:hover'))
|
||||
->toBe(['box-shadow' => 'none'])
|
||||
// One FAB whose label springs shut, not two swapped by display (N-23).
|
||||
->toContain('[data-navigation-rail-header] [data-md-fab] > span {')
|
||||
->toMatch('/@variant rail-collapsed \{\s+min-width: 0;\s+aspect-ratio: 1;\s+gap: 0;/');
|
||||
->and($css->declarations('[data-md-navigation-rail-header] [data-md-fab] > span'))
|
||||
->toHaveKey('max-width', '256px');
|
||||
|
||||
expect($css->declarations("[data-md-navigation-rail-header] [data-md-fab] > span:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->toBe(['max-width' => '0', 'opacity' => '0'])
|
||||
->and($css->declarations("[data-md-navigation-rail-header] [data-md-fab][data-md-extended]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->toBe(['min-inline-size' => '0', 'aspect-ratio' => '1', 'gap' => '0']);
|
||||
});
|
||||
|
||||
it('draws a destination in both shapes, with its count on the icon and at the end', function () {
|
||||
$html = (string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" active badge="12" />');
|
||||
|
||||
expect($html)
|
||||
->toContain('<a data-navigation-rail-item')
|
||||
->toContain('<a data-md-navigation-rail-item')
|
||||
->toContain('wire:navigate')
|
||||
->toContain('aria-current="page"')
|
||||
->toContain('data-navigation-indicator')
|
||||
->toContain('<span data-navigation-label>Inbox</span>')
|
||||
->toContain('hidden rail-collapsed:contents')
|
||||
->toContain('rail-collapsed:hidden')
|
||||
->toContain('<span class="sr-only">, 12</span>')
|
||||
->toContain('data-md-navigation-indicator')
|
||||
->toContain('<span data-md-navigation-label>Inbox</span>')
|
||||
->toContain('data-md-navigation-badge-icon')
|
||||
->toContain('data-md-navigation-badge-end')
|
||||
->toContain('<span class="md-visually-hidden">, 12</span>')
|
||||
->and(substr_count($html, '>12</span>'))->toBe(2)
|
||||
->and((string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" link="/inbox" no-wire-navigate />'))->not->toContain('wire:navigate')
|
||||
->and((string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" />'))->toContain('<button data-navigation-rail-item');
|
||||
->and((string) $this->blade('<x-navigation-rail-item label="Inbox" icon="inbox" />'))->toContain('<button data-md-navigation-rail-item');
|
||||
});
|
||||
|
||||
it('groups destinations under a heading that names the group', function () {
|
||||
@@ -167,5 +215,16 @@ it('groups destinations under a heading that names the group', function () {
|
||||
|
||||
expect($html)->toContain('role="group"')
|
||||
->and($labelledBy[1] ?? null)->not->toBeNull()
|
||||
->and($html)->toContain("<p id=\"{$labelledBy[1]}\" data-navigation-rail-heading>Labels</p>");
|
||||
->and($html)->toContain("<p id=\"{$labelledBy[1]}\" data-md-navigation-rail-heading>Labels</p>");
|
||||
});
|
||||
|
||||
it('draws the item\'s own state layer split between the item and its indicator', function () {
|
||||
$item = ComponentStylesheet::read('navigation-rail-item');
|
||||
|
||||
expect($item->declarations('[data-md-navigation-rail-item]'))
|
||||
->toHaveKeys(['--navigation-item-layer', '--navigation-indicator-layer'])
|
||||
->and($item->declarations('[data-md-navigation-rail-item][data-md-navigation-rail-item]::before'))
|
||||
->toBe(['background-color' => 'var(--md-sys-color-on-secondary-container)', 'opacity' => 'var(--navigation-item-layer)'])
|
||||
->and($item->declarations('[data-md-navigation-rail-item][data-md-navigation-rail-item] [data-md-navigation-indicator]::before'))
|
||||
->toBe(['background-color' => 'var(--md-sys-color-on-secondary-container)', 'opacity' => 'var(--navigation-indicator-layer)']);
|
||||
});
|
||||
|
||||
@@ -26,6 +26,9 @@ dataset('navigation components', [
|
||||
'toolbar',
|
||||
'tabs',
|
||||
'navigation-bar-item',
|
||||
'navigation-rail',
|
||||
'navigation-rail-item',
|
||||
'navigation-rail-section',
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user