@lovett/ui 0.0.11 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/chunk-RBYWGBQ2.js +2752 -0
  2. package/dist/chunk-RBYWGBQ2.js.map +1 -0
  3. package/dist/index.d.ts +5574 -57
  4. package/dist/index.js +21650 -11206
  5. package/dist/index.js.map +1 -1
  6. package/dist/rich-composer-impl-5NO443A6.js +1859 -0
  7. package/dist/rich-composer-impl-5NO443A6.js.map +1 -0
  8. package/dist/styles.css +1570 -0
  9. package/dist/tokens.css +112 -0
  10. package/package.json +8 -1
  11. package/src/__tests__/avatar.test.tsx +272 -0
  12. package/src/__tests__/bar-chart.test.tsx +809 -0
  13. package/src/__tests__/board.test.tsx +420 -0
  14. package/src/__tests__/chart-math.test.ts +922 -0
  15. package/src/__tests__/chart-series.test.ts +339 -0
  16. package/src/__tests__/code-block.test.tsx +134 -0
  17. package/src/__tests__/display-popover.test.tsx +195 -0
  18. package/src/__tests__/display-store.test.tsx +307 -0
  19. package/src/__tests__/donut-chart.test.tsx +397 -0
  20. package/src/__tests__/dropdown-menu.test.tsx +156 -2
  21. package/src/__tests__/filter-menu.test.tsx +175 -0
  22. package/src/__tests__/gauge-ring.test.tsx +233 -0
  23. package/src/__tests__/line-chart.test.tsx +612 -0
  24. package/src/__tests__/ranked-bars.test.tsx +343 -0
  25. package/src/__tests__/remark-underline.test.ts +194 -0
  26. package/src/__tests__/sortable.test.tsx +394 -0
  27. package/src/__tests__/sparkline.test.tsx +368 -0
  28. package/src/__tests__/stat-layer.test.tsx +271 -0
  29. package/src/__tests__/stat-strip.test.tsx +175 -0
  30. package/src/__tests__/status.test.tsx +293 -0
  31. package/src/__tests__/tabs.test.tsx +303 -0
  32. package/src/__tests__/token-shape.test.ts +132 -2
  33. package/src/avatar.tsx +352 -0
  34. package/src/bar-chart.tsx +1214 -0
  35. package/src/board.tsx +658 -0
  36. package/src/chart-frame.tsx +960 -0
  37. package/src/chart-legend.tsx +304 -0
  38. package/src/chart-tooltip.tsx +267 -0
  39. package/src/code-block.tsx +62 -8
  40. package/src/delta-chip.tsx +263 -0
  41. package/src/detail/__tests__/activity-pane.test.tsx +369 -0
  42. package/src/detail/__tests__/detail-chrome.test.tsx +134 -0
  43. package/src/detail/__tests__/detail-surface.test.tsx +529 -0
  44. package/src/detail/__tests__/field-row.test.tsx +357 -0
  45. package/src/detail/activity-pane.tsx +611 -0
  46. package/src/detail/calendar.tsx +355 -0
  47. package/src/detail/detail-divider.tsx +261 -0
  48. package/src/detail/detail-header.tsx +287 -0
  49. package/src/detail/detail-menu.tsx +254 -0
  50. package/src/detail/detail-surface.tsx +1110 -0
  51. package/src/detail/field-list.tsx +196 -0
  52. package/src/detail/field-row.tsx +1131 -0
  53. package/src/detail/index.ts +58 -0
  54. package/src/detail/segmented-choice.tsx +94 -0
  55. package/src/detail/types.ts +129 -0
  56. package/src/display-popover.tsx +487 -0
  57. package/src/display-store.tsx +301 -0
  58. package/src/donut-chart.tsx +988 -0
  59. package/src/dropdown-menu.tsx +290 -19
  60. package/src/filter-core/EXPORTS.md +85 -0
  61. package/src/filter-core/__tests__/columns.test.ts +159 -0
  62. package/src/filter-core/__tests__/faceting.test.ts +193 -0
  63. package/src/filter-core/__tests__/filter-fns.test.ts +519 -0
  64. package/src/filter-core/__tests__/operators.test.ts +235 -0
  65. package/src/filter-core/__tests__/state.test.ts +268 -0
  66. package/src/filter-core/__tests__/url.test.ts +350 -0
  67. package/src/filter-core/columns.ts +134 -0
  68. package/src/filter-core/date-utils.ts +38 -0
  69. package/src/filter-core/examples/task-filter-columns.ts +121 -0
  70. package/src/filter-core/faceting.ts +120 -0
  71. package/src/filter-core/filter-fns.ts +335 -0
  72. package/src/filter-core/index.ts +105 -0
  73. package/src/filter-core/operators.ts +433 -0
  74. package/src/filter-core/state.ts +280 -0
  75. package/src/filter-core/types.ts +247 -0
  76. package/src/filter-core/url.ts +261 -0
  77. package/src/filter-dropdown.tsx +12 -0
  78. package/src/filter-menu.tsx +649 -0
  79. package/src/floating-drawer.tsx +19 -1
  80. package/src/gauge-ring.tsx +435 -0
  81. package/src/hue.ts +52 -0
  82. package/src/index.ts +303 -0
  83. package/src/kbd.tsx +27 -4
  84. package/src/lib/chart.ts +866 -0
  85. package/src/lib/focus.ts +43 -1
  86. package/src/lib/layer-stack.ts +30 -3
  87. package/src/lib/remark-underline.ts +443 -0
  88. package/src/lib/series.ts +169 -0
  89. package/src/line-chart.tsx +1176 -0
  90. package/src/markdown.tsx +26 -7
  91. package/src/modal.tsx +42 -18
  92. package/src/progress-ledger.tsx +304 -0
  93. package/src/ranked-bars.tsx +386 -0
  94. package/src/segmented-pill.tsx +32 -9
  95. package/src/sortable.tsx +520 -1
  96. package/src/sparkline.tsx +416 -0
  97. package/src/stat-card.tsx +376 -0
  98. package/src/stat-strip.tsx +327 -0
  99. package/src/status.tsx +215 -0
  100. package/src/styles.css +1570 -0
  101. package/src/tabs.tsx +206 -25
  102. package/src/task-card.tsx +610 -0
  103. package/src/thread/__tests__/comment-body-hostile.test.tsx +331 -0
  104. package/src/thread/__tests__/comment-tree.test.ts +151 -0
  105. package/src/thread/__tests__/emoji.test.ts +187 -0
  106. package/src/thread/__tests__/fixtures/thread-fixture.ts +252 -0
  107. package/src/thread/__tests__/link-preview-source.test.ts +120 -0
  108. package/src/thread/__tests__/link-preview.test.tsx +600 -0
  109. package/src/thread/__tests__/markdown-format.test.ts +82 -0
  110. package/src/thread/__tests__/markdown-spec.test.ts +469 -0
  111. package/src/thread/__tests__/relative-time.test.ts +71 -0
  112. package/src/thread/__tests__/rich-composer.test.tsx +799 -0
  113. package/src/thread/__tests__/scroll-caret.test.ts +58 -0
  114. package/src/thread/__tests__/suggestion-list.test.tsx +648 -0
  115. package/src/thread/__tests__/thread-scroll-ownership.test.tsx +88 -0
  116. package/src/thread/__tests__/thread.test.tsx +742 -0
  117. package/src/thread/__tests__/use-attachments.test.tsx +679 -0
  118. package/src/thread/actions.tsx +196 -0
  119. package/src/thread/attachments.tsx +1071 -0
  120. package/src/thread/comment-body.tsx +148 -0
  121. package/src/thread/comment-tree.ts +182 -0
  122. package/src/thread/comment.tsx +967 -0
  123. package/src/thread/composer-footer.tsx +125 -0
  124. package/src/thread/composer.tsx +319 -0
  125. package/src/thread/emoji.ts +283 -0
  126. package/src/thread/index.ts +153 -0
  127. package/src/thread/link-preview.tsx +341 -0
  128. package/src/thread/markdown-format.ts +155 -0
  129. package/src/thread/markdown-spec.ts +754 -0
  130. package/src/thread/rail.tsx +372 -0
  131. package/src/thread/reactions.tsx +415 -0
  132. package/src/thread/relative-time.tsx +94 -0
  133. package/src/thread/rich-composer-impl.tsx +1601 -0
  134. package/src/thread/rich-composer.tsx +195 -0
  135. package/src/thread/scroll-caret.ts +37 -0
  136. package/src/thread/suggestion-list.tsx +182 -0
  137. package/src/thread/thread.tsx +718 -0
  138. package/src/thread/types.ts +232 -0
  139. package/src/thread/use-attachments.ts +598 -0
  140. package/src/thread/use-now.ts +73 -0
  141. package/src/thread/use-thread.ts +316 -0
  142. package/src/tokens.css +112 -0
package/src/index.ts CHANGED
@@ -71,6 +71,85 @@ export {
71
71
  // @dnd-kit/core + sortable + utilities (CLAUDE.md §1 amendment in same phase).
72
72
  export { SortableList, SortableItem, DragHandle } from './sortable'
73
73
 
74
+ // Multi-container drag — ADR-146 slice A. <SortableList> is single-container;
75
+ // a kanban needs a card to LEAVE its column, which is a droppable per column
76
+ // plus a DragOverlay. Keeps the KeyboardSensor, so cross-column movement is
77
+ // reachable without a pointer.
78
+ export {
79
+ MultiSortableList,
80
+ SortableDropZone,
81
+ // The default `collisionDetection`, exported so a consumer overriding the
82
+ // prop can COMPOSE with it rather than replace it. Without this, the only way
83
+ // to add one rule is to reimplement pointer-first resolution — which is how a
84
+ // component ends up with two collision strategies that disagree.
85
+ pointerFirstCollision,
86
+ PREVIEW_SETTLE_MS,
87
+ type MultiSortableListProps,
88
+ type MultiSortableMove,
89
+ type SortableContainers,
90
+ } from './sortable'
91
+
92
+ // The board kit — ADR-146 D2/D7/D13, spec'd in
93
+ // `docs/design-briefs/tasks-board-design-language.md` and its
94
+ // interface-standards addendum.
95
+ //
96
+ // ONE board primitive, TWO consumers: the Tasks lens and the Community
97
+ // roadmap. It is generic over its card BODY (`renderCard`), never over its
98
+ // data — a Community card carries votes where a Tasks card carries a ledger.
99
+ // Community's fixture-backed `components/ui/community/board.tsx` is deleted in
100
+ // the same change.
101
+ //
102
+ // `hueClass()` arms the derived tint ladder (`.hue` + `.hue-<key>` in
103
+ // styles.css), which mixes a --folder-* hue INTO --surface-card with
104
+ // color-mix so every rung is opaque in both themes by construction. `red` is
105
+ // excluded: --folder-red is byte-identical to --accent in light mode.
106
+ export {
107
+ BOARD_HUE_KEYS,
108
+ hueClass,
109
+ hueForKey,
110
+ type BoardHueKey,
111
+ } from './hue'
112
+ export {
113
+ Board,
114
+ BoardColumn,
115
+ type BoardProps,
116
+ type BoardColumnProps,
117
+ type BoardColumnSpec,
118
+ type BoardCardContext,
119
+ type BoardItemLeadContext,
120
+ } from './board'
121
+ export {
122
+ TaskCard,
123
+ type TaskCardProps,
124
+ type TaskCardAvatar,
125
+ type TaskCardCount,
126
+ type TaskCardDate,
127
+ type TaskCardDragProps,
128
+ type TaskCardMedia,
129
+ } from './task-card'
130
+ export {
131
+ ProgressLedger,
132
+ ledgerProgress,
133
+ type ProgressLedgerProps,
134
+ type LedgerCell,
135
+ type LedgerProgress,
136
+ type LedgerSegment,
137
+ } from './progress-ledger'
138
+ // The status vocabulary (header-primitives §2). `StatusRing` renders a REAL
139
+ // percent — the one `ledgerProgress()` computes, so a ring and the ledger it
140
+ // sits beside cannot disagree; `StatusGlyph` covers the discrete states, in
141
+ // lucide rather than the source's six hand-written inline SVGs.
142
+ export {
143
+ BOARD_STATUS_KEYS,
144
+ STATUS_GLYPHS,
145
+ STATUS_LABELS,
146
+ StatusGlyph,
147
+ StatusRing,
148
+ type BoardStatusKey,
149
+ type StatusGlyphProps,
150
+ type StatusRingProps,
151
+ } from './status'
152
+
74
153
  // EmptyState — promoted post-phase-15 (ADR-019). 4-consumer trigger:
75
154
  // Products / Personas / Competitors / Assets lenses all forked their own
76
155
  // full-area empty-state pattern. Canonical shape derived from the Phase
@@ -379,6 +458,25 @@ export {
379
458
  type ShellStatTone,
380
459
  } from './shell'
381
460
 
461
+ // Avatar / AvatarGroup — a person. Promoted in ADR-147 D14, collapsing four
462
+ // forks (task-card's AvatarStack, community/primitives, its byte-identical
463
+ // admin clone, and the inline one in folders/folder-row) that disagreed on
464
+ // hash, initials cap, geometry and colour. `initials()` and
465
+ // `avatarTintForKey()` are the one derivation each, exported so a caller that
466
+ // needs the monogram or the hue outside an avatar does not re-derive it.
467
+ export {
468
+ Avatar,
469
+ AvatarGroup,
470
+ AVATAR_TINTS,
471
+ asAvatarTint,
472
+ avatarTintForKey,
473
+ initials,
474
+ type AvatarProps,
475
+ type AvatarGroupProps,
476
+ type AvatarSize,
477
+ type AvatarTint,
478
+ } from './avatar'
479
+
382
480
  // FolderCard — drive-style folder tile (colour-keyed swatch + name +
383
481
  // count + hover checkbox / menu slot). Promoted in ADR-035 (Assets
384
482
  // lens redesign); a card surface, so it owns the internal surface
@@ -526,3 +624,208 @@ export type {
526
624
  PreviewConfiguration,
527
625
  } from './meta-previews/types'
528
626
  export { PLACEMENT_CONFIGS } from './meta-previews/types'
627
+
628
+ // Board filter model — pure TypeScript, no React. See src/filter-core/index.ts.
629
+ export * from './filter-core'
630
+
631
+ // FilterMenu + FilterChips — the UI layer over filter-core (header-primitives
632
+ // §5). Two-step field-then-values picker with faceted counts, and the active
633
+ // filters as removable, editable chips. The host owns FiltersState and is
634
+ // expected to keep it in the URL, which is what makes a filtered view a
635
+ // shareable link.
636
+ export {
637
+ FilterMenu,
638
+ FilterChipBar,
639
+ type FilterMenuProps,
640
+ type FilterChipBarProps,
641
+ type FilterOptionIconRenderer,
642
+ type FilterFieldIconRenderer,
643
+ } from './filter-menu'
644
+
645
+ // The Display popover and its row primitives (header-primitives §4) — the
646
+ // boundary the source never drew. Every control here is real or absent;
647
+ // PopoverFooterActions renders no primary unless one is supplied.
648
+ export {
649
+ DisplayPopover,
650
+ OptionRow,
651
+ SelectRow,
652
+ ToggleRow,
653
+ ChipToggleGroup,
654
+ SectionHeading,
655
+ PopoverFooterActions,
656
+ type DisplayPopoverProps,
657
+ type DisplayPopoverSectionProps,
658
+ type OptionRowProps,
659
+ type SelectRowProps,
660
+ type ToggleRowProps,
661
+ type ChipToggleGroupProps,
662
+ type ChipToggleItem,
663
+ type SectionHeadingProps,
664
+ type PopoverFooterActionsProps,
665
+ type PopoverFooterAction,
666
+ } from './display-popover'
667
+
668
+ // createDisplayStore — one factory for per-scope display settings, passed
669
+ // down by CONTEXT rather than read from a module singleton (the trap
670
+ // header-primitives §4 names: their cards subscribe to a global store, so two
671
+ // boards with different settings cannot coexist).
672
+ export {
673
+ createDisplayStore,
674
+ type DisplayStore,
675
+ type DisplayController,
676
+ type DisplaySettings,
677
+ type DisplayValue,
678
+ type DisplayAllowed,
679
+ type DisplayMigrations,
680
+ type DisplayProviderProps,
681
+ type DisplayStoreOptions,
682
+ } from './display-store'
683
+ // ── ADR-146 — the analytics primitive set ─────────────────────────────
684
+ //
685
+ // Promoted as ONE block (ADR-146 D5). The trigger was not a missing chart:
686
+ // it was ~26 forked stat tiles, 3 independent delta chips, and 7 hand-written
687
+ // SVG charts sitting in `design-system/spec/shell-charts.tsx` that no product
688
+ // lens could reach — while `--series-1..8` sat in `theme-v2.css` with zero
689
+ // consumers. Each primitive below carries a MEASURED consumer count in its
690
+ // own docstring — measured 2026-09-06, gallery demos excluded. Only
691
+ // `StatCard` clears the ADR-008 D3 two-consumer gate today; the rest are
692
+ // shipped ahead of their consumers, which is a deliberate, recorded debt, not
693
+ // a met gate. The per-primitive ledger and the named surface that should
694
+ // become each one's second consumer live in
695
+ // `docs/follow-ups/_pending/adr-146-consumer-gate.md`. The ~26 forked tiles
696
+ // are NOT rewritten here (ADR-146 D13).
697
+ //
698
+ // Three rules that govern every export in this block:
699
+ // D7 a chart is MONOCHROME until it earns a palette — the accent's scarcity
700
+ // is the point, and bar length already encodes what bar colour would.
701
+ // D8 series colour is a frozen literal lookup, never a constructed
702
+ // `--series-${i}`. A name that misses is invalid CSS the browser
703
+ // silently discards, with every gate green.
704
+ // D10 identity is never colour alone, and text wears text tokens, never a
705
+ // series colour. The empty / loading / error states and the table view
706
+ // come from `ChartFrame`, so — measured 2026-09-06, not asserted — only
707
+ // `LineChart`, `AreaChart` and `BarChart` carry them. `DonutChart`,
708
+ // `RankedBars`, `Sparkline` and `GaugeRing` do NOT compose the frame (a
709
+ // donut has no axes; ranked bars are their own rows), so they have no
710
+ // table view. Their data does reach assistive tech another way — the
711
+ // donut's legend is a label+value+percentage list and RankedBars is a
712
+ // labelled `<ul>` — but D10.5 says "every chart", and four of seven is
713
+ // not every. Tracked as a deviation in the ADR-146 completion report.
714
+ //
715
+ // Stat layer — DeltaChip replaces the 3 forks (admin/stat-tile.tsx:10,
716
+ // shell-preview.tsx:254, community/primitives.tsx:117); StatCard is the KPI
717
+ // tile WITH a delta slot, the slot whose absence caused the forks; StatStrip
718
+ // is one card divided by hairlines, which is a different object from a grid
719
+ // of StatCards and is why budget-flighting's GlobalHeader forked.
720
+ export { DeltaChip, type DeltaChipProps, type DeltaDirection, type DeltaFormat } from './delta-chip'
721
+ export { StatCard, type StatCardProps, type StatCardSize, type StatCardFooterLink } from './stat-card'
722
+ export { StatStrip, type StatStripProps, type StatStripCell } from './stat-strip'
723
+
724
+ // Chart chrome — the frame every chart draws inside, the legend that is the
725
+ // only place a series hue may sit beside text, and the readout that positions
726
+ // by transform so it can never reflow the plot.
727
+ export {
728
+ ChartFrame,
729
+ type ChartFrameProps,
730
+ type ChartFrameState,
731
+ type ChartFrameView,
732
+ type ChartTableData,
733
+ type ChartTableRow,
734
+ } from './chart-frame'
735
+ export { ChartLegend, type ChartLegendProps, type ChartLegendItem } from './chart-legend'
736
+ export { ChartTooltip, tooltipPosition, type ChartTooltipProps, type ChartTooltipRow } from './chart-tooltip'
737
+
738
+ // The series palette (ADR-146 D2/D8). Re-exported through `chart-legend.tsx`
739
+ // so this block stays one import surface and no consumer reaches into `lib/`.
740
+ // `lib/series.ts` remains the canonical definition. Callers pass a SLOT
741
+ // INDEX — never a colour, and never a token name.
742
+ export {
743
+ SERIES,
744
+ SERIES_SCATTER,
745
+ SERIES_SLOT_COUNT,
746
+ SCATTER_SLOT_COUNT,
747
+ OTHER_SLOT_COLOR,
748
+ seriesColor,
749
+ scatterColor,
750
+ paletteColor,
751
+ slotColor,
752
+ isSeriesSlot,
753
+ isScatterSlot,
754
+ type SeriesSlot,
755
+ type ScatterSlot,
756
+ type SeriesPalette,
757
+ type ChartSlot,
758
+ } from './chart-legend'
759
+
760
+ // Chart layer. Every one takes its data as `{ label, value }[]` and its series
761
+ // identity as a slot index; none of them accepts a colour.
762
+ export {
763
+ LineChart,
764
+ AreaChart,
765
+ type LineChartProps,
766
+ type LineChartSeries,
767
+ type AreaChartProps,
768
+ } from './line-chart'
769
+ export {
770
+ BarChart,
771
+ type BarChartProps,
772
+ type BarChartDatum,
773
+ type BarChartSeries,
774
+ type BarChartOrientation,
775
+ type BarChartMode,
776
+ type BarChartEmphasis,
777
+ } from './bar-chart'
778
+ export {
779
+ DonutChart,
780
+ foldDonutData,
781
+ withMinimumArc,
782
+ DONUT_OTHER_KEY,
783
+ type DonutChartProps,
784
+ type DonutChartDatum,
785
+ type DonutSlice,
786
+ type DonutFold,
787
+ type DonutGapSurface,
788
+ } from './donut-chart'
789
+ export { RankedBars, type RankedBarsProps, type RankedBarsItem, type RankedBarsSort } from './ranked-bars'
790
+ export { Sparkline, type SparklineProps, type SparklineVariant } from './sparkline'
791
+ export { GaugeRing, type GaugeRingProps, type GaugeBand, type GaugeTone } from './gauge-ring'
792
+
793
+ // Chart math (ADR-146 D6) — the scale / tick / path / arc arithmetic that was
794
+ // inlined in seven places, now one tested module. `niceTicks` is the piece
795
+ // with no ancestor in the repo: gridlines were the literal list
796
+ // [0.25, 0.5, 0.75] and axis labels were hardcoded strings. Pure functions;
797
+ // they produce numbers and path data and never name a colour or a token.
798
+ export {
799
+ linearScale,
800
+ bandScale,
801
+ niceTicks,
802
+ linePath,
803
+ areaPath,
804
+ arcGeometry,
805
+ arcSegments,
806
+ ringDash,
807
+ clampPct,
808
+ normalizeToMax,
809
+ stackTotals,
810
+ stackFractions,
811
+ monthDomain,
812
+ type NumericInput,
813
+ type Point,
814
+ type Extent,
815
+ type ScaleFn,
816
+ type BandScale,
817
+ type ArcGeometry,
818
+ type ArcSegment,
819
+ type RingDash,
820
+ } from './lib/chart'
821
+
822
+ // ── Item detail (tasks-item-detail-full.md) ─────────────────────────────────
823
+ // The three-mode detail frame, its slotted header and ellipsis menu, the
824
+ // Notion-style field rows, and the activity pane. Composed by a lens; owns no
825
+ // fetch, no entity and no board.
826
+ export * from './detail'
827
+
828
+ // ── Thread (ADR-147) ────────────────────────────────────────────────────────
829
+ // A headless comment/social core plus one styled skin, bound to an arbitrary
830
+ // entity rather than to a chat room. Owns no fetch client and no base URL.
831
+ export * from './thread'
package/src/kbd.tsx CHANGED
@@ -20,17 +20,40 @@ export interface KbdProps {
20
20
  children: ReactNode
21
21
  /** Optional size — defaults to 'md'. */
22
22
  size?: 'sm' | 'md'
23
+ /**
24
+ * Which surface this sits on. `neutral` is a normal page/card surface;
25
+ * `on-accent` is inside a filled accent control (a primary button's shortcut
26
+ * hint).
27
+ *
28
+ * This exists because the neutral palette is illegible on an accent fill and
29
+ * fails quietly rather than loudly. `--text-tertiary` ink and a `--border`
30
+ * hairline are tuned for a light-neutral ground; dropped onto `--accent` they
31
+ * read as grey-on-red mush, which is exactly what shipped in the thread
32
+ * composer before this prop existed — and it forked this primitive inline to
33
+ * do it, so the bug was invisible here.
34
+ */
35
+ tone?: 'neutral' | 'on-accent'
23
36
  }
24
37
 
25
- export function Kbd({ children, size = 'md' }: KbdProps) {
38
+ export function Kbd({ children, size = 'md', tone = 'neutral' }: KbdProps) {
39
+ const onAccent = tone === 'on-accent'
26
40
  const style: CSSProperties = {
27
41
  display: 'inline-flex',
28
42
  alignItems: 'center',
29
43
  justifyContent: 'center',
30
44
  gap: '0.125rem',
31
- background: 'rgb(var(--surface-overlay-soft))',
32
- border: '1px solid rgb(var(--border))',
33
- color: 'rgb(var(--text-tertiary))',
45
+ // On an accent fill the INK stays at full strength and the container does
46
+ // the quieting. Fading the text instead is the tempting move and it is
47
+ // wrong: white at 85% over --accent measures 4.33:1, under AA, while full
48
+ // white is 5.64:1. The translucent fill also lightens the local ground, so
49
+ // it raises the text's contrast rather than lowering it.
50
+ background: onAccent
51
+ ? 'rgb(var(--accent-foreground) / 0.16)'
52
+ : 'rgb(var(--surface-overlay-soft))',
53
+ border: `1px solid ${
54
+ onAccent ? 'rgb(var(--accent-foreground) / 0.4)' : 'rgb(var(--border))'
55
+ }`,
56
+ color: onAccent ? 'rgb(var(--accent-foreground))' : 'rgb(var(--text-tertiary))',
34
57
  fontFamily: 'var(--font-mono)',
35
58
  fontSize: size === 'sm' ? '10.5px' : '11.5px',
36
59
  padding: size === 'sm' ? '1px 6px' : '2px 8px',