StateDot.tsx 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. import clsx from 'clsx'
  2. import css from './StateDot.module.css'
  3. /**
  4. * State semantic: green done / amber user-attention / blue running ring /
  5. * red error / grey idle for a tracked subject with nothing in progress.
  6. */
  7. export type StateDotState = 'done' | 'warning' | 'ongoing' | 'error' | 'idle'
  8. /** Outer 3x3 matrix cells (2px pixels on a 10px grid), clockwise from top-left. */
  9. const MATRIX_CELLS: readonly (readonly [number, number])[] = [
  10. [0, 0], [4, 0], [8, 0], [8, 4], [8, 8], [4, 8], [0, 8], [0, 4],
  11. ]
  12. /**
  13. * Render a state dot.
  14. * @param props.state - which of `done`, `warning`, `ongoing`, `error`, or `idle` to show.
  15. * @param props.size - outer diameter in px (default 10, the figma size).
  16. * @param props.className - extra class for layout placement.
  17. * @returns the dot element (aria-hidden; pair with text for accessibility).
  18. */
  19. export function StateDot({ state, size = 10, className }: {
  20. state: StateDotState
  21. size?: number | undefined
  22. className?: string | undefined
  23. }) {
  24. if (state === 'ongoing') {
  25. return (
  26. <svg
  27. className={clsx(css.matrix, className)}
  28. data-state="ongoing"
  29. width={size}
  30. height={size}
  31. viewBox="0 0 10 10"
  32. shapeRendering="crispEdges"
  33. aria-hidden="true"
  34. >
  35. {MATRIX_CELLS.map(([x, y], index) => (
  36. <rect
  37. key={`${x}-${y}`}
  38. className={css.cell}
  39. x={x}
  40. y={y}
  41. width="2"
  42. height="2"
  43. /* Negative delay phases the chase so every cell animates from mount. */
  44. style={{ animationDelay: `${(index - MATRIX_CELLS.length) * 125}ms` }}
  45. />
  46. ))}
  47. </svg>
  48. )
  49. }
  50. return (
  51. <span
  52. className={clsx(css.dot, className)}
  53. data-state={state}
  54. style={{ width: size, height: size }}
  55. aria-hidden="true"
  56. />
  57. )
  58. }