@lovett/ui 0.2.4 → 0.2.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovett/ui",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Design system primitives, tokens, layouts, and patterns for the Lovett portfolio.",
@@ -52,7 +52,7 @@ const QUIET = cn(
52
52
  'transition-[background-color,color] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
53
53
  'motion-reduce:transition-none',
54
54
  'hover:bg-[rgb(var(--surface-overlay))] hover:text-[rgb(var(--foreground))]',
55
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
55
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
56
56
  'disabled:cursor-default disabled:opacity-60',
57
57
  )
58
58
 
@@ -108,7 +108,7 @@ export const QUIET_ICON = cn(
108
108
  'transition-[background-color,color] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
109
109
  'motion-reduce:transition-none',
110
110
  'hover:bg-[rgb(var(--surface-overlay))] hover:text-[rgb(var(--foreground))]',
111
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
111
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
112
112
  'disabled:cursor-default disabled:opacity-60',
113
113
  )
114
114
 
@@ -719,7 +719,7 @@ export function AttachmentGif({ attachment, resolveUrl, className }: AttachmentG
719
719
  setPlaying(true)
720
720
  void videoRef.current?.play()
721
721
  }}
722
- className="absolute inset-0 flex items-center justify-center focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]"
722
+ className="absolute inset-0 flex items-center justify-center focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]"
723
723
  style={{ background: 'rgb(var(--media-scrim-mid))', color: 'rgb(var(--media-on-scrim))' }}
724
724
  >
725
725
  <Play size={20} strokeWidth={1.75} aria-hidden="true" />
@@ -886,7 +886,7 @@ export function AttachmentImage({
886
886
  'transition-[border-color] duration-[var(--dur-fast)] ease-[var(--ease-out)] motion-reduce:transition-none',
887
887
  interactive && !showStill && 'hover:border-[rgb(var(--border-strong))]',
888
888
  (interactive || showStill) &&
889
- 'focus-within:outline-none focus-within:[box-shadow:var(--ring-focus)]',
889
+ 'focus-within:outline-none focus-within:[box-shadow:var(--ring-focus-inset)]',
890
890
  className,
891
891
  )}
892
892
  style={{
@@ -118,7 +118,7 @@ export function CommentBody({ bodyMd, onSelectMention, className }: CommentBodyP
118
118
  'motion-reduce:transition-none',
119
119
  'border border-[rgb(var(--border))] bg-[rgb(var(--surface-inset))] text-[rgb(var(--accent-ink))]',
120
120
  interactive &&
121
- 'hover:border-[rgb(var(--accent))] hover:bg-[rgb(var(--surface-hover))] focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
121
+ 'hover:border-[rgb(var(--accent))] hover:bg-[rgb(var(--surface-hover))] focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
122
122
  )}
123
123
  style={{
124
124
  borderRadius: 'var(--radius-sm)',
@@ -329,7 +329,7 @@ function Attachments({
329
329
  'border-[rgb(var(--border))] bg-[rgb(var(--surface-inset))] text-[rgb(var(--text-secondary))]',
330
330
  'transition-[background-color,border-color] duration-[var(--dur-fast)] ease-[var(--ease-out)] motion-reduce:transition-none',
331
331
  'enabled:hover:border-[rgb(var(--border-strong))] enabled:hover:bg-[rgb(var(--surface-hover))]',
332
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
332
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
333
333
  'disabled:cursor-default',
334
334
  )}
335
335
  style={{ borderRadius: 'var(--thread-inner-radius, var(--radius-sm))' }}
@@ -596,7 +596,7 @@ export function CommentItem({
596
596
  className={cn(
597
597
  'min-w-0 truncate text-[14px] font-semibold text-[rgb(var(--foreground))]',
598
598
  'underline-offset-2 hover:underline',
599
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
599
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
600
600
  )}
601
601
  style={{ borderRadius: 'var(--radius-xs)' }}
602
602
  >
@@ -241,7 +241,7 @@ export function LinkPreview({ preview, onHide, className }: LinkPreviewProps) {
241
241
  onClick={() => {
242
242
  setPlaying(true)
243
243
  }}
244
- className="absolute inset-0 flex items-center justify-center focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]"
244
+ className="absolute inset-0 flex items-center justify-center focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]"
245
245
  style={{
246
246
  // diag:contrast-ok — this scrim sits on a PHOTOGRAPH, which
247
247
  // is the one thing check-contrast says it cannot see:
@@ -278,7 +278,7 @@ export function LinkPreview({ preview, onHide, className }: LinkPreviewProps) {
278
278
  rel="noopener noreferrer"
279
279
  className={cn(
280
280
  'group/link block min-w-0 flex-1 no-underline',
281
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
281
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
282
282
  )}
283
283
  style={{ borderRadius: 'var(--radius-xs)' }}
284
284
  >
@@ -129,7 +129,7 @@ export function ThreadRail({ collapsed, label, onToggle }: ThreadRailProps) {
129
129
  'motion-reduce:transition-none',
130
130
  'group-hover/rail:border-[rgb(var(--border-strong))] group-hover/rail:text-[rgb(var(--foreground))]',
131
131
  'group-focus-visible/rail:border-[rgb(var(--accent))] group-focus-visible/rail:text-[rgb(var(--accent-ink))]',
132
- 'group-focus-visible/rail:[box-shadow:var(--ring-focus)]',
132
+ 'group-focus-visible/rail:[box-shadow:var(--ring-focus-inset)]',
133
133
  'group-active/rail:scale-[0.96] motion-reduce:group-active/rail:scale-100',
134
134
  )}
135
135
  style={{
@@ -324,7 +324,7 @@ export function CollapsedReplies({
324
324
  'transition-[background-color,border-color,color] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
325
325
  'motion-reduce:transition-none',
326
326
  'hover:border-[rgb(var(--border-strong))] hover:bg-[rgb(var(--surface-hover))] hover:text-[rgb(var(--foreground))]',
327
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
327
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
328
328
  )}
329
329
  style={{ minBlockSize: 'var(--space-6)', borderRadius: 'var(--radius-sm)' }}
330
330
  >
@@ -365,7 +365,7 @@ export function CommentVote({
365
365
  'inline-flex items-center justify-center border border-transparent',
366
366
  'transition-[color,background-color,transform] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
367
367
  'motion-reduce:transition-none',
368
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
368
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
369
369
  'active:scale-[0.96] motion-reduce:active:scale-100',
370
370
  'disabled:cursor-not-allowed disabled:opacity-60',
371
371
  active
@@ -1242,7 +1242,7 @@ function RichSurface({
1242
1242
  'ds-thread-rich-composer relative min-w-0 w-full border',
1243
1243
  'transition-[border-color,box-shadow] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
1244
1244
  'motion-reduce:transition-none',
1245
- 'focus-within:border-[rgb(var(--accent))] focus-within:[box-shadow:var(--ring-focus)]',
1245
+ 'focus-within:border-[rgb(var(--accent))] focus-within:[box-shadow:var(--ring-focus-inset)]',
1246
1246
  )}
1247
1247
  style={shellStyle}
1248
1248
  >
@@ -137,7 +137,7 @@ function SortPills({
137
137
  'inline-flex h-[24px] items-center px-[var(--space-2)] text-[12px] font-semibold',
138
138
  'transition-[background-color,color,border-color,box-shadow] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
139
139
  'motion-reduce:transition-none',
140
- 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
140
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus-inset)]',
141
141
  active
142
142
  ? 'border border-[rgb(var(--accent))] bg-[rgb(var(--surface-card))] text-[rgb(var(--accent-ink))] [box-shadow:var(--shadow-sm)]'
143
143
  : 'border border-transparent text-[rgb(var(--text-tertiary))] hover:text-[rgb(var(--foreground))]',
@@ -31,6 +31,25 @@ export const THREAD_REACTION_KEYS = [
31
31
 
32
32
  export type ThreadReactionKey = (typeof THREAD_REACTION_KEYS)[number]
33
33
 
34
+ /**
35
+ * FOCUS RINGS IN THIS MODULE ARE INSET. All of them, without exception.
36
+ *
37
+ * Not a preference — a consequence of where this module renders. `comment.tsx`
38
+ * wraps the whole reply subtree in the `overflow-hidden` that its grid-rows
39
+ * reveal requires; `Card` clips by default; a thread lives in a scroll
40
+ * container. An OUTSET `box-shadow` ring is clipped by any of those, and a
41
+ * clipped focus ring does not degrade — it DISAPPEARS, which is a WCAG 2.4.7
42
+ * failure rather than a cosmetic one.
43
+ *
44
+ * So: `var(--ring-focus-inset)` on every focusable thing in `src/thread/`, and
45
+ * `var(--ring-focus)` is a bug here. It was found the slow way — the owner
46
+ * reported the reply composer clipped, that one was fixed, and switching to the
47
+ * rich editor clipped the next one. Fourteen more were waiting. Fixing them by
48
+ * report is a queue with no end; the rule is the fix.
49
+ *
50
+ * Outside this module the outset ring is still correct and still the default.
51
+ */
52
+
34
53
  /** One reaction row as the API groups it: key, tally, and the viewer's own state. */
35
54
  export interface ThreadReactionCount {
36
55
  readonly key: ThreadReactionKey