[DnD] Answer Tile Component - #4099
Draft
SonicScrewdriver wants to merge 27 commits into
Draft
Conversation
Contributor
npm SnapshotWant to try this PR's changes before it merges? Comment |
Contributor
|
Size Change: 0 B Total Size: 519 kB ℹ️ View Unchanged
|
AnswerTile is the card a learner moves into a blank. It renders authored markdown content (text, TeX, image, or empty) via the Perseus Renderer, composes DndActionMenu at its leading edge, and exposes parent-set scored states (rest / correct / incorrect / disabled). Notable decisions: - The menu is fed by a data prop rather than a ReactNode slot, so the label and disabled-ness flow down once from the tile instead of being wired twice by every widget. The config type is derived from DndActionMenuProps so the two components can't drift. - The renderer's global .paragraph styles (Lato 18px, 22px vertical margins) are reset inside the tile — without this, tiles render far taller than the design. - Scored states swap the menu for a decorative check/x glyph, so correctness is never conveyed by color alone; announcing it is the widget's job. - Scored borders are 2px vs the 1px rest border; scored states give up the extra width from padding so the tile doesn't shift size on state flips. - The on-hover-or-focus menu visibility (inline blanks) is CSS-only, so the opener stays focusable and reachable for keyboard and screen reader users. The placeholder tiles in the Action Menu and Choice Bank stories are replaced with the real component. Tile-level hover/drag styling is deferred to the dnd-kit wiring ticket.
Rewrite the AnswerTile comments in short, active sentences so they are easier to read.
- Treat whitespace-only content as empty so the tile always has a visible minimum width and a spoken label. - Merge the check/x icon tests into one honestly-titled test: jsdom cannot tell the two glyphs apart because the graphic lives in a generated stylesheet. - Correct the content-width comment: TeX does not wrap. - Use the shared menu testdata generator in the ChoiceBank stories and drop the tile story that duplicated them. - Explain the always-false disabled prop at the menu render site.
leadingBox told readers where the element sits, not what it is. It is now menuOrScoredIcon, which states the one-or-the-other rule directly. stateIcon is now scoredIcon, since only scored states have icons.
The single menu-or-icon variable needed an umbrella name that never read well. Two small named components state what renders and when, and the render conditions in AnswerTile show they cannot appear together.
The menu opener carried its own 6px trailing margin, so the tile needed two different wrappers: a bare one for the menu and a sized one for the scored icon. The margin now lives on the tile's single .startContainer, which holds either element and owns the gap before the content.
The stateClasses record added indirection that no other CSS-module component uses. The class names match the state names, so styles[state] is enough.
The single state prop bundled two independent facts and needed a novel
"rest" value for the default. Scoring makes the two facts exclusive by
position: a placed tile shows a correct or incorrect result, and an
unused bank tile is disabled. The tile now models them the way radio
does: an optional showCorrectness ("correct" | "incorrect") and an
optional disabled boolean, with the default look on the base class.
A one-line tile computed to 42px: 22px line height, 16px padding, and 2px of border. The design sets 48px for one-line tiles, so the tile now has a 48px minimum height. Taller content still grows the tile.
SonicScrewdriver
force-pushed
the
dnd-answer-tile
branch
from
August 20, 2026 23:44
79db0f8 to
216cea5
Compare
The AnswerTileMenuConfig doc described the type derivation without ever naming the data. It now leads with the contents (move targets, callbacks, remaining-use count). Also fixes the menu prop doc, which still referred to the removed state enum.
The keyof Pick guard protected against a silent stale key after a menu prop rename, but the tile also hard-codes label and disabled at the render site, so a rename already fails compilation there. Plain Omit reads better and loses no real safety.
The nested menu config object and its derived type existed only because label and disabled belong to both the tile and the menu. The menu's widget-facing fields are now plain tile props (moveTargets, onMove, clearFromLabel, onClear, remainingUses, menuRef), so FITB passes label once and every field is a plain type traceable from widget to menu. moveTargets and onMove are now required: every live tile can be moved, so there is no separate no-menu mode. The menu hides only on scored and disabled tiles. An empty moveTargets array stays valid — a placed tile in a one-blank exercise only offers Clear (new story covers this). Adds a generateAnswerTileProps testdata generator and exports AnswerTileProps for it and for widget call sites.
The hidden-until-hover menu behavior for inline blanks is under discussion with design: the Figma frames remove the handle's space, which forces a layout shift on reveal, while the built version kept the space and hid the handle visually, which matched neither the frames nor the readability goal. The tile now always shows the menu on unscored, enabled tiles. The FITB ticket can add the agreed behavior back once the design question resolves.
…led precedence - Inline TileActionsMenu and TileScoredIcon: both were a single span with the same startContainer styles around one child, so the indirection added nothing. - Fix the disabled class so showCorrectness wins when both props arrive, matching the documented precedence. - The content wrapper is a div: the renderer emits block paragraph divs, which are not valid inside a span. - Remove the data-testid attributes; tests now query by role and pass every asserted value explicitly instead of relying on generator defaults. Add an onClear passthrough test covering the Clear-only (empty moveTargets) path through the real menu. - Trim the label prop doc: the empty-tile value is the widget's concern.
Keep only tests that catch failures nothing else would: the Renderer wiring (text and image content), the empty-tile spoken label, the menu-suppression logic for scored and disabled tiles, and the optional props (onClear, menuRef) whose forwarding could be dropped without a compile error. The removed tests covered behavior the menu's own suite already pins (onMove) or asserted raw TeX source text. Comments on the optional-prop tests record why each exists, so future cleanups have the rationale at hand.
Matches the svg-image tests. The mocked loader never fetches the URL, and a localhost address makes that clearer than a third-party domain.
The menu and the scored icon shared the same startContainer styles in two sibling spans. One span now holds whichever shows. The explicit aria-hidden is gone: an unlabeled PhosphorIcon hides itself from assistive technology by default.
The tile has three valid states, which need exactly two checks: does the tile get a start slot (not when disabled), and what goes in it (the menu, or the icon when scored). The extra condition existed only to give the invalid scored-and-disabled combination a defined winner. The props doc now forbids that combination instead of the code handling it.
The 64px minimum previously applied only to empty tiles. The Figma frames show it holds for all tiles: the empty tile (64px) and the single-digit tiles (65px) are the narrowest in the design, and the smallest image preset (24px square) yields an 80px tile. A universal minimum on the tile removes the empty-only class, so the empty check has one job left: the screen-reader label. The content area now grows to keep narrow content centered inside a minimum-width tile.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
tbd
Issue: LEMS-4363
Test plan: