Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 43 additions & 9 deletions src/components/FeatureList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,49 @@ const FeatureList: React.FC<FeatureListProps> = ({
{Object.entries(feature.frameworks)
.filter(([, info]) => info.version !== 'n/a')
.map(([frameworkName, frameworkInfo]) => (
<a
key={frameworkName}
href={withCurrentDocsPath(frameworkInfo.apiUrl)}
className={styles.frameworkItem}
target="_blank"
rel="noopener noreferrer"
>
{frameworkName} v{frameworkInfo.version}
</a>
// The ELEMENT changes, not just the href. Guarding the
// href alone rendered the same DOM - withCurrentDocsPath
// returns undefined for undefined and React omits the
// attribute - so the chip stayed an <a> with no href:
// not keyboard-focusable, navigating nowhere, still
// showing the link hover. 17 cells in features.json
// carry no apiUrl, of which 16 reach this branch - the
// 17th is Web / 7-Segment Display, filtered out earlier
// by version === "n/a".
// Latent only while every usage is compact mode, which
// skips this column. Same shape as the feature-name cell
// above, which swaps <a> for <h4>.
frameworkInfo.apiUrl ? (
<a
key={frameworkName}
href={withCurrentDocsPath(frameworkInfo.apiUrl)}
className={styles.frameworkItem}
target="_blank"
rel="noopener noreferrer"
>
{frameworkName} v{frameworkInfo.version}
</a>
) : (
<span
key={frameworkName}
className={`${styles.frameworkItem} ${styles.frameworkItemUnavailable}`}
/* Short on purpose: screen readers often read `title` after
the content, so repeating the name and version here would
announce them twice. No `aria-disabled` either: a plain
<span> is not a control, and ARIA 1.2 does not allow the
attribute on generic elements. */
title="No API reference"
>
{frameworkName} v{frameworkInfo.version}
{/* The dashed border and the dimmed token show sighted readers
that this chip has no link. Without this text the chip
announces exactly what a linked one announces - "iOS v1.2.3".
It says "no API reference", not "not available": every chip in
this column is a framework that DOES support the feature, so
"not available" would read as "this feature isn't on iOS". */}
<span className={styles.visuallyHidden}> (no API reference)</span>
</span>
)
))}
</div>
</td>
Expand Down
45 changes: 41 additions & 4 deletions src/components/FeatureList/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,18 @@
}


/* The chips carry padding and a border but no margin, and React renders the
.map() output with no whitespace between siblings, so without a container
layout they sit flush against one another. This rule was `display: none`
from the file's first commit - the column has never rendered, because all 50
FeatureList usages pass displayMode="compact", which takes a different path.
Hiding the content while the component still emits its
<th>Available Frameworks</th> header was incoherent; this is the smallest
layout that makes the column legible if that view is ever used. */
.frameworkList {
display: none;
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}

.frameworkItem {
Expand All @@ -112,14 +122,41 @@
text-decoration: none;
}

/* Reachable by a screen reader, absent from the visual layout. Not
`display: none` or `visibility: hidden`, which remove it from the
accessibility tree as well and would defeat the point. */
.visuallyHidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

/* No API reference for this framework yet. Dimmed through the border and a
colour token rather than `opacity`, which composited the label to 3.19:1 in
light mode - below AA - while looking merely faint. Measured against the
BUILT css rather than assumed token values: 5.29:1 light, 12.26:1 dark.
`border-style: dashed` carries the state without relying on colour. */
.frameworkItemUnavailable {
opacity: 0.5;
cursor: not-allowed;
color: var(--ifm-color-emphasis-700);
border-style: dashed;
cursor: default;
}

/* Hold the dimmed colour on hover. This rule exists to cancel
.frameworkItem:hover, but it was left over from the opacity version and
restored --ifm-color-content - the full-contrast colour - so hovering an
unavailable chip made it look available, with only the dashed border
still carrying the state. The span is not interactive; cursor: default
already says so. */
.frameworkItemUnavailable:hover {
background: var(--ifm-color-emphasis-100);
color: var(--ifm-color-content);
color: var(--ifm-color-emphasis-700);
border-color: var(--ifm-color-emphasis-200);
}

Expand Down
Loading