feat(web): Library — continuous grid + sticky alphabet rail
test-web / test (push) Successful in 33s
test-web / test (push) Successful in 33s
Operator UI review: artists/albums layout had a full-row letter divider per first-character, so a section like '2' (one artist) left 9 empty cells before '8' (one artist) started a new row. Lots of dead space across letters with few entries. Operator chose option 2: drop the dividers, add a side jump-bar. - AlphabeticalGrid no longer renders per-letter row-spanning dividers. Items flow continuously across the grid; first item of each letter gets id='alpha-<letter>' so the rail can target it via scrollIntoView. - New sticky vertical alphabet rail on the right. position:sticky + top:50% + translateY keeps it vertically centered in the viewport while the grid scrolls underneath. - Each rail entry is a focusable button with aria-label='Jump to <letter>'. Hover/focus tinted with accent. - Only renders when there's more than one distinct letter so small libraries (or filter-narrowed views) don't get an empty rail. Test rewritten — letters are buttons on the rail, not dividers in the grid. Three assertions: - one jump button per distinct first-letter - DOM order is items first then rail (was rail-then-items before) - first item of each letter carries the alpha-<letter> id
This commit is contained in:
@@ -23,7 +23,7 @@ const itemSnippet = createRawSnippet<[Item]>((getIt) => ({
|
||||
type AnyProps = any;
|
||||
|
||||
describe('AlphabeticalGrid', () => {
|
||||
test('inserts a divider for each new key letter', () => {
|
||||
test('emits a jump button for each distinct first-letter', () => {
|
||||
render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items,
|
||||
@@ -31,14 +31,16 @@ describe('AlphabeticalGrid', () => {
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
// Three dividers (A, B, D); C is absent because no items use C.
|
||||
expect(screen.getByText('A')).toBeInTheDocument();
|
||||
expect(screen.getByText('B')).toBeInTheDocument();
|
||||
expect(screen.getByText('D')).toBeInTheDocument();
|
||||
expect(screen.queryByText('C')).not.toBeInTheDocument();
|
||||
// Rail has one button per distinct first-letter (A, B, D);
|
||||
// C is absent because no items use C. Letters render as
|
||||
// <button> elements, not text dividers.
|
||||
expect(screen.getByRole('button', { name: /jump to a/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /jump to b/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /jump to d/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /jump to c/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders items in given order', () => {
|
||||
test('renders items in given order followed by the jump rail', () => {
|
||||
const { container } = render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items,
|
||||
@@ -46,6 +48,23 @@ describe('AlphabeticalGrid', () => {
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
expect(container.textContent).toMatch(/A.*Apple.*Avocado.*B.*Banana.*D.*Date/s);
|
||||
// New layout: continuous grid (items in order) then the
|
||||
// sticky alphabet rail. Items come first in DOM order; rail
|
||||
// is the trailing nav.
|
||||
expect(container.textContent).toMatch(/Apple.*Avocado.*Banana.*Date.*A.*B.*D/s);
|
||||
});
|
||||
|
||||
test('tags the first item of each letter with an alpha-<letter> id', () => {
|
||||
const { container } = render(AlphabeticalGrid, {
|
||||
props: {
|
||||
items,
|
||||
getKey: (it: Item) => it.key,
|
||||
item: itemSnippet
|
||||
} as AnyProps
|
||||
});
|
||||
expect(container.querySelector('#alpha-A')).not.toBeNull();
|
||||
expect(container.querySelector('#alpha-B')).not.toBeNull();
|
||||
expect(container.querySelector('#alpha-D')).not.toBeNull();
|
||||
expect(container.querySelector('#alpha-C')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user