Repository navigation
Change Criteria UI - #123
Conversation
…scription feat: create criteria json file
feat: create criteria domain & UI
Change Display Criteria UI
There was a problem hiding this comment.
Pull request overview
This pull request introduces a new interactive UI for displaying UNESCO World Heritage criteria. It adds localized criteria definitions, creates a reusable CriteriaTags component, and updates the heritage detail pages to use the new criteria display instead of plain text.
Key changes:
- New domain model (
criteria.ts) with localization support for UNESCO World Heritage criteria (i-x) - New
CriteriaTagscomponent that displays clickable criteria badges linking to UNESCO documentation - Updated heritage detail components to integrate the new criteria UI with locale-aware rendering
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
client/src/shared/uis/CriteriaTags.tsx |
New component that renders clickable criteria badges with titles and descriptions |
client/src/locals/ja/world-heritage-criteria.json |
Japanese localization for all 10 UNESCO criteria |
client/src/locals/en/world-heritage-criteria.json |
English localization for all 10 UNESCO criteria |
client/src/domain/criteria.ts |
Domain logic for criteria types, locales, and helper functions to retrieve localized criteria |
client/src/app/features/top/components/heritage-detail/heritage-detail.css |
New CSS classes for layout, sidebar, facts card, and map placeholder |
client/src/app/features/top/components/heritage-detail/HeritageSidebar.tsx |
Updated to accept locale prop and display formatted criteria inline |
client/src/app/features/top/components/heritage-detail/HeritageOverviewSection.tsx |
Replaced text criteria with CriteriaTags component, improved accessibility with aria-labelledby |
client/src/app/features/top/components/heritage-detail/HeritageHero.tsx |
Replaced category chip with CriteriaTags component |
client/src/app/features/top/components/heritage-detail/HeritageDetailLayout.tsx |
Added locale prop threading to child components |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div className="heritage-detail__chips"> | ||
| {item.criteria?.length ? ( | ||
| <CriteriaTags criteria={item.criteria} locale={locale} /> | ||
| ) : ( | ||
| <span className="heritage-detail__muted">—</span> | ||
| )} | ||
| </div> |
There was a problem hiding this comment.
The CSS classes 'heritage-detail__chips' and 'heritage-detail__muted' are used but not defined in any CSS file. These classes need to be added to the heritage-detail.css file or the elements should use existing classes. Without these styles, the criteria tags and the fallback dash may not be displayed correctly.
| <section className="heritage-detail__subsection" aria-labelledby="heritage-criteria-heading"> | ||
| <h3 id="heritage-criteria-heading" className="heritage-detail__subsection-title"> | ||
| Criteria | ||
| </h3> | ||
|
|
||
| <div className="heritage-detail__section"> | ||
| <h3 className="heritage-detail__subsection-title">Criteria</h3> | ||
| <p className="heritage-detail__text">{item.criteriaText || "—"}</p> | ||
| </div> | ||
| <div className="heritage-detail__chips"> | ||
| {item.criteria?.length ? ( | ||
| <CriteriaTags criteria={item.criteria} locale={locale} /> | ||
| ) : ( | ||
| <span className="heritage-detail__muted">—</span> | ||
| )} | ||
| </div> | ||
| </section> | ||
|
|
||
| <section className="heritage-detail__subsection" aria-labelledby="heritage-area-heading"> | ||
| <h3 id="heritage-area-heading" className="heritage-detail__subsection-title"> | ||
| Area & Buffer zone | ||
| </h3> | ||
|
|
||
| <div className="heritage-detail__section"> | ||
| <h3 className="heritage-detail__subsection-title">Area & Buffer zone</h3> | ||
| <HeritageMetadataList | ||
| items={[ | ||
| { label: "Area", value: item.areaText }, | ||
| { label: "Buffer zone", value: item.bufferText }, | ||
| { label: "Area", value: item.areaText ?? "—" }, | ||
| { label: "Buffer zone", value: item.bufferText ?? "—" }, | ||
| ]} | ||
| /> | ||
| </div> | ||
| </section> | ||
|
|
||
| {item.isEndangered && ( | ||
| <div className="heritage-detail__section heritage-detail__section--danger"> | ||
| <h3 className="heritage-detail__subsection-title">Danger status</h3> | ||
| <section | ||
| className="heritage-detail__subsection heritage-detail__subsection--danger" | ||
| aria-labelledby="heritage-danger-heading" | ||
| > | ||
| <h3 id="heritage-danger-heading" className="heritage-detail__subsection-title"> | ||
| Danger status | ||
| </h3> | ||
| <p className="heritage-detail__danger-text"> | ||
| This property is inscribed on the List of World Heritage in Danger. | ||
| </p> | ||
| </div> | ||
| </section> |
There was a problem hiding this comment.
The CSS classes 'heritage-detail__subsection' and 'heritage-detail__subsection--danger' are used but not defined in the CSS file. These classes should be added to heritage-detail.css to properly style these sections, or the existing 'heritage-detail__section' and 'heritage-detail__section--danger' classes should be used instead.
| </a> | ||
| )} | ||
| </div> | ||
| <div className="heritage-detail__map-card" aria-label="Map"> |
There was a problem hiding this comment.
The aria-label attribute on a div element has no semantic meaning without an appropriate ARIA role. Consider changing this div to a section element or adding role="region" to make the aria-label effective for screen readers.
| <div className="heritage-detail__map-card" aria-label="Map"> | |
| <div className="heritage-detail__map-card" aria-label="Map" role="region"> |
| import "./heritage-detail.css"; | ||
| import { HeritageMetadataList } from "./HeritageMetadataList"; | ||
| import { CriteriaTags } from "@shared/uis/CriteriaTags.tsx"; | ||
| import type { Locale } from "../../../../../domain/criteria.ts"; |
There was a problem hiding this comment.
TypeScript imports should omit .tsx/.ts extensions in non-test runtime code files. The codebase convention is to use imports without extensions for relative paths. Remove the .tsx and .ts extensions from these import statements.
| import type { Locale } from "../../../../../domain/criteria.ts"; | |
| import type { Locale } from "../../../../../domain/criteria"; |
| import { HeroImage } from "./HeroImage"; | ||
| import "./heritage-detail.css"; | ||
| import { CriteriaTags } from "@shared/uis/CriteriaTags.tsx"; | ||
| import type { Locale } from "../../../../../domain/criteria.ts"; |
There was a problem hiding this comment.
TypeScript imports should omit .tsx/.ts extensions in non-test runtime code files. The codebase convention is to use imports without extensions for relative paths. Remove the .tsx and .ts extensions from these import statements.
| import type { Locale } from "../../../../../domain/criteria.ts"; | |
| import type { Locale } from "../../../../../domain/criteria"; |
| import { HeritageOverviewSection } from "./HeritageOverviewSection.tsx"; | ||
| import { HeritageSidebar } from "./HeritageSidebar.tsx"; | ||
| import { HeritageGallery } from "./HeritageGallery"; | ||
| import type { Locale } from "../../../../../domain/criteria.ts"; |
There was a problem hiding this comment.
TypeScript imports should omit .tsx/.ts extensions in non-test runtime code files. The codebase convention is to use imports without extensions for relative paths. Remove the .tsx and .ts extensions from these import statements.
| import type { Locale } from "../../../../../domain/criteria.ts"; | |
| import type { Locale } from "../../../../../domain/criteria"; |
| const formatCriteriaInline = (criteria: string[] | undefined) => | ||
| criteria?.length ? criteria.map((c) => `(${c})`).join("") : "—"; | ||
|
|
||
| export function HeritageSidebar({ item }: Props) { |
There was a problem hiding this comment.
The locale prop is defined in the Props type but not destructured in the function signature. The function signature should be: export function HeritageSidebar({ item, locale }: Props) to match the Props type. Currently the locale parameter is accepted but never accessible in the function body.
| export function HeritageSidebar({ item }: Props) { | |
| export function HeritageSidebar({ item, locale }: Props) { |
| const safeLocale: Locale = (LOCALES as readonly string[]).includes(locale) | ||
| ? (locale as Locale) | ||
| : "en"; | ||
| return CRITERIA_BY_LOCALE[safeLocale][code]; |
There was a problem hiding this comment.
The getCriteriaSafe function doesn't include the same fallback logic as the getCriteria function. If the specified locale doesn't contain the requested code, this will return undefined instead of falling back to English or a default value. Consider adding the same fallback chain as getCriteria (lines 36-43) to ensure consistent behavior.
| return CRITERIA_BY_LOCALE[safeLocale][code]; | |
| const entry = CRITERIA_BY_LOCALE[safeLocale]?.[code]; | |
| if (entry) return entry; | |
| const fallback = CRITERIA_BY_LOCALE.en?.[code]; | |
| if (fallback) return fallback; | |
| return { title: code, description: "" }; |
No description provided.