@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
@@ -0,0 +1,196 @@
1
+ /**
2
+ * The byline action cluster — `↩ Reply` and a `•••` overflow, at the inline-end
3
+ * of the byline row.
4
+ *
5
+ * WHY THESE ARE QUIET, AND WHY THAT IS A DELIBERATE EXCEPTION
6
+ *
7
+ * CLAUDE.md §2 bans `variant="ghost"` as the default lesser-button: "buttons
8
+ * must read as buttons, with at least a 1px border", with ghost "reserved for
9
+ * genuinely chrome-dense contexts — side-panel modals, inline icon toolbars".
10
+ * A per-comment byline action cluster is exactly that context, and there is a
11
+ * second, product reason on top of it:
12
+ *
13
+ * Owner direction, 2026-09-06: Reply sits slightly OUT OF THE WAY, so people
14
+ * reach for the main comment box instead of threading every remark. A
15
+ * bordered button beside a name is an invitation. A muted, borderless one is
16
+ * available without asking to be used.
17
+ *
18
+ * So this is intentional. Do not "fix" it back to bordered secondary buttons in
19
+ * an audit — that would reverse a product decision, not correct a slip.
20
+ *
21
+ * NO REFLOW. Two separate guarantees, because they fail differently:
22
+ *
23
+ * • The cluster is ALWAYS VISIBLE, never revealed on hover. A hover-revealed
24
+ * control changes the row's occupied width as the pointer crosses it, and
25
+ * with a flexible name beside it that re-truncates the name on every pass.
26
+ * • The cluster reserves a fixed `min-inline-size`, so a comment with no
27
+ * actions at all (a tombstone) truncates its name at the same column as a
28
+ * live one. The identity group beside it is `min-w-0 flex-1` with the name
29
+ * truncating, and the cluster is `shrink-0`, so a long name eats its own
30
+ * width rather than pushing the actions off the row.
31
+ */
32
+
33
+ import { useCallback } from 'react'
34
+ import { Copy, CornerDownRight, Link2, MoreHorizontal, Pencil, Trash2 } from 'lucide-react'
35
+ import type { CSSProperties } from 'react'
36
+ import { cn } from '../lib/utils'
37
+ import {
38
+ DropdownMenu,
39
+ DropdownMenuContent,
40
+ DropdownMenuItem,
41
+ DropdownMenuTrigger,
42
+ } from '../dropdown-menu'
43
+
44
+ /**
45
+ * Quiet: no border, no fill at rest, muted ink. The hover fill and the focus
46
+ * ring are what make it legible as a control — an interactive element still has
47
+ * to look interactive (better-layout §2), it just does not have to shout.
48
+ */
49
+ const QUIET = cn(
50
+ 'inline-flex items-center justify-center gap-[var(--space-1)] border-0 bg-transparent',
51
+ 'text-[12px] font-semibold text-[rgb(var(--text-tertiary))]',
52
+ 'transition-[background-color,color] duration-[var(--dur-fast)] ease-[var(--ease-out)]',
53
+ 'motion-reduce:transition-none',
54
+ 'hover:bg-[rgb(var(--surface-overlay))] hover:text-[rgb(var(--foreground))]',
55
+ 'focus-visible:outline-none focus-visible:[box-shadow:var(--ring-focus)]',
56
+ 'disabled:cursor-default disabled:opacity-60',
57
+ )
58
+
59
+ /** 24x24 floor from the interface standards §6, met by padding rather than ink. */
60
+ const QUIET_BOX: CSSProperties = {
61
+ minBlockSize: 'var(--space-6)',
62
+ minInlineSize: 'var(--space-6)',
63
+ paddingInline: 'var(--space-2)',
64
+ borderRadius: 'var(--radius-xs)',
65
+ }
66
+
67
+ const QUIET_ICON_BOX: CSSProperties = {
68
+ ...QUIET_BOX,
69
+ paddingInline: '0',
70
+ }
71
+
72
+ /**
73
+ * Reserved so a byline with no actions truncates its name at the same column as
74
+ * one with both. Sized for the widest cluster this renders — `↩ Reply` plus the
75
+ * overflow — as a multiple of the spacing scale rather than a measured pixel
76
+ * count, because the exact width moves with the font and the reserve only has
77
+ * to be *at least* the real width.
78
+ */
79
+ const CLUSTER: CSSProperties = {
80
+ minInlineSize: 'calc(var(--space-12) * 2)',
81
+ }
82
+
83
+ export interface CommentActionsProps {
84
+ /** Author display name. Thirty "Reply" buttons need thirty distinct names. */
85
+ readonly label: string
86
+ readonly replyOpen: boolean
87
+ /** Omitted, no Reply is rendered — a read-only surface, or a tombstone. */
88
+ readonly onReplyToggle?: (() => void) | undefined
89
+ readonly onCopyLink?: (() => void) | undefined
90
+ /** The body to put on the clipboard. Omitted, the item is not rendered. */
91
+ readonly copyText?: string | undefined
92
+ /** Reports the clipboard outcome so a host can toast either way. */
93
+ readonly onCopyText?: ((ok: boolean) => void) | undefined
94
+ readonly onEdit?: (() => void) | undefined
95
+ readonly onDelete?: (() => void) | undefined
96
+ }
97
+
98
+ /**
99
+ * `navigator.clipboard` is absent in an insecure context and in some embedded
100
+ * webviews. Checked before the item is RENDERED, so we never offer an
101
+ * affordance that cannot work — and the write is still guarded, because
102
+ * permission can be denied at the moment of the call.
103
+ */
104
+ function clipboardAvailable(): boolean {
105
+ return typeof navigator !== 'undefined' && 'clipboard' in navigator
106
+ }
107
+
108
+ export function CommentActions({
109
+ label,
110
+ replyOpen,
111
+ onReplyToggle,
112
+ onCopyLink,
113
+ copyText,
114
+ onCopyText,
115
+ onEdit,
116
+ onDelete,
117
+ }: CommentActionsProps) {
118
+ const canCopyText = copyText !== undefined && copyText !== '' && clipboardAvailable()
119
+
120
+ const copy = useCallback(() => {
121
+ if (copyText === undefined) return
122
+ // Never assume it resolves: a denied permission, a document that is not
123
+ // focused, and a webview with no clipboard all reject here. The host is
124
+ // told which happened so it can say so rather than silently doing nothing.
125
+ navigator.clipboard.writeText(copyText).then(
126
+ () => {
127
+ onCopyText?.(true)
128
+ },
129
+ () => {
130
+ onCopyText?.(false)
131
+ },
132
+ )
133
+ }, [copyText, onCopyText])
134
+
135
+ const hasMenu = onCopyLink !== undefined || canCopyText || onEdit !== undefined || onDelete !== undefined
136
+
137
+ return (
138
+ <div
139
+ className="flex shrink-0 items-center justify-end gap-[var(--space-1)]"
140
+ style={CLUSTER}
141
+ >
142
+ {onReplyToggle === undefined ? null : (
143
+ <button
144
+ type="button"
145
+ onClick={onReplyToggle}
146
+ aria-label={`Reply to ${label}`}
147
+ aria-expanded={replyOpen}
148
+ className={QUIET}
149
+ style={QUIET_BOX}
150
+ >
151
+ <CornerDownRight size={12} strokeWidth={1.75} aria-hidden="true" />
152
+ Reply
153
+ </button>
154
+ )}
155
+
156
+ {hasMenu ? (
157
+ <DropdownMenu>
158
+ <DropdownMenuTrigger
159
+ unstyled
160
+ aria-label={`More actions for ${label}'s comment`}
161
+ className={QUIET}
162
+ style={QUIET_ICON_BOX}
163
+ >
164
+ <MoreHorizontal size={12} strokeWidth={1.75} aria-hidden="true" />
165
+ </DropdownMenuTrigger>
166
+ <DropdownMenuContent align="end">
167
+ {onCopyLink === undefined ? null : (
168
+ <DropdownMenuItem onClick={onCopyLink}>
169
+ <Link2 size={12} strokeWidth={1.75} aria-hidden="true" />
170
+ Copy link
171
+ </DropdownMenuItem>
172
+ )}
173
+ {canCopyText ? (
174
+ <DropdownMenuItem onClick={copy}>
175
+ <Copy size={12} strokeWidth={1.75} aria-hidden="true" />
176
+ Copy text
177
+ </DropdownMenuItem>
178
+ ) : null}
179
+ {onEdit === undefined ? null : (
180
+ <DropdownMenuItem onClick={onEdit}>
181
+ <Pencil size={12} strokeWidth={1.75} aria-hidden="true" />
182
+ Edit
183
+ </DropdownMenuItem>
184
+ )}
185
+ {onDelete === undefined ? null : (
186
+ <DropdownMenuItem variant="destructive" onClick={onDelete}>
187
+ <Trash2 size={12} strokeWidth={1.75} aria-hidden="true" />
188
+ Delete
189
+ </DropdownMenuItem>
190
+ )}
191
+ </DropdownMenuContent>
192
+ </DropdownMenu>
193
+ ) : null}
194
+ </div>
195
+ )
196
+ }