-
Notifications
You must be signed in to change notification settings - Fork 0
feat: build modular element detail page #64
base: 48-element-variant-card
Are you sure you want to change the base?
Conversation
9f23af7 to
41097f6
Compare
|
Checked against
Fine for now, but please mention it in the PR description:
Generated with claude code |
… the badge - Merge 48-element-variant-card (keeps this branch's badge and variant list). - Prettier on all changed source files (single quotes, no semicolons). - vite.config.ts copies data/images to dist/data/images on build, so the drawings also work after npm run build. - DifficultyBadge: title and a visually hidden label instead of role=img.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work setting up the aria-live region for screen readers!
Two quick fixes before merging:
- Visual Feedback:
p.sr-onlyhides the status text from sight, leaving sighted users with no feedback when clicking the buttons. Render the status message visually (e.g.<p role="status" className="text-xs text-muted-foreground">) so everyone sees why the feature is locked/unbuilt. - Icon Sizing: Add an explicit size class like
size-4to<Plus />and<LockKeyhole />to prevent SVG scaling layout glitches across different browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Two quick accessibility improvements before merging:
- Back Button
aria-label: Addaria-label="Go back to previous page"to the back button so screen readers give a descriptive context when navigating by interactive elements. - Conditional Description Guard: Wrap
<p>with{element.description && ...}so empty description fields don't leave empty<p>elements in the DOM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ElementDetailPage.tsx state machine and data-fetching flow look very solid!
One quick styling polish: Add heading font utility classes (text-3xl font-bold) to the <h1> elements in the not-found and error conditional branches so they don't render as unstyled browser defaults when a 404 or network error occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a tiny polish suggestion when you get a chance:
- Alt Text: We could simplify the
altattribute toIllustration for ${element.code}${element.name ?: ${element.name}: ''}. Screen readers automatically announce "Image", so keeping it focused on the code/name avoids reading out long description text or repeating words like "sequence". - Semantics: Since there isn't a visible
<figcaption>, changing<figure>to a standard<div>(or adding a<figcaption>if we want a caption below the image) will keep the HTML structure super tight.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love how clean and well-structured ElementMetadataCard.tsx is coming along! Using <dl>, <dt>, and <dd> inside a semantic <section> gives it fantastic document structure for accessibility.
Just a couple of small suggestions to consider:
- Badge Label Consistency: Inside
MetadataItem,element.isVault ? 'D-value' : 'Difficulty'works great, but for non-vaults with an unmapped difficulty letter,ElementValueBadgereceiveslabel="Difficulty value". We might want to passlabel="Difficulty"there too so screen reader announcements stay consistent regardless of whether it falls back to a numeric value or rendersDifficultyBadge. - Card Padding Utilities: If you're using Tailwind v4 variable utilities elsewhere in the layout, swapping
style={{ backgroundColor: 'var(--background-color-element)' }}forbg-(--background-color-element)will keep the styling approach uniform across your UI components.
Really nice work on the layout and semantic markup!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just two quick accessibility tweaks to consider:
- Replace inline style with Tailwind v4 syntax: Swap
style={{ backgroundColor: 'var(--background-color-element)' }}forclassName="w-full bg-(--background-color-element)"on<Card>to keep your utility class pattern consistent across components. - Standardize
ElementValueBadgelabel: InsideElementValueBadge, passlabel={variant.isVault ? 'D-value' : 'Difficulty'}(matchingElementMetadataCard) so screen reader announcements stay completely uniform across fallback states.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just two small data-handling improvements to consider:
- Fallback for Missing Image Filename: If
record.imageis undefined or null on a record,${import.meta.env.BASE_URL}data/images/${apparatusId}/${record.image}will produce an invalid image path ending in/undefined. SettingimageUrltorecord.image ? ... : nullensures the component cleanly triggers its "No illustration available" fallback state. - Dynamic Base URL Trailing Slashes: Depending on how
import.meta.env.BASE_URLis configured in Vite, concatenating without normalization can lead to double slashes (e.g.//data/images/...). Replacingimport.meta.env.BASE_URLwith a light path-join or stripping extra slashes will prevent unexpected 404s in production subfolder deployments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just two quick test suite improvements to consider:
- Explicit
imageUrltest for missing images: Add a test case for an element record whereimageis undefined or null to ensuregetElementDetailsetsimageUrltonullinstead of building an invalid path ending in/undefined. - Loosen strict path structure in
imageUrlmatch: Ifimport.meta.env.BASE_URLchanges between environments (e.g., local preview vs. CI/staging subpaths), checkingexpect(detail?.imageUrl).toContain('UB-1.101-a.webp')keeps the assertion clean while preventing fragile regex matches on leading slash patterns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just two quick improvements to consider:
- Floating-point comparison safety in
getDifficultyLetter: Rather than relying on exact equality aftertoFixed(1), using an epsilon check (e.g.Math.abs(difficultyValue - value) < 0.05) prevents precision edge cases if dynamic values like0.10000000000000002are passed in. - Reverse lookup map: Pre-building a
Map<number, DifficultyLetter>once at module scope avoids runningObject.entries()array iterations on every lookup call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Just two quick structural improvements to consider:
- Move CSS imports into
App.tsxor a single styles entry point: Keeping global styles (variables.css,global.css,index.css) organized inApp.tsxor bundled through a central stylesheet keepsmain.tsxcleanly focused on bootstrapping the React root and router. - Set basename on
BrowserRouterfor subpath deployments: If hosting on a subfolder or virtual machine path (like Vite'simport.meta.env.BASE_URL), passing<BrowserRouter basename={import.meta.env.BASE_URL}>prevents routing 404s on page refresh or direct navigation.
Summary
Build a modular, route-backed element detail page using the existing variant and difficulty components.
What's in it
/elements/:elementIdwith loading, not-found, and error/retry states.ElementVariantsandDifficultyBadge; use existing CSS variables for page and badge colors.Choices
The GraphQL server is not implemented yet, so the page reads the checked-in catalog JSON behind a separate adapter that can be replaced with an API client later. No new color values are introduced.
src/lib/catalog.tsreads the catalog fromdata/*.jsonin the frontend, so the whole catalog goes into the bundle (JS grows from about 224 kB to 496 kB). This is a stand-in until the GraphQLelement/catalogItemqueries are used./redirects to a fixed element (UB-1.101-a) while this is the only page.data/imagesare copied todist/data/imagesbyvite.config.tson build, so they also work afternpm run build.Testing
npm run lintpassed.npm run buildpassed.npm test -- --run: 26 passed, 23 skipped.ElementVariantstests passed after the final badge change.Not done (still open)
Dependencies
This draft is stacked on PR #62, which in turn depends on PR #61. Its temporary base is
48-element-variant-card; retarget it todevafter #62 merges.Part of #63
Generated with GitHub Copilot