Skip to the content

Props

NameTypeDefaultDescription
counts*DataStateKeyCounts—How many vehicles are in each data state, such as { live: 412, ageing: 38, estimated: 61, timetableOnly: 9, noSignal: 3 }. Required. A state left out, null or not a finite number is unknown, and shows "—" and says "Unknown".
liveThreshold*number—The app's live threshold in milliseconds, the age under which a report is live, such as 150000, filled into the live and ageing sentences as "2.5 min". Required: each app sets its own from its feed (Thresholds). Not finite or below 0, each sentence that holds it shows "—" and says "Unknown" in its place.
ageingThreshold*number—The app's ageing threshold in milliseconds, the age past which a vehicle has no signal, such as 300000, filled into the ageing and no-signal sentences as "5 min". Required, as liveThreshold is. Not finite or below 0, each sentence that holds it shows "—" and says "Unknown" in its place.
liveLabelstringThe locale'sOverrides "Live", the word beside the live dot; given to that item's VehicleDotLabel
ageingLabelstringThe locale'sOverrides "Ageing", the word beside the ageing dot; given to that item's VehicleDotLabel
estimatedLabelstringThe locale'sOverrides "Estimated", the word beside the estimated dot; given to that item's VehicleDotLabel
timetableOnlyLabelstringThe locale'sOverrides "Timetable only", the word beside the timetable-only dot; given to that item's VehicleDotLabel
noSignalLabelstringThe locale'sOverrides "No signal", the word beside the no-signal dot; given to that item's VehicleDotLabel
liveDescriptionLabelstringThe locale'sOverrides "Reported by the vehicle in the last {liveThreshold}", the live sentence; {liveThreshold} is the live threshold
ageingDescriptionLabelstringThe locale'sOverrides "Last reported {liveThreshold} to {ageingThreshold} ago", the ageing sentence; {liveThreshold} and {ageingThreshold} are the two thresholds
estimatedDescriptionLabelstringThe locale'sOverrides "Placed from the timetable and the latest delay", the estimated sentence
timetableOnlyDescriptionLabelstringThe locale'sOverrides "No real-time data, placed from the timetable alone", the timetable-only sentence
noSignalDescriptionLabelstringThe locale'sOverrides "Moved on by the timetable, with no report for over {ageingThreshold}", the no-signal sentence; {ageingThreshold} is the ageing threshold
secondsDurationLabelstringThe locale'sOverrides "{seconds} s", a threshold under 2 min; {seconds} is its whole seconds
minutesDurationLabelstringThe locale'sOverrides "{minutes} min", a threshold of 2 min or more; {minutes} is its minutes, to one decimal place
unknownLabelstringThe locale'sOverrides "Unknown", heard in place of "—" for a count no data gives, and in place of a sentence whose threshold no data gives
classNamestring—Joined after pn-DataStateKey on the root
styleCSSProperties—Reaches the root
refRef<HTMLUListElement>—Reaches the root element
slotProps{ item?: DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement> | undefined; label?: Omit<VehicleDotLabelProps, "ageingLabel" | "estimatedLabel" | "line" | "liveLabel" | "noSignalLabel" | "render" | "timetableOnlyLabel" | "variant"> | undefined; count?: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined; description?: Omit<TextProps, "children" | "color" | "emphasis" | "render" | "variant"> | undefined; }—Props for each part: item, label, count, description.
counts (required)DataStateKeyCounts, default —
How many vehicles are in each data state, such as { live: 412, ageing: 38, estimated: 61, timetableOnly: 9, noSignal: 3 }. Required. A state left out, null or not a finite number is unknown, and shows "—" and says "Unknown".
liveThreshold (required)number, default —
The app's live threshold in milliseconds, the age under which a report is live, such as 150000, filled into the live and ageing sentences as "2.5 min". Required: each app sets its own from its feed (Thresholds). Not finite or below 0, each sentence that holds it shows "—" and says "Unknown" in its place.
ageingThreshold (required)number, default —
The app's ageing threshold in milliseconds, the age past which a vehicle has no signal, such as 300000, filled into the ageing and no-signal sentences as "5 min". Required, as liveThreshold is. Not finite or below 0, each sentence that holds it shows "—" and says "Unknown" in its place.
liveLabelstring, default The locale's
Overrides "Live", the word beside the live dot; given to that item's VehicleDotLabel
ageingLabelstring, default The locale's
Overrides "Ageing", the word beside the ageing dot; given to that item's VehicleDotLabel
estimatedLabelstring, default The locale's
Overrides "Estimated", the word beside the estimated dot; given to that item's VehicleDotLabel
timetableOnlyLabelstring, default The locale's
Overrides "Timetable only", the word beside the timetable-only dot; given to that item's VehicleDotLabel
noSignalLabelstring, default The locale's
Overrides "No signal", the word beside the no-signal dot; given to that item's VehicleDotLabel
liveDescriptionLabelstring, default The locale's
Overrides "Reported by the vehicle in the last {liveThreshold}", the live sentence; {liveThreshold} is the live threshold
ageingDescriptionLabelstring, default The locale's
Overrides "Last reported {liveThreshold} to {ageingThreshold} ago", the ageing sentence; {liveThreshold} and {ageingThreshold} are the two thresholds
estimatedDescriptionLabelstring, default The locale's
Overrides "Placed from the timetable and the latest delay", the estimated sentence
timetableOnlyDescriptionLabelstring, default The locale's
Overrides "No real-time data, placed from the timetable alone", the timetable-only sentence
noSignalDescriptionLabelstring, default The locale's
Overrides "Moved on by the timetable, with no report for over {ageingThreshold}", the no-signal sentence; {ageingThreshold} is the ageing threshold
secondsDurationLabelstring, default The locale's
Overrides "{seconds} s", a threshold under 2 min; {seconds} is its whole seconds
minutesDurationLabelstring, default The locale's
Overrides "{minutes} min", a threshold of 2 min or more; {minutes} is its minutes, to one decimal place
unknownLabelstring, default The locale's
Overrides "Unknown", heard in place of "—" for a count no data gives, and in place of a sentence whose threshold no data gives
classNamestring, default —
Joined after pn-DataStateKey on the root
styleCSSProperties, default —
Reaches the root
refRef<HTMLUListElement>, default —
Reaches the root element
slotProps{ item?: DetailedHTMLProps<LiHTMLAttributes<HTMLLIElement>, HTMLLIElement> | undefined; label?: Omit<VehicleDotLabelProps, "ageingLabel" | "estimatedLabel" | "line" | "liveLabel" | "noSignalLabel" | "render" | "timetableOnlyLabel" | "variant"> | undefined; count?: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> | undefined; description?: Omit<TextProps, "children" | "color" | "emphasis" | "render" | "variant"> | undefined; }, default —
Props for each part: item, label, count, description.

Use it

import { DataStateKey } from '@mylder/perron/patterns'

Ask your agent

search_docs({ query: "DataStateKey" })

Keyboard and ARIA

DataStateKey has no keys of its own and nothing in it is focusable: it is read, not operated.

KeyDoesFrom
TabPasses the key by to the next focusable element: it has no tab stop (CR-12)Perron
ElementRoleAccessible name fromStates announced
rootlist, from the native <ul>None of its own; the caller's aria-label or aria-labelledby when it gives one (CR-13)Its number of items, from the <ul>
itemlistitem, from the native <li>Its text, in order: the state's word, its count or "Unknown", then its sentenceNone
labelNone: VehicleDotLabel's generic <span>Not named; its word is read in placeNone
The dot inside labelhidden: VehicleDot's img, with aria-hidden="true"None: the word beside it names the state (VehicleDotLabel's CR-2)None
countNone: a generic <span>Not named; its digits are read in placeNone
descriptionNone: Text's generic <span>Not named; its sentence is read in placeNone
The "—" in count or description, unknownhidden: aria-hidden="true"Not readNone
The words in its place, when unknownText, in VisuallyHiddenThe owned string "Unknown", or unknownLabelNone

An item reads "Live 412 Reported by the vehicle in the last 2.5 min", and one with no count "No signal Unknown Moved on by the timetable, with no report for over 5 min".

The root is a list with no name of its own and no live region, as ReportStrip's is: a caller names it with the heading beside it, such as "How sure are we?", through aria-labelledby; the counts change every few seconds, which would flood a screen reader, and Accessibility asks that only changes of state be announced, in the screen's own polite live region.

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: each dot's state is told to the ear by the word beside it, through VehicleDotLabel; each count is read after its word, and an unknown one as "Unknown" in place of the hidden "—" (CR-5); each sentence is visible text, read in place, and one whose threshold is unknown is "—" for the eye and "Unknown" for the ear (CR-7). No colour carries meaning: the dots are one ink, the text's (CR-3), and no signal's grey comes with its cut and its word.
  • Every landmark and named region has an accessible name: none here; the key is a list, not a landmark or region, and a caller names it with aria-label or aria-labelledby (CR-13).
  • Every default label can be overridden: liveLabel, ageingLabel, estimatedLabel, timetableOnlyLabel, noSignalLabel, liveDescriptionLabel, ageingDescriptionLabel, estimatedDescriptionLabel, timetableOnlyDescriptionLabel, noSignalDescriptionLabel, secondsDurationLabel, minutesDurationLabel and unknownLabel.
  • Every shape the type accepts renders, in every variant: counts with all five states, with some left out, null, NaN and Infinity (unknown), with 0 (a count) and with a count past a thousand (grouped); thresholds under 2 min, at 2 min, past it with a half minute and whole, and NaN or below 0 (unknown); every *Label string replaces only its own string, a sentence's override keeps its placeholders filled, and an undefined one keeps the locale's. DataStateKey takes no ReactNode.
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; nothing animates or collapses. The dots and an unknown figure's "—" are aria-hidden because words beside them say them, not for animation.
  • Native semantics first: a <ul> of <li>s, each with a VehicleDotLabel, a <span> of digits and a Text <span> of words; no ARIA of the pattern's own beyond the "—"'s aria-hidden, which UnknownValue sets.
  • Focus moves in reading order and returns on close: nothing takes focus or opens; Tab passes the key by.
  • Every target is at least size.target-min square: no target; nothing in the key is pressed.
  • Loops stop when the system asks for less motion: none here; nothing moves, and a count changes its text without a transition.

Owned strings

Every word the key shows or announces is its own; the caller gives only numbers. The five state words and "Unknown" are keys packages/perron/src/locale/en.ts already has. The five sentences and the two durations are new: each sentence is worded from what Data states says the state means, so every screen that explains the states says the same thing in the same four languages, and the thresholds are placeholders the app's figures fill (CR-6, CR-7).

The English sentences are "Reported by the vehicle in the last {liveThreshold}", "Last reported {liveThreshold} to {ageingThreshold} ago", "Placed from the timetable and the latest delay", "No real-time data, placed from the timetable alone" and "Moved on by the timetable, with no report for over {ageingThreshold}", with no full stop: each says one state's "Means" from Data states in a plain sentence about the vehicle, as content design asks, live in the ticket's own words; "GPS" is left out, since a vehicle's report is its position whatever the source; "{liveThreshold} to {ageingThreshold}" writes each figure with its own unit, "2.5 min to 5 min", rather than content design's range with one unit at its end, "Every 3 to 4 min", since the two thresholds can take different units, "60 s to 5 min"; no signal's "moved on by the timetable" is the foundation's own and says, as No signal keeps moving asks, that its place is a guess, and it comes first, before the figure, so no punctuation follows a placeholder in any locale: expectOwnedStrings reads a comma straight after a filled figure, "5 min,", as a word no dictionary holds; and with one sentence each and no other in the item, a full stop adds nothing, as the library's other owned strings have none.

KeyEnglishOverrideSeen or heard where
liveLiveliveLabelThe first item's label, seen and read as shown
ageingAgeingageingLabelThe second item's label, seen and read as shown
estimatedEstimatedestimatedLabelThe third item's label, seen and read as shown
timetableOnlyTimetable onlytimetableOnlyLabelThe fourth item's label, seen and read as shown
noSignalNo signalnoSignalLabelThe fifth item's label, seen and read as shown
liveDescriptionReported by the vehicle in the last {liveThreshold}liveDescriptionLabelThe first item's description, seen and read as shown; new
ageingDescriptionLast reported {liveThreshold} to {ageingThreshold} agoageingDescriptionLabelThe second item's description, seen and read as shown; new
estimatedDescriptionPlaced from the timetable and the latest delayestimatedDescriptionLabelThe third item's description, seen and read as shown; new
timetableOnlyDescriptionNo real-time data, placed from the timetable alonetimetableOnlyDescriptionLabelThe fourth item's description, seen and read as shown; new
noSignalDescriptionMoved on by the timetable, with no report for over {ageingThreshold}noSignalDescriptionLabelThe fifth item's description, seen and read as shown; new
secondsDuration{seconds} ssecondsDurationLabelA threshold under 2 min, inside a sentence, such as "60 s"; new
minutesDuration{minutes} minminutesDurationLabelA threshold of 2 min or more, inside a sentence, such as "2.5 min"; new
unknownUnknownunknownLabelA count that is unknown, heard in place of its aria-hidden "—", and a sentence holding an unknown threshold, heard in place of the whole sentence (CR-5, CR-7)

The new strings join en.ts with a JSDoc each that names its placeholders, and the other dictionaries in Danish, Swedish and Norwegian. In secondsDuration and minutesDuration the space between the figure's placeholder and the unit is a no-break space (U+00A0) in every locale:

KeyDanishSwedishNorwegian Bokmål
liveDescriptionMeldt af køretøjet inden for de seneste {liveThreshold}Rapporterad av fordonet under de senaste {liveThreshold}Meldt av kjøretøyet i løpet av de siste {liveThreshold}
ageingDescriptionSidst meldt for {liveThreshold} til {ageingThreshold} sidenSenast rapporterad för {liveThreshold} till {ageingThreshold} sedanSist meldt for {liveThreshold} til {ageingThreshold} siden
estimatedDescriptionPlaceret ud fra køreplanen og den seneste forsinkelsePlacerad utifrån tidtabellen och den senaste förseningenPlassert ut fra rutetabellen og den siste forsinkelsen
timetableOnlyDescriptionIngen realtidsdata, placeret ud fra køreplanen aleneIngen realtidsdata, placerad enbart utifrån tidtabellenIngen sanntidsdata, plassert ut fra rutetabellen alene
noSignalDescriptionFlyttet videre efter køreplanen, uden melding i over {ageingThreshold}Flyttad vidare enligt tidtabellen, utan rapport på över {ageingThreshold}Flyttet videre etter rutetabellen, uten melding på over {ageingThreshold}
secondsDuration{seconds} s{seconds} s{seconds} s
minutesDuration{minutes} min{minutes} min{minutes} min

Do and don't

For a caller using the pattern, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1mustPass the thresholds the app works out its states with, liveThreshold and ageingThreshold, in milliseconds, so the sentences say the figures the dots followPass the tokens' defaults when the app's feed sets its own thresholds: the key would tell the rider 60 s while the dots fade at 150 sreview
DD-2mustWork out each vehicle's state from its report's age with those thresholds, then countCount a vehicle as live because it is on the map: the key would claim more than the data knowsreview
DD-3mustLeave a state's count out, or pass null, when the data doesn't say it, so the key shows "—" and says "Unknown"Pass 0 for a count the data doesn't know: 0 says no vehicle is in that statereview
DD-4mustName the key with the heading beside it, such as "How sure are we?", and aria-labelledbyPass aria-hidden to the key: the eye reads its words, counts and sentences, so the ear gets them tooreview
DD-5shouldUse ReportStrip where the counts sit in one row and need no explaining, and VehicleDotLegend where the dots need their words and nothing is countedWrite your own sentences beside a ReportStrip or a legend: they drift from the key's, and from the four languages the key says them inreview
DD-6mustOverride a sentence with its *DescriptionLabel prop when a screen's copy needs it, keeping its placeholdersWrite the figures into an override, such as "Reported in the last 2.5 min": the sentence would stop following the thresholds the app passesreview
DD-7mustPut the key in a DataStatePanel while its counts loadShow five unknown counts while the data loads: the panel says it is loading, the key says the data doesn't knowreview