@lovett/ui 0.0.11 → 0.1.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 (141) 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 +5402 -71
  4. package/dist/index.js +21992 -11618
  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 +208 -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__/sparkline.test.tsx +368 -0
  27. package/src/__tests__/stat-layer.test.tsx +271 -0
  28. package/src/__tests__/stat-strip.test.tsx +175 -0
  29. package/src/__tests__/status.test.tsx +293 -0
  30. package/src/__tests__/tabs.test.tsx +303 -0
  31. package/src/__tests__/token-shape.test.ts +132 -2
  32. package/src/avatar.tsx +352 -0
  33. package/src/bar-chart.tsx +1214 -0
  34. package/src/board.tsx +658 -0
  35. package/src/chart-frame.tsx +960 -0
  36. package/src/chart-legend.tsx +304 -0
  37. package/src/chart-tooltip.tsx +267 -0
  38. package/src/code-block.tsx +62 -8
  39. package/src/delta-chip.tsx +263 -0
  40. package/src/detail/__tests__/activity-pane.test.tsx +186 -0
  41. package/src/detail/__tests__/detail-chrome.test.tsx +134 -0
  42. package/src/detail/__tests__/detail-surface.test.tsx +529 -0
  43. package/src/detail/__tests__/field-row.test.tsx +357 -0
  44. package/src/detail/activity-pane.tsx +507 -0
  45. package/src/detail/calendar.tsx +355 -0
  46. package/src/detail/detail-divider.tsx +261 -0
  47. package/src/detail/detail-header.tsx +287 -0
  48. package/src/detail/detail-menu.tsx +254 -0
  49. package/src/detail/detail-surface.tsx +1110 -0
  50. package/src/detail/field-list.tsx +196 -0
  51. package/src/detail/field-row.tsx +1131 -0
  52. package/src/detail/index.ts +58 -0
  53. package/src/detail/segmented-choice.tsx +94 -0
  54. package/src/detail/types.ts +129 -0
  55. package/src/display-popover.tsx +487 -0
  56. package/src/display-store.tsx +242 -0
  57. package/src/donut-chart.tsx +988 -0
  58. package/src/dropdown-menu.tsx +290 -19
  59. package/src/filter-core/EXPORTS.md +85 -0
  60. package/src/filter-core/__tests__/columns.test.ts +159 -0
  61. package/src/filter-core/__tests__/faceting.test.ts +193 -0
  62. package/src/filter-core/__tests__/filter-fns.test.ts +519 -0
  63. package/src/filter-core/__tests__/operators.test.ts +235 -0
  64. package/src/filter-core/__tests__/state.test.ts +268 -0
  65. package/src/filter-core/__tests__/url.test.ts +350 -0
  66. package/src/filter-core/columns.ts +134 -0
  67. package/src/filter-core/date-utils.ts +38 -0
  68. package/src/filter-core/examples/task-filter-columns.ts +121 -0
  69. package/src/filter-core/faceting.ts +120 -0
  70. package/src/filter-core/filter-fns.ts +335 -0
  71. package/src/filter-core/index.ts +105 -0
  72. package/src/filter-core/operators.ts +433 -0
  73. package/src/filter-core/state.ts +280 -0
  74. package/src/filter-core/types.ts +247 -0
  75. package/src/filter-core/url.ts +261 -0
  76. package/src/filter-dropdown.tsx +12 -0
  77. package/src/filter-menu.tsx +649 -0
  78. package/src/floating-drawer.tsx +19 -1
  79. package/src/gauge-ring.tsx +435 -0
  80. package/src/hue.ts +52 -0
  81. package/src/index.ts +296 -0
  82. package/src/kbd.tsx +27 -4
  83. package/src/lib/chart.ts +866 -0
  84. package/src/lib/focus.ts +43 -1
  85. package/src/lib/layer-stack.ts +30 -3
  86. package/src/lib/remark-underline.ts +443 -0
  87. package/src/lib/series.ts +169 -0
  88. package/src/line-chart.tsx +1176 -0
  89. package/src/markdown.tsx +26 -7
  90. package/src/modal.tsx +42 -18
  91. package/src/progress-ledger.tsx +304 -0
  92. package/src/ranked-bars.tsx +386 -0
  93. package/src/segmented-pill.tsx +32 -9
  94. package/src/sortable.tsx +315 -1
  95. package/src/sparkline.tsx +416 -0
  96. package/src/stat-card.tsx +376 -0
  97. package/src/stat-strip.tsx +327 -0
  98. package/src/status.tsx +215 -0
  99. package/src/styles.css +1570 -0
  100. package/src/tabs.tsx +206 -25
  101. package/src/task-card.tsx +610 -0
  102. package/src/thread/__tests__/comment-body-hostile.test.tsx +331 -0
  103. package/src/thread/__tests__/comment-tree.test.ts +151 -0
  104. package/src/thread/__tests__/emoji.test.ts +187 -0
  105. package/src/thread/__tests__/fixtures/thread-fixture.ts +235 -0
  106. package/src/thread/__tests__/link-preview-source.test.ts +120 -0
  107. package/src/thread/__tests__/link-preview.test.tsx +600 -0
  108. package/src/thread/__tests__/markdown-format.test.ts +82 -0
  109. package/src/thread/__tests__/markdown-spec.test.ts +469 -0
  110. package/src/thread/__tests__/relative-time.test.ts +71 -0
  111. package/src/thread/__tests__/rich-composer.test.tsx +799 -0
  112. package/src/thread/__tests__/scroll-caret.test.ts +58 -0
  113. package/src/thread/__tests__/suggestion-list.test.tsx +648 -0
  114. package/src/thread/__tests__/thread-scroll-ownership.test.tsx +88 -0
  115. package/src/thread/__tests__/thread.test.tsx +662 -0
  116. package/src/thread/__tests__/use-attachments.test.tsx +679 -0
  117. package/src/thread/actions.tsx +196 -0
  118. package/src/thread/attachments.tsx +1071 -0
  119. package/src/thread/comment-body.tsx +148 -0
  120. package/src/thread/comment-tree.ts +182 -0
  121. package/src/thread/comment.tsx +915 -0
  122. package/src/thread/composer-footer.tsx +125 -0
  123. package/src/thread/composer.tsx +319 -0
  124. package/src/thread/emoji.ts +283 -0
  125. package/src/thread/index.ts +153 -0
  126. package/src/thread/link-preview.tsx +341 -0
  127. package/src/thread/markdown-format.ts +155 -0
  128. package/src/thread/markdown-spec.ts +754 -0
  129. package/src/thread/rail.tsx +372 -0
  130. package/src/thread/reactions.tsx +415 -0
  131. package/src/thread/relative-time.tsx +94 -0
  132. package/src/thread/rich-composer-impl.tsx +1601 -0
  133. package/src/thread/rich-composer.tsx +195 -0
  134. package/src/thread/scroll-caret.ts +37 -0
  135. package/src/thread/suggestion-list.tsx +182 -0
  136. package/src/thread/thread.tsx +718 -0
  137. package/src/thread/types.ts +218 -0
  138. package/src/thread/use-attachments.ts +598 -0
  139. package/src/thread/use-now.ts +73 -0
  140. package/src/thread/use-thread.ts +316 -0
  141. package/src/tokens.css +112 -0
@@ -0,0 +1,196 @@
1
+ /**
2
+ * FieldList — the chrome above the field rows, and the filter they read.
3
+ *
4
+ * `docs/design-briefs/tasks-item-detail-full.md` §4: "Above the list: Manage
5
+ * fields, a field search, and All / Filled / Empty chips that filter the
6
+ * visible rows."
7
+ *
8
+ * ## How a row disappears, and why it is `hidden` rather than unmounted
9
+ *
10
+ * Filtering by returning `null` from a filtered-out row would throw away that
11
+ * row's editor state — type half a due date, type a letter in the search box,
12
+ * lose it. §1's "switching modes must not lose an in-progress edit" is about
13
+ * modes, but the same loss through the search box would be no less of a bug.
14
+ * So a filtered-out row stays MOUNTED and carries the `hidden` attribute,
15
+ * which the package's reset renders as `display: none !important` and which
16
+ * removes it from the accessibility tree and the tab ring. Clear the search
17
+ * and the half-typed editor is still there.
18
+ *
19
+ * ## The value column
20
+ *
21
+ * Every row's value starts on the same line because the label column is one
22
+ * shared custom property, declared here and read by every row
23
+ * (`--field-label-inline-size`). A row cannot set its own and drift.
24
+ *
25
+ * ## The empty state
26
+ *
27
+ * Driven by CSS (`.ds-field-list-empty` in styles.css) rather than by counting
28
+ * matched rows in React. Counting would need every row to REGISTER with this
29
+ * component in an effect, which is a render-loop hazard for a purely cosmetic
30
+ * line — and the browser can already answer "are any of my rows visible?" with
31
+ * `:has()`.
32
+ */
33
+
34
+ import {
35
+ createContext,
36
+ useContext,
37
+ useId,
38
+ useState,
39
+ type CSSProperties,
40
+ type ReactNode,
41
+ } from 'react'
42
+ import { Search, Settings2 } from 'lucide-react'
43
+ import { Button } from '../button'
44
+ import { Input } from '../input'
45
+ import { cn } from '../lib/utils'
46
+ import { SegmentedChoice } from './segmented-choice'
47
+ import { FIELD_FILTERS, FIELD_FILTER_LABELS, type FieldFilter } from './types'
48
+
49
+ interface FieldListContextValue {
50
+ readonly query: string
51
+ readonly filter: FieldFilter
52
+ }
53
+
54
+ const FieldListCtx = createContext<FieldListContextValue | null>(null)
55
+
56
+ /**
57
+ * Whether a row with this label and fill state passes the list's current
58
+ * search and chip. Outside a `<FieldList>` every row is visible — a row is
59
+ * usable on its own.
60
+ */
61
+ export function useFieldRowVisible(label: string, filled: boolean): boolean {
62
+ const ctx = useContext(FieldListCtx)
63
+ if (ctx === null) return true
64
+ if (ctx.filter === 'filled' && !filled) return false
65
+ if (ctx.filter === 'empty' && filled) return false
66
+ const query = ctx.query.trim().toLowerCase()
67
+ if (query.length === 0) return true
68
+ return label.toLowerCase().includes(query)
69
+ }
70
+
71
+ /** React accepts custom properties in `style`; the DOM typings do not. */
72
+ type StyleWithVars = CSSProperties & Record<`--${string}`, string>
73
+
74
+ export interface FieldListProps {
75
+ children: ReactNode
76
+ /** Omit and no Manage fields control is drawn. */
77
+ onManageFields?: (() => void) | undefined
78
+ manageFieldsLabel?: string | undefined
79
+ /** Controlled search. Omit for internal state. */
80
+ query?: string | undefined
81
+ onQueryChange?: ((next: string) => void) | undefined
82
+ searchPlaceholder?: string | undefined
83
+ /** Controlled chip. Omit for internal state. */
84
+ filter?: FieldFilter | undefined
85
+ onFilterChange?: ((next: FieldFilter) => void) | undefined
86
+ /**
87
+ * Width of the shared label column, px. The DEFAULT lives in styles.css
88
+ * (`.ds-field-list`); this only overrides it.
89
+ */
90
+ labelWidth?: number | undefined
91
+ /** Shown when the search and chip leave nothing visible. */
92
+ emptyState?: ReactNode | undefined
93
+ className?: string | undefined
94
+ }
95
+
96
+ export function FieldList({
97
+ children,
98
+ onManageFields,
99
+ manageFieldsLabel = 'Manage fields',
100
+ query,
101
+ onQueryChange,
102
+ searchPlaceholder = 'Search fields',
103
+ filter,
104
+ onFilterChange,
105
+ labelWidth,
106
+ emptyState = 'No fields match that search.',
107
+ className,
108
+ }: FieldListProps) {
109
+ const searchId = useId()
110
+ const [internalQuery, setInternalQuery] = useState('')
111
+ const [internalFilter, setInternalFilter] = useState<FieldFilter>('all')
112
+ const activeQuery = query ?? internalQuery
113
+ const activeFilter = filter ?? internalFilter
114
+
115
+ const style: StyleWithVars | undefined =
116
+ labelWidth === undefined
117
+ ? undefined
118
+ : { '--field-label-inline-size': `${labelWidth}px` }
119
+
120
+ return (
121
+ <FieldListCtx.Provider value={{ query: activeQuery, filter: activeFilter }}>
122
+ <div
123
+ data-slot="field-list"
124
+ className={cn('ds-field-list flex min-h-0 flex-1 flex-col', className)}
125
+ {...(style === undefined ? {} : { style })}
126
+ >
127
+ <div
128
+ className="flex shrink-0 flex-wrap items-center border-b"
129
+ style={{
130
+ gap: 'var(--space-2)',
131
+ paddingInline: 'var(--space-4)',
132
+ paddingBlock: 'var(--space-2)',
133
+ borderColor: 'rgb(var(--border))',
134
+ }}
135
+ >
136
+ <label htmlFor={searchId} className="sr-only">
137
+ {searchPlaceholder}
138
+ </label>
139
+ <Input
140
+ id={searchId}
141
+ inputSize="sm"
142
+ type="search"
143
+ value={activeQuery}
144
+ placeholder={searchPlaceholder}
145
+ onChange={(event) => {
146
+ const next = event.target.value
147
+ if (query === undefined) setInternalQuery(next)
148
+ onQueryChange?.(next)
149
+ }}
150
+ leadingAffix={<Search size={14} strokeWidth={1.5} aria-hidden="true" />}
151
+ shellClassName="min-w-[10rem] flex-1"
152
+ />
153
+
154
+ <SegmentedChoice
155
+ ariaLabel="Filter fields"
156
+ value={activeFilter}
157
+ onChange={(next) => {
158
+ if (filter === undefined) setInternalFilter(next)
159
+ onFilterChange?.(next)
160
+ }}
161
+ items={FIELD_FILTERS.map((id) => ({ id, label: FIELD_FILTER_LABELS[id] }))}
162
+ />
163
+
164
+ {onManageFields === undefined ? null : (
165
+ <Button
166
+ variant="secondary"
167
+ size="sm"
168
+ onClick={onManageFields}
169
+ leadingIcon={<Settings2 size={14} strokeWidth={1.5} aria-hidden="true" />}
170
+ >
171
+ {manageFieldsLabel}
172
+ </Button>
173
+ )}
174
+ </div>
175
+
176
+ <div
177
+ data-slot="field-list-body"
178
+ className="flex min-h-0 flex-1 flex-col overflow-y-auto"
179
+ style={{
180
+ gap: 'var(--space-1)',
181
+ paddingInline: 'var(--space-4)',
182
+ paddingBlock: 'var(--space-3)',
183
+ }}
184
+ >
185
+ {children}
186
+ <p
187
+ className="ds-field-list-empty text-[12.5px] leading-[1.5]"
188
+ style={{ margin: 0, color: 'rgb(var(--text-tertiary))' }}
189
+ >
190
+ {emptyState}
191
+ </p>
192
+ </div>
193
+ </div>
194
+ </FieldListCtx.Provider>
195
+ )
196
+ }