@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.
- package/dist/chunk-RBYWGBQ2.js +2752 -0
- package/dist/chunk-RBYWGBQ2.js.map +1 -0
- package/dist/index.d.ts +5574 -57
- package/dist/index.js +21650 -11206
- package/dist/index.js.map +1 -1
- package/dist/rich-composer-impl-5NO443A6.js +1859 -0
- package/dist/rich-composer-impl-5NO443A6.js.map +1 -0
- package/dist/styles.css +1570 -0
- package/dist/tokens.css +112 -0
- package/package.json +8 -1
- package/src/__tests__/avatar.test.tsx +272 -0
- package/src/__tests__/bar-chart.test.tsx +809 -0
- package/src/__tests__/board.test.tsx +420 -0
- package/src/__tests__/chart-math.test.ts +922 -0
- package/src/__tests__/chart-series.test.ts +339 -0
- package/src/__tests__/code-block.test.tsx +134 -0
- package/src/__tests__/display-popover.test.tsx +195 -0
- package/src/__tests__/display-store.test.tsx +307 -0
- package/src/__tests__/donut-chart.test.tsx +397 -0
- package/src/__tests__/dropdown-menu.test.tsx +156 -2
- package/src/__tests__/filter-menu.test.tsx +175 -0
- package/src/__tests__/gauge-ring.test.tsx +233 -0
- package/src/__tests__/line-chart.test.tsx +612 -0
- package/src/__tests__/ranked-bars.test.tsx +343 -0
- package/src/__tests__/remark-underline.test.ts +194 -0
- package/src/__tests__/sortable.test.tsx +394 -0
- package/src/__tests__/sparkline.test.tsx +368 -0
- package/src/__tests__/stat-layer.test.tsx +271 -0
- package/src/__tests__/stat-strip.test.tsx +175 -0
- package/src/__tests__/status.test.tsx +293 -0
- package/src/__tests__/tabs.test.tsx +303 -0
- package/src/__tests__/token-shape.test.ts +132 -2
- package/src/avatar.tsx +352 -0
- package/src/bar-chart.tsx +1214 -0
- package/src/board.tsx +658 -0
- package/src/chart-frame.tsx +960 -0
- package/src/chart-legend.tsx +304 -0
- package/src/chart-tooltip.tsx +267 -0
- package/src/code-block.tsx +62 -8
- package/src/delta-chip.tsx +263 -0
- package/src/detail/__tests__/activity-pane.test.tsx +369 -0
- package/src/detail/__tests__/detail-chrome.test.tsx +134 -0
- package/src/detail/__tests__/detail-surface.test.tsx +529 -0
- package/src/detail/__tests__/field-row.test.tsx +357 -0
- package/src/detail/activity-pane.tsx +611 -0
- package/src/detail/calendar.tsx +355 -0
- package/src/detail/detail-divider.tsx +261 -0
- package/src/detail/detail-header.tsx +287 -0
- package/src/detail/detail-menu.tsx +254 -0
- package/src/detail/detail-surface.tsx +1110 -0
- package/src/detail/field-list.tsx +196 -0
- package/src/detail/field-row.tsx +1131 -0
- package/src/detail/index.ts +58 -0
- package/src/detail/segmented-choice.tsx +94 -0
- package/src/detail/types.ts +129 -0
- package/src/display-popover.tsx +487 -0
- package/src/display-store.tsx +301 -0
- package/src/donut-chart.tsx +988 -0
- package/src/dropdown-menu.tsx +290 -19
- package/src/filter-core/EXPORTS.md +85 -0
- package/src/filter-core/__tests__/columns.test.ts +159 -0
- package/src/filter-core/__tests__/faceting.test.ts +193 -0
- package/src/filter-core/__tests__/filter-fns.test.ts +519 -0
- package/src/filter-core/__tests__/operators.test.ts +235 -0
- package/src/filter-core/__tests__/state.test.ts +268 -0
- package/src/filter-core/__tests__/url.test.ts +350 -0
- package/src/filter-core/columns.ts +134 -0
- package/src/filter-core/date-utils.ts +38 -0
- package/src/filter-core/examples/task-filter-columns.ts +121 -0
- package/src/filter-core/faceting.ts +120 -0
- package/src/filter-core/filter-fns.ts +335 -0
- package/src/filter-core/index.ts +105 -0
- package/src/filter-core/operators.ts +433 -0
- package/src/filter-core/state.ts +280 -0
- package/src/filter-core/types.ts +247 -0
- package/src/filter-core/url.ts +261 -0
- package/src/filter-dropdown.tsx +12 -0
- package/src/filter-menu.tsx +649 -0
- package/src/floating-drawer.tsx +19 -1
- package/src/gauge-ring.tsx +435 -0
- package/src/hue.ts +52 -0
- package/src/index.ts +303 -0
- package/src/kbd.tsx +27 -4
- package/src/lib/chart.ts +866 -0
- package/src/lib/focus.ts +43 -1
- package/src/lib/layer-stack.ts +30 -3
- package/src/lib/remark-underline.ts +443 -0
- package/src/lib/series.ts +169 -0
- package/src/line-chart.tsx +1176 -0
- package/src/markdown.tsx +26 -7
- package/src/modal.tsx +42 -18
- package/src/progress-ledger.tsx +304 -0
- package/src/ranked-bars.tsx +386 -0
- package/src/segmented-pill.tsx +32 -9
- package/src/sortable.tsx +520 -1
- package/src/sparkline.tsx +416 -0
- package/src/stat-card.tsx +376 -0
- package/src/stat-strip.tsx +327 -0
- package/src/status.tsx +215 -0
- package/src/styles.css +1570 -0
- package/src/tabs.tsx +206 -25
- package/src/task-card.tsx +610 -0
- package/src/thread/__tests__/comment-body-hostile.test.tsx +331 -0
- package/src/thread/__tests__/comment-tree.test.ts +151 -0
- package/src/thread/__tests__/emoji.test.ts +187 -0
- package/src/thread/__tests__/fixtures/thread-fixture.ts +252 -0
- package/src/thread/__tests__/link-preview-source.test.ts +120 -0
- package/src/thread/__tests__/link-preview.test.tsx +600 -0
- package/src/thread/__tests__/markdown-format.test.ts +82 -0
- package/src/thread/__tests__/markdown-spec.test.ts +469 -0
- package/src/thread/__tests__/relative-time.test.ts +71 -0
- package/src/thread/__tests__/rich-composer.test.tsx +799 -0
- package/src/thread/__tests__/scroll-caret.test.ts +58 -0
- package/src/thread/__tests__/suggestion-list.test.tsx +648 -0
- package/src/thread/__tests__/thread-scroll-ownership.test.tsx +88 -0
- package/src/thread/__tests__/thread.test.tsx +742 -0
- package/src/thread/__tests__/use-attachments.test.tsx +679 -0
- package/src/thread/actions.tsx +196 -0
- package/src/thread/attachments.tsx +1071 -0
- package/src/thread/comment-body.tsx +148 -0
- package/src/thread/comment-tree.ts +182 -0
- package/src/thread/comment.tsx +967 -0
- package/src/thread/composer-footer.tsx +125 -0
- package/src/thread/composer.tsx +319 -0
- package/src/thread/emoji.ts +283 -0
- package/src/thread/index.ts +153 -0
- package/src/thread/link-preview.tsx +341 -0
- package/src/thread/markdown-format.ts +155 -0
- package/src/thread/markdown-spec.ts +754 -0
- package/src/thread/rail.tsx +372 -0
- package/src/thread/reactions.tsx +415 -0
- package/src/thread/relative-time.tsx +94 -0
- package/src/thread/rich-composer-impl.tsx +1601 -0
- package/src/thread/rich-composer.tsx +195 -0
- package/src/thread/scroll-caret.ts +37 -0
- package/src/thread/suggestion-list.tsx +182 -0
- package/src/thread/thread.tsx +718 -0
- package/src/thread/types.ts +232 -0
- package/src/thread/use-attachments.ts +598 -0
- package/src/thread/use-now.ts +73 -0
- package/src/thread/use-thread.ts +316 -0
- 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
|
+
}
|