diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md index b5d0da3..1f831c3 100644 --- a/docs/AI_USAGE.md +++ b/docs/AI_USAGE.md @@ -162,6 +162,18 @@ does not, and checked the browser support and the cascade layers in the Tailwind documentation. Claude checked lint, build and tests. +## Element variant card (`src/components/ui/`, `src/lib/difficulty.ts`) + +- **Tool:** GitHub Copilot +- **What the team did:** Specified a reusable element card and difficulty + badge, including the letter-to-value display, the existing navy palette, + accessible labeling, and a sample catalog image. +- **What GitHub Copilot did:** Added the card primitives, a difficulty badge, + the shared A–J value mapping, and a sample element-variant card. The title + uses an explicit native heading level and the description uses a paragraph. +- **Review:** The team refined the layout, colors, difficulty display, and + accessibility semantics during review. + ## Missing fields in the GraphQL schema (`backend/src/schema.graphql`, `docs/API.md`) - **Tool:** Claude Code, and Claude for the research. diff --git a/src/components/ui/DifficultyBadge.tsx b/src/components/ui/DifficultyBadge.tsx new file mode 100644 index 0000000..2daf98e --- /dev/null +++ b/src/components/ui/DifficultyBadge.tsx @@ -0,0 +1,37 @@ +import { cn } from 'cn' +import { getDifficultyValue } from '@/lib/difficulty' + +export default function DifficultyBadge({ + difficulty, +}: { + difficulty: string | number +}) { + const letter = + typeof difficulty === 'string' ? difficulty.toUpperCase() : undefined + const value = letter ? getDifficultyValue(letter) : undefined + const displayDifficulty = value !== undefined ? letter : difficulty + + return ( + + {/* The hidden labels sit inside the same span as their value, so a + screen reader reads "Difficulty B, Value 0.2" once. No title: it + would be read a second time, and a tooltip only works with a mouse. */} + + Difficulty + {displayDifficulty} + {value !== undefined && ,} + + {value !== undefined && ( + + Value + {value.toFixed(1)} + + )} + + ) +} diff --git a/src/components/ui/ElementVariants.tsx b/src/components/ui/ElementVariants.tsx new file mode 100644 index 0000000..50453e8 --- /dev/null +++ b/src/components/ui/ElementVariants.tsx @@ -0,0 +1,28 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import DifficultyBadge from '@/components/ui/DifficultyBadge' +import elementImage from '../../../data/images/BB/BB-1.101-a.webp' + +const ElementVariant = () => { + return ( + + + + Code - Name (if any) - Element variant id (a,b,c) + + + +
+

Element description goes here.

+ +
+ Balance beam element diagram +
+
+ ) +} + +export default ElementVariant diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..2674a95 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,106 @@ +import * as React from 'react' +import { cn } from 'cn' + +function Card({ + className, + size = 'default', + ...props +}: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl', + className, + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +type CardTitleProps = React.ComponentProps<'h2'> & { + as: 'h2' | 'h3' | 'h4' +} + +function CardTitle({ as: Heading, className, ...props }: CardTitleProps) { + return ( + + ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<'p'>) { + return ( +

+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<'div'>) { + return ( +

+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/lib/difficulty.ts b/src/lib/difficulty.ts new file mode 100644 index 0000000..153fd0d --- /dev/null +++ b/src/lib/difficulty.ts @@ -0,0 +1,24 @@ +export const DIFFICULTY_VALUE_BY_LETTER = { + A: 0.1, + B: 0.2, + C: 0.3, + D: 0.4, + E: 0.5, + F: 0.6, + G: 0.7, + H: 0.8, + I: 0.9, + J: 1.0, +} as const + +export type DifficultyLetter = keyof typeof DIFFICULTY_VALUE_BY_LETTER + +export function getDifficultyValue(letter: string): number | undefined { + const normalizedLetter = letter.toUpperCase() + + if (!Object.hasOwn(DIFFICULTY_VALUE_BY_LETTER, normalizedLetter)) { + return undefined + } + + return DIFFICULTY_VALUE_BY_LETTER[normalizedLetter as DifficultyLetter] +}