WEBDEV-8951 Add a mediatype center icon to ia-status-indicator - #91
Conversation
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #91 +/- ##
==========================================
- Coverage 84.69% 82.48% -2.21%
==========================================
Files 38 38
Lines 1274 1342 +68
Branches 303 325 +22
==========================================
+ Hits 1079 1107 +28
- Misses 115 154 +39
- Partials 80 81 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| @property({ type: String, reflect: true }) mediatype?: MediaTypeIcon; | ||
|
|
||
| /* Renders a bare ring, with no dots in the middle. Ignored when a center icon is present. */ | ||
| @property({ type: Boolean, reflect: true }) hideDots = false; |
There was a problem hiding this comment.
How come you're reflecting these 3 properties?
| * consumers with an idle/pre-validation state (see `ia-otp-form`), which keeps | ||
| * their layout from shifting when the indicator appears. | ||
| */ | ||
| export type LoadingStatus = 'loading' | 'success' | 'error'; |
There was a problem hiding this comment.
Why did we remove the ready status? Since this is a typed value on the property, it should be validating the type that it gets set to
There was a problem hiding this comment.
good call, restored in 25e9c7d. i'd taken it out thinking nothing used it, which was wrong — ia-otp-form renders the indicator unconditionally and relies on ready to reserve the row's width so it doesn't jump when validation starts. back in the union as a real case, and otp-form is back to plain LoadingStatus.
— Claude Code, o/b/o Isa HV
| ], | ||
| // Anything unrecognised (e.g. an idle 'ready' state) reserves space | ||
| // without drawing. See the note on LoadingStatus. | ||
| () => this.placeholderTemplate, |
There was a problem hiding this comment.
When would this get reached? this.mode is of type LoadingStatus so it should only ever be one of those types.
There was a problem hiding this comment.
it wouldn't. that branch only existed to catch ready after i'd removed it from the type, so the type and the runtime disagreed. restoring ready made it an explicit case in choose and the fallback is gone — along with the type widening and comments that were propping it up. net -24 lines.
— Claude Code, o/b/o Isa HV
There was a problem hiding this comment.
Would cropping the whitespace around this icon fix the vertical alignment issue?
| interface HTMLElementTagNameMap { | ||
| 'ia-status-indicator': IAStatusIndicator; | ||
| } | ||
| } |
There was a problem hiding this comment.
Can we organize the icons in a subdirectory so we don't have a bunch of icons at the top-level of the component?
`maskedIcon` arrived with the item navigator in #64, but nothing about it is navigator-specific: it renders a standalone `.svg` as a CSS-masked span so the glyph stays recolorable and its path data stays out of the JS bundle, and it carries the quoted-`url()` fix that keeps bundler-inlined `data:` URIs from silently dropping the whole `mask-image` declaration. The status indicator needs exactly that, so the helper and its test move to `src/util/` rather than a second copy carrying the same fix. Only import paths change for the navigator; the helper itself is untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ia-status-indicator rendered a ring with three animated dots and nothing else — the middle was not addressable. #64 then landed a second loader, ia-itemnav-loading-view, differing only in putting a static book glyph inside a spinning ring. This closes that gap so the details-page theater can show what kind of item is loading, and gives the duplicate loader a path out. The ticket's original framing was wrong: ia-status-indicator has always lived in elements (WEBDEV-8019, #17) and offshoot only consumes it. Nothing needed moving — the gap was capability. New API: - mediatype: one of 10 bundled glyphs in the ring's center - icon slot: escape hatch for glyphs with no mediatype - hideDots: bare ring, replacing loadingStyle="ring" - mode and mediatype now reflect, for CSS attribute hooks Center resolution in loading mode: slotted icon, then mediatype glyph, then nothing when hideDots is set, then the default dots. Two ring geometries are kept deliberately — the original 120x120 when the center is dots or empty, the roomier 100x100 when a glyph is present — so existing call sites see no visual change. Unmapped mediatypes (account, data, movies) fall back to the dots. Glyphs sit flat beside the component, matching ia-combo-box and ia-dropdown-search-bar, and render through the shared maskedIcon. Breaking changes: - loadingStyle is removed. Consumers passing "ring-dots" can drop the attribute; "ring" becomes hideDots. - 'ready' is removed from LoadingStatus. The component still renders its space-reserving placeholder for any unrecognised mode, and that fallback is now documented — ia-otp-form relies on it for its idle state and renders the indicator unconditionally, so removing the render would shift its row. 40 component tests at 100% coverage. Verified in-browser that all 10 glyphs are distinct and unclipped, the glyph stays static while the ring spins, recolouring drives glyph and ring together, and the otp-form row measures 60px in both idle and loading. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Checking a component at a realistic size meant typing values by hand, and the
usage example only ever showed property bindings. These are generic additions
to the shared panels, available to any story.
Styles panel:
- `presets`: one-click values rendered under an input, or beside it with
`presetsInline`. The input and its range readout now share a row so the
presets can stack beneath them.
- `section`: groups consecutive inputs under a heading.
- `stylesApplied` now bubbles and is composed, so a story can react to the
variables its own panel applies. It previously stopped at story-template's
shadow boundary.
Settings panel:
- `section`, as above.
- Radios apply on change, so the demo and the usage example follow a
selection without a trip to Apply. Text fields still need it.
- Props sitting at their default are left out of the usage example — a
consumer only needs to see what they are actually changing — while still
being assigned, otherwise selecting a default would not reset anything.
- `reflects` renders a reflecting property as an attribute rather than a
binding, bare when boolean, since a reflected boolean is present or absent
rather than ="false".
That last one needed story-template to stop treating an empty stringifiedProps
as "nothing to do": an empty string is the meaningful all-defaults case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Uses the new panel affordances to make the demo answer real questions.
- Width presets are the widths consumers actually render at: 1.25rem default,
3rem otp-form, 4rem page, 5rem theater, 6rem account settings. Each button
names its consumer in a tooltip.
- Colour inputs group under a `Color` heading, so the labels are Loading /
Success / Error rather than repeating the prefix. Loading carries a White
preset inline beside its swatch — one swatch and one button read fine on a
line, unlike Width's five.
- The accessible titles group at the bottom under their own heading.
- The dark surface is derived from the loading colour rather than toggled: a
light indicator needs a dark background to be visible at all. Revert sends
an empty styles string, which is treated as "back to defaults" and clears
the surface, rather than leaving it stuck on.
- Mediatype uses a 'none' sentinel because the panel skips empty values, and
the component degrades any unmapped mediatype to the dots anyway.
- `revertable` opts into the panel's own Revert; the story's remaining button
resets only the props, which Revert does not cover.
Width and colour are deliberately left out of the demo element's inline style —
the panel applies those to the demo's container, and setting either inline
would override it and make the panel's own controls look broken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fbdb22c to
bec1e01
Compare
Removing `ready` from `LoadingStatus` while `ia-otp-form` still passed it left the type and the runtime disagreeing: the union said three states, the render carried a fallback for a fourth, and otp-form widened its own property back to `LoadingStatus | 'ready'` to compile. The fallback was scaffolding for a removal that nothing wanted. `ready` goes back in the union and becomes an explicit case in `choose`, so the placeholder is a real state rather than a default branch. That also removes an unreachable-by-type arm: with `mode` typed, `choose` needs no fallback. otp-form goes back to plain `LoadingStatus`. Net -24 lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
WEBDEV-8951
Preview: https://internetarchive.github.io/elements/pr/pr-91/#elem-ia-status-indicator
Extend and upgrade the status indicator so that it can carry a mediatype icon in the centre of its ring, not just the three animated dots it draws today.
API
modeLoadingStatusloadingmediatypeMediaTypeIconhideDotsbooleanfalseloadingStyleSlot:
icon— for glyphs with no mediatype, e.g. item-nav's book.Behaviour
The centre is a mediatype icon or the dots, never both. In
loading, first match wins:iconmediatypeglyph — static, monochrome with the ringhideDotsis setTwo ring geometries, both kept. 120x120 for dots, a roomier 100x100 for a glyph. Unifying them would shift every existing call site for no gain.
success/errorignoremediatype— they replace the whole SVG, anderrorhas no ring.readyrenders a placeholder that reserves the indicator's space without drawing, so a consumer's layout doesn't shift once the indicator appears.ia-otp-formrelies on it for its idle state.Unmapped mediatypes degrade to dots.
account,data,movieshave no glyph here.Breaking changes
Consumer updates are separate tickets.
loadingStyleremoved. Four offshoot sites pass"ring-dots"(the default) — drop the attribute.home-page.ts:215passes"ring"→hideDots.Implementation
Glyphs are CSS masks, not
<img>—<img>can't be recoloured. They ship as separate.svgfiles to keep path data out of the bundle. The maskurl()stays quoted: bundlers inline these asdata:URIs containing apostrophes, and an unquotedurl()silently drops the declaration and paints a solid block.MediaTypeIconis a local union, not@internetarchive/field-parsers— no runtime dep for a type, and it can't promise a glyph we don't ship.maskedIconmoved fromia-item-navigatortosrc/utilso both components share one copy of the helper and its quoted-url()fix, rather than each carrying its own. Only import paths changed for the navigator. Retiringia-itemnav-loading-viewin favour of this component is WEBDEV-8980.Demo
Checking a glyph at a realistic size meant typing values by hand, so the story gained real controls. The additions are generic and available to any story:
StyleInputSettings.presets— one-click values, rendered under the input, or beside it withpresetsInline. Width carries the widths consumers actually use; Loading carries White.StyleInputSettings.section/PropInputSettings.section— groups consecutive inputs under a heading, giving the Styles panel aColorgroup and the Settings panel anAccessible titlesgroup instead of repeating the prefix in every label.Resetin the Styles panel, which had no way back to defaults.PropInputSettings.reflectsrenders reflecting props as attributes rather than property bindings — closer to how a consumer writes them.stylesAppliedto bubble and be composed, since it previously stopped at story-template's shadow boundary.Testing
40 component tests, 100% coverage on
ia-status-indicator. 333 pass repo-wide.madge --circularclean, build clean, lint clean. The 10 new SVGs land indist/.QA
Open the preview at
#elem-ia-status-indicator.Component
Hide dotstrue with mediatypenone→ bare ringModesuccess, then error, with a mediatype set → mode glyph wins, no centre glypharia-hidden, exactly one<title>Styles panel
Widthpresets sit under the input; one click applies, no ApplyColorheading groups Loading / Success / ErrorWhitepreset sits beside the swatch, not under itWhite→ glyph and ring both white, and the demo gains a black background so they stay visibleurl()quoting has regressed — check computedmask-image, not just the visualReset→ all four inputs back to defaults, background offSettings panel
Accessible titlesheading groups Loading / Success / Error at the bottom; text fields, still need Apply<ia-status-indicator></ia-status-indicator>Reset props→ mode, mediatype, hideDots back to defaults; example back to the bare tagElsewhere
#elem-ia-otp-form— flip Validation Status idle → loading, the input row must not jump100%keyframe exists because Safari needs it)🤖 Generated with Claude Code