@mediaradar/design-components 0.1.0 → 0.4.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/index.cjs +2531 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1137 -1
- package/dist/index.d.ts +1137 -1
- package/dist/index.js +2503 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
+
import { ReactNode, RefObject } from 'react';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @pattern InfoTip
|
|
@@ -35,4 +36,1139 @@ interface InfoTipProps {
|
|
|
35
36
|
}
|
|
36
37
|
declare function InfoTip({ text, size, label, trigger }: InfoTipProps): react.JSX.Element;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
/**
|
|
40
|
+
* @pattern AdvertiserQuickFilter
|
|
41
|
+
* @description Search-first quick filter. Mirrors the standard filter-chip
|
|
42
|
+
* shape (two-line caption + value + chevron) but never preloads the option
|
|
43
|
+
* list -- users must search first, since the advertiser pool is effectively
|
|
44
|
+
* endless. Selections are pinned in a "Selected (n)" section at the top so
|
|
45
|
+
* they're never lost when the user runs another search. Named for its
|
|
46
|
+
* original (and still primary) use, but the copy props make it reusable for
|
|
47
|
+
* any dimension that's also search-first in its own filter tray, so the
|
|
48
|
+
* dimension reads the same in the quick bar as it does there.
|
|
49
|
+
* @tokens rounded-control, rounded-card, border-border-default, border-border-strong,
|
|
50
|
+
* border-border-interactive, border-border-focus, bg-surface, bg-input,
|
|
51
|
+
* shadow-overlay, text-text-primary, text-text-secondary, text-text-tertiary,
|
|
52
|
+
* text-text-link, bg-brand-primaryAction, text-brand-onprimary,
|
|
53
|
+
* hover:bg-subtle, hover:text-text-primary, placeholder:text-text-tertiary
|
|
54
|
+
* @dependencies lucide-react
|
|
55
|
+
* @behavior The search input auto-focuses the moment the tray opens.
|
|
56
|
+
* Selections buffer in a local draft while open. Without `showFooter`,
|
|
57
|
+
* closing by any means (outside click, Escape, re-click) commits the draft
|
|
58
|
+
* to `onChange`. With `showFooter`, only the Apply button commits -- every
|
|
59
|
+
* other close path discards the draft, so the trigger label never previews
|
|
60
|
+
* an unapplied change. The pinned "selected" section persists across
|
|
61
|
+
* searches and collapses when the search is cleared.
|
|
62
|
+
* @usage
|
|
63
|
+
* ```tsx
|
|
64
|
+
* <AdvertiserQuickFilter options={advertisers} selected={selected} onChange={setSelected} />
|
|
65
|
+
* <AdvertiserQuickFilter options={advertisers} selected={selected} onChange={setSelected} showFooter clearValue={defaults} />
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
interface AdvertiserQuickFilterProps {
|
|
69
|
+
/** Small uppercase label above the value. */
|
|
70
|
+
caption?: string;
|
|
71
|
+
options: {
|
|
72
|
+
id: string;
|
|
73
|
+
label: string;
|
|
74
|
+
}[];
|
|
75
|
+
selected: string[];
|
|
76
|
+
onChange: (next: string[]) => void;
|
|
77
|
+
align?: "left" | "right";
|
|
78
|
+
/** Optional leading icon rendered before the caption/value stack. */
|
|
79
|
+
icon?: React.ReactNode;
|
|
80
|
+
/**
|
|
81
|
+
* Deferred mode: checkbox edits stage in a draft; only the fixed
|
|
82
|
+
* Apply/Clear All footer commits or discards them (no live commit on
|
|
83
|
+
* close). Defaults to false -- live commit-on-close -- so an existing
|
|
84
|
+
* caller's behavior never changes just by picking up a new version of
|
|
85
|
+
* this component.
|
|
86
|
+
*/
|
|
87
|
+
showFooter?: boolean;
|
|
88
|
+
/** What "Clear all" resets the draft to in footer mode (the page's
|
|
89
|
+
* default selection). */
|
|
90
|
+
clearValue?: string[];
|
|
91
|
+
/** Selected ids the active workspace can't resolve. They stay in the
|
|
92
|
+
* selection -- still committed back, still removable -- but they narrow
|
|
93
|
+
* nothing, so the trigger label leaves them out of the count. */
|
|
94
|
+
parked?: string[];
|
|
95
|
+
/** Search box placeholder. Override alongside the other copy props when
|
|
96
|
+
* the filter isn't advertisers, so the empty and no-results states name
|
|
97
|
+
* the right dimension. */
|
|
98
|
+
searchPlaceholder?: string;
|
|
99
|
+
/** Completes "Start typing … to filter." in the pre-search prompt. */
|
|
100
|
+
promptLabel?: string;
|
|
101
|
+
/** The actionable next step under a no-results message. */
|
|
102
|
+
noResultsHint?: string;
|
|
103
|
+
/** The clear link in footerless mode (unused when `showFooter` is set). */
|
|
104
|
+
clearLabel?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Optional prefix for `data-testid` attributes on the trigger, popover,
|
|
107
|
+
* search input, selected-section toggle, select-all-visible row, each
|
|
108
|
+
* option row (`${testIdPrefix}-option-<slugified-id>`), and the
|
|
109
|
+
* Apply/Clear All footer. Omit entirely for no test-id wiring at all --
|
|
110
|
+
* added specifically so an existing Playwright suite keyed off these ids
|
|
111
|
+
* (mr-clientapps' `AdvertisersPicker`) doesn't lose its locators when
|
|
112
|
+
* swapped over to this shared component.
|
|
113
|
+
*/
|
|
114
|
+
testIdPrefix?: string;
|
|
115
|
+
}
|
|
116
|
+
declare function AdvertiserQuickFilter({ caption, options, selected, onChange, align, icon, showFooter, clearValue, parked, searchPlaceholder, promptLabel, noResultsHint, clearLabel, testIdPrefix, }: AdvertiserQuickFilterProps): react.JSX.Element;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @pattern AdvertiserSearchPrompt
|
|
120
|
+
* @description Pre-search empty state for a search-first picker — shown the
|
|
121
|
+
* moment the tray opens, before anything has been typed. Used for option
|
|
122
|
+
* pools too large to preload (the advertiser list is effectively endless),
|
|
123
|
+
* where the caller must search before any options can be listed at all.
|
|
124
|
+
* Named for its original (and still primary) use, but the `label` prop makes
|
|
125
|
+
* it reusable for any other search-first dimension (a league picker, say).
|
|
126
|
+
* @tokens bg-subtle, text-text-tertiary
|
|
127
|
+
* @dependencies lucide-react
|
|
128
|
+
* @usage
|
|
129
|
+
* ```tsx
|
|
130
|
+
* <AdvertiserSearchPrompt />
|
|
131
|
+
* <AdvertiserSearchPrompt label="a league name" />
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
interface AdvertiserSearchPromptProps {
|
|
135
|
+
/** Completes "Start typing … to filter." */
|
|
136
|
+
label?: string;
|
|
137
|
+
}
|
|
138
|
+
declare function AdvertiserSearchPrompt({ label }: AdvertiserSearchPromptProps): react.JSX.Element;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @pattern CheckBox
|
|
142
|
+
* @description Tri-state checkbox rendered as a `<button>` (not a native
|
|
143
|
+
* `<input type="checkbox">`), so it can carry an indeterminate "some but not
|
|
144
|
+
* all" state without the native element's limited styling hooks. Designed to
|
|
145
|
+
* be dropped into a row that already has its own click-to-toggle handler
|
|
146
|
+
* (a common list/table pattern) without double-firing.
|
|
147
|
+
* @tokens bg-brand-primaryAction, border-border-interactive, bg-input,
|
|
148
|
+
* hover:border-brand-primaryAction, focus-visible:outline-border-focus
|
|
149
|
+
* @dependencies lucide-react
|
|
150
|
+
* @behavior `aria-checked` is `"mixed"` when indeterminate, otherwise the
|
|
151
|
+
* plain boolean. Indeterminate renders a dash (Minus) instead of a
|
|
152
|
+
* checkmark; checked renders a checkmark; neither renders nothing. The
|
|
153
|
+
* unchecked state previews the brand action color on hover, matching the
|
|
154
|
+
* color the box fills with once checked -- the hover state is a preview of
|
|
155
|
+
* the click's effect, not an unrelated highlight. `onClick` calls
|
|
156
|
+
* `stopPropagation` before firing `onChange`, so nesting this inside a row
|
|
157
|
+
* that toggles on its own click can't fire the toggle twice.
|
|
158
|
+
* @usage
|
|
159
|
+
* ```tsx
|
|
160
|
+
* <CheckBox checked={selected} ariaLabel="Select row" onChange={toggle} />
|
|
161
|
+
* <CheckBox checked={allSelected} indeterminate={someSelected} ariaLabel="Select all" onChange={toggleAll} />
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
interface CheckBoxProps {
|
|
165
|
+
checked: boolean;
|
|
166
|
+
/**
|
|
167
|
+
* Some-but-not-all state for a "select all" checkbox -- shows a dash
|
|
168
|
+
* instead of a check, and reports `aria-checked="mixed"` instead of the
|
|
169
|
+
* plain boolean.
|
|
170
|
+
*/
|
|
171
|
+
indeterminate?: boolean;
|
|
172
|
+
onChange: () => void;
|
|
173
|
+
/** Accessible label -- required since the control carries no visible text of its own. */
|
|
174
|
+
ariaLabel: string;
|
|
175
|
+
disabled?: boolean;
|
|
176
|
+
/** Forwarded as `data-testid`, for Playwright/test selectors. */
|
|
177
|
+
testId?: string;
|
|
178
|
+
}
|
|
179
|
+
declare function CheckBox({ checked, indeterminate, onChange, ariaLabel, disabled, testId }: CheckBoxProps): react.JSX.Element;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @pattern DateRangeFilter
|
|
183
|
+
* @description Date-range control -- the two-line chip (caption + value) with
|
|
184
|
+
* a chevron that opens a two-pane picker: preset shortcuts on the left, a
|
|
185
|
+
* dual-month calendar with range highlighting on the right, Start/End fields,
|
|
186
|
+
* and Cancel/Apply. Shared by Creative Intel and the Account Review.
|
|
187
|
+
* @tokens rounded-control, rounded-card, border-border-default, border-border-strong,
|
|
188
|
+
* bg-surface, bg-input, bg-subtle, bg-ai-tint, bg-brand-primaryAction,
|
|
189
|
+
* shadow-overlay, text-text-primary, text-text-secondary, text-text-tertiary,
|
|
190
|
+
* hover:bg-subtle, hover:text-text-primary, focus-visible:outline-border-focus
|
|
191
|
+
* @dependencies lucide-react
|
|
192
|
+
* @behavior Escape and outside-click both close the popup. Opening always
|
|
193
|
+
* resets the draft (shortcut/start/end/visible month) to the last committed
|
|
194
|
+
* values, so a previous unsaved edit never lingers into the next open. Only
|
|
195
|
+
* the Apply button commits -- Cancel, Escape, and outside-click all discard
|
|
196
|
+
* the draft silently.
|
|
197
|
+
* @usage
|
|
198
|
+
* ```tsx
|
|
199
|
+
* <DateRangeFilter caption="Date Range" defaultValue="Rolling 6 months" onChange={setRange} />
|
|
200
|
+
* <DateRangeFilter presetSet="creativeIntel" defaultValue="Last 7 days" onChange={setRange} />
|
|
201
|
+
* <DateRangeFilter presetSet="creativeIntel" value={preset} range={[start, end]} onChange={setPreset} onRangeChange={(s, e) => setRange({ start: s, end: e })} />
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
interface DateRangeFilterProps {
|
|
205
|
+
/** Static label shown above the value when `showDates` is false. */
|
|
206
|
+
caption?: string;
|
|
207
|
+
/** Initial preset (matched case-insensitively against the active preset set) or, if unmatched, treated as a custom label. Ignored once `value`/`range` are given -- see those. */
|
|
208
|
+
defaultValue?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Controlled mode: the parent owns the committed preset/range instead of
|
|
211
|
+
* this component tracking it internally. Give both `value` and `range`
|
|
212
|
+
* together to enable it -- omit both (the default) for the original
|
|
213
|
+
* uncontrolled behavior, where `defaultValue` seeds the initial state and
|
|
214
|
+
* `onChange`/`onRangeChange` are fire-and-forget notifications. In
|
|
215
|
+
* controlled mode the chip's displayed value/dates always reflect these
|
|
216
|
+
* props directly, so the parent must call `onChange`/`onRangeChange` and
|
|
217
|
+
* feed the result back for the chip to update after Apply -- the same
|
|
218
|
+
* contract as a controlled `<input value onChange>`.
|
|
219
|
+
*/
|
|
220
|
+
value?: string;
|
|
221
|
+
/** Controlled mode's resolved range -- see `value`. */
|
|
222
|
+
range?: [Date, Date];
|
|
223
|
+
/** Which edge of the trigger the popup aligns to. */
|
|
224
|
+
align?: "left" | "right";
|
|
225
|
+
/** Fired on Apply with the chip's new display value. */
|
|
226
|
+
onChange?: (value: string) => void;
|
|
227
|
+
/** Fired on Apply (and once on mount, uncontrolled only) with the resolved start/end dates -- e.g. to drive dependent table columns. */
|
|
228
|
+
onRangeChange?: (start: Date, end: Date) => void;
|
|
229
|
+
/** Show the resolved start-end dates in the chip instead of the preset name. */
|
|
230
|
+
showDates?: boolean;
|
|
231
|
+
/** Use an abbreviated date format (e.g. "Dec 1, 2025 - Jun 24, 2026") for the dates line. */
|
|
232
|
+
shortDates?: boolean;
|
|
233
|
+
/** Optional leading icon rendered before the caption/value stack. */
|
|
234
|
+
icon?: React.ReactNode;
|
|
235
|
+
/** 'creativeIntel' swaps in the day-level shortcut list (Today/Yesterday/Last N days/All available history) used by that dashboard. */
|
|
236
|
+
presetSet?: "default" | "creativeIntel";
|
|
237
|
+
/** Prefix for data-testid attributes on the trigger, popover, each preset option, and Cancel/Apply. Omit for no test-id wiring. */
|
|
238
|
+
testIdPrefix?: string;
|
|
239
|
+
}
|
|
240
|
+
declare function DateRangeFilter({ caption, defaultValue, value: controlledValue, range: controlledRange, align, onChange, onRangeChange, showDates, shortDates, icon, presetSet, testIdPrefix, }: DateRangeFilterProps): react.JSX.Element;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @pattern SearchBar
|
|
244
|
+
* @description Filter-bar keyword search. 'default' is a plain keyword
|
|
245
|
+
* input; 'filterable' adds an attached match-type dropdown sharing one
|
|
246
|
+
* bordered control, mirroring the legacy Creative Intel search + match-type
|
|
247
|
+
* modal (Any Word/Exact Match/Contextual/Ad Code), including the Or/And
|
|
248
|
+
* sub-choice nested under Any Word.
|
|
249
|
+
* @tokens rounded-control, border-border-strong, bg-input, text-text-primary,
|
|
250
|
+
* text-text-tertiary, hover:bg-subtle, bg-brand-primaryAction, bg-subtle,
|
|
251
|
+
* bg-surface, border-brand-primaryAction, border-border-default,
|
|
252
|
+
* border-border-interactive, focus-visible:outline-border-focus,
|
|
253
|
+
* hover:text-text-primary, placeholder:text-text-tertiary, rounded-card,
|
|
254
|
+
* rounded-full, shadow-overlay, text-brand-primaryAction, text-text-secondary
|
|
255
|
+
* @dependencies lucide-react
|
|
256
|
+
* @behavior The match-type dropdown ('filterable' variant) closes on Escape
|
|
257
|
+
* and outside click. Each mode row is a real `radio` role, with a nested
|
|
258
|
+
* Or/And sub-choice under Any Word and its own click-to-toggle info tooltip.
|
|
259
|
+
* The text input carries an `aria-label` derived from `placeholder` — the
|
|
260
|
+
* wrapping `<label>` has no visible text content (just an icon and the
|
|
261
|
+
* input), so it grants the input no accessible name on its own.
|
|
262
|
+
* @usage
|
|
263
|
+
* ```tsx
|
|
264
|
+
* <SearchBar value={query} onChange={setQuery} />
|
|
265
|
+
* <SearchBar variant="filterable" value={query} onChange={setQuery} matchValue={matchValue} onMatchChange={setMatchValue} />
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
type SearchBarVariant = "default" | "filterable";
|
|
269
|
+
type SearchMatchMode = "anyWord" | "exactMatch" | "contextual" | "adCode";
|
|
270
|
+
interface SearchMatchValue {
|
|
271
|
+
mode: SearchMatchMode;
|
|
272
|
+
/** Only meaningful when mode === 'anyWord'. */
|
|
273
|
+
wordJoin: "Or" | "And";
|
|
274
|
+
}
|
|
275
|
+
declare const DEFAULT_SEARCH_MATCH: SearchMatchValue;
|
|
276
|
+
interface SearchBarProps {
|
|
277
|
+
/** Input placeholder text. Also used as the input's `aria-label`. */
|
|
278
|
+
placeholder?: string;
|
|
279
|
+
value?: string;
|
|
280
|
+
onChange?: (v: string) => void;
|
|
281
|
+
/** 'default' is a plain keyword input; 'filterable' adds the match-type dropdown. */
|
|
282
|
+
variant?: SearchBarVariant;
|
|
283
|
+
/** The 'filterable' variant's dropdown state. */
|
|
284
|
+
matchValue?: SearchMatchValue;
|
|
285
|
+
onMatchChange?: (v: SearchMatchValue) => void;
|
|
286
|
+
className?: string;
|
|
287
|
+
/**
|
|
288
|
+
* Optional prefix for `data-testid` attributes on the input, the
|
|
289
|
+
* match-type trigger/popover, each mode row, and the Or/And radio pair
|
|
290
|
+
* (e.g. `${testIdPrefix}-search`, `${testIdPrefix}-mode-trigger`,
|
|
291
|
+
* `${testIdPrefix}-mode-anyWord`, `${testIdPrefix}-word-join-or`). Omit
|
|
292
|
+
* entirely for no test-id wiring at all -- added specifically so an
|
|
293
|
+
* existing Playwright suite keyed off these ids (mr-clientapps'
|
|
294
|
+
* `KeywordSearchBar`) doesn't lose its locators when swapped over to
|
|
295
|
+
* this shared component.
|
|
296
|
+
*/
|
|
297
|
+
testIdPrefix?: string;
|
|
298
|
+
}
|
|
299
|
+
declare function SearchBar({ placeholder, value, onChange, variant, matchValue, onMatchChange, className, testIdPrefix, }: SearchBarProps): react.JSX.Element;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @pattern SectionRail
|
|
303
|
+
* @description Generic titled section wrapper for a row of cards — a
|
|
304
|
+
* horizontal scroll rail on mobile/tablet (snap, bleeds to edges), a
|
|
305
|
+
* left-aligned 4-column grid on desktop. Card width is handled here, not on
|
|
306
|
+
* the card itself, so cards stay layout-agnostic; it works with any
|
|
307
|
+
* `children`. Real call sites vary: a fixed 4-item row that fills the
|
|
308
|
+
* desktop grid exactly, or a variable-length list with no "view all" link
|
|
309
|
+
* at all — both need to keep working without the row assuming a count.
|
|
310
|
+
* @tokens text-text-primary, text-text-secondary, text-text-link,
|
|
311
|
+
* rounded-control, rounded-sm, border-border, focus-visible:outline-solid,
|
|
312
|
+
* focus-visible:outline-border-focus, hover:bg-subtle, hover:text-text-primary,
|
|
313
|
+
* tracking-tight, font-serif
|
|
314
|
+
* @dependencies lucide-react
|
|
315
|
+
* @behavior Card width lives on the row, not the card: a fixed 268px per
|
|
316
|
+
* card in rail mode, capped at 320px per column in the lg+ grid, so cards
|
|
317
|
+
* stay layout-agnostic. The "view all" link only renders when both
|
|
318
|
+
* `viewAllLabel` and `viewAllHref` are given — either alone hides it, rather
|
|
319
|
+
* than falling back to a dead `href="#"`. Prev/next arrows are hidden below
|
|
320
|
+
* `sm` (swipe is the native affordance there) and at `lg`+ (the grid shows
|
|
321
|
+
* every card, nothing left to scroll to). `viewAllHref` renders as a plain
|
|
322
|
+
* anchor rather than a framework router Link — this package has no router
|
|
323
|
+
* peer dependency, and the two consuming apps disagree on one (react-router-dom
|
|
324
|
+
* vs next/link); a plain anchor is the one thing that works unmodified in both.
|
|
325
|
+
* @usage
|
|
326
|
+
* ```tsx
|
|
327
|
+
* <SectionRail title="Sports" subtitle="Your tracked leagues" viewAllLabel="View all" viewAllHref="/sports">
|
|
328
|
+
* {items.map((i) => <Card key={i.id}>{i.name}</Card>)}
|
|
329
|
+
* </SectionRail>
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
interface SectionRailProps {
|
|
333
|
+
/** Section heading. */
|
|
334
|
+
title: string;
|
|
335
|
+
/** Use the serif display treatment instead of the default sans heading. */
|
|
336
|
+
titleSerif?: boolean;
|
|
337
|
+
/** Supporting line under the title. */
|
|
338
|
+
subtitle: string;
|
|
339
|
+
/** Label for the "view all" link. Has no effect unless `viewAllHref` is also given. */
|
|
340
|
+
viewAllLabel?: string;
|
|
341
|
+
/** Route the "view all" link points to. Has no effect unless `viewAllLabel` is also given. */
|
|
342
|
+
viewAllHref?: string;
|
|
343
|
+
/** The row's cards. Any content works — width is handled by the row, not the card. */
|
|
344
|
+
children: React.ReactNode;
|
|
345
|
+
}
|
|
346
|
+
declare function SectionRail({ title, titleSerif, subtitle, viewAllLabel, viewAllHref, children, }: SectionRailProps): react.JSX.Element;
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @pattern DataScopeFilter
|
|
350
|
+
* @description "Data" scope chip -- toggles the filter bar between All data
|
|
351
|
+
* and Final-only data (data that's finalized across the selected media
|
|
352
|
+
* formats, for stable report-ready numbers). One boolean, one control,
|
|
353
|
+
* shared across every dashboard that has this data-finality concept
|
|
354
|
+
* (Publisher Intel, Opportunities, Sports).
|
|
355
|
+
* @tokens rounded-control, border-border-strong, bg-input, hover:bg-subtle,
|
|
356
|
+
* hover:text-text-primary, focus-visible:outline-border-focus, rounded-card,
|
|
357
|
+
* border-border-default, bg-surface, shadow-overlay, text-text-primary,
|
|
358
|
+
* text-text-secondary, text-text-tertiary, text-brand-primaryAction
|
|
359
|
+
* @dependencies lucide-react
|
|
360
|
+
* @behavior Escape and outside-click both close the popover. The "Final
|
|
361
|
+
* only" option's info tooltip is a real InfoTip in hover mode, rendered as a
|
|
362
|
+
* sibling of the option button rather than nested inside it -- InfoTip
|
|
363
|
+
* renders its own `<button>`, and a button nested inside another button is
|
|
364
|
+
* invalid HTML that breaks click targeting (confirmed against how
|
|
365
|
+
* mr-clientapps-insights-studio's own equivalent, `ChipOption`, already
|
|
366
|
+
* avoids exactly this by rendering its InfoTip as a sibling, not a child).
|
|
367
|
+
* @usage
|
|
368
|
+
* ```tsx
|
|
369
|
+
* <DataScopeFilter value={finalOnly} onChange={setFinalOnly} />
|
|
370
|
+
* ```
|
|
371
|
+
*/
|
|
372
|
+
interface DataScopeFilterProps {
|
|
373
|
+
value: boolean;
|
|
374
|
+
onChange: (v: boolean) => void;
|
|
375
|
+
/** Which edge of the trigger the popover aligns to. Both known consumers
|
|
376
|
+
* anchor this at the right edge of a filter bar, hence the default. */
|
|
377
|
+
align?: "left" | "right";
|
|
378
|
+
}
|
|
379
|
+
declare function DataScopeFilter({ value, onChange, align }: DataScopeFilterProps): react.JSX.Element;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* @pattern SelectFilter
|
|
383
|
+
* @description Single-select filter chip -- the caption-over-value trigger
|
|
384
|
+
* shape shared with AdvertiserQuickFilter/DateRangeFilter, opening a
|
|
385
|
+
* lightweight listbox of plain string options. Portal-rendered to
|
|
386
|
+
* `document.body` so the popover is never clipped by a scrollable ancestor
|
|
387
|
+
* (e.g. inside a modal) -- unlike this package's other chip popovers, none
|
|
388
|
+
* of which are known to sit inside a clipping container today.
|
|
389
|
+
* @tokens rounded-control, rounded-card, border-border-default,
|
|
390
|
+
* border-border-strong, bg-input, bg-surface, shadow-overlay,
|
|
391
|
+
* text-text-primary, text-text-secondary,
|
|
392
|
+
* text-text-tertiary, hover:bg-subtle, hover:text-text-primary,
|
|
393
|
+
* text-brand-primaryAction, focus-visible:outline-border-focus
|
|
394
|
+
* @dependencies lucide-react
|
|
395
|
+
* @behavior The popover is portal-rendered to document.body, so its own DOM
|
|
396
|
+
* node isn't inside the trigger. The outside-click check tests both the
|
|
397
|
+
* trigger and the popover explicitly for that reason -- treating the
|
|
398
|
+
* popover as "outside" would close it on every option click before
|
|
399
|
+
* `onChange` ever fires, so nothing would ever get selected. Selecting an
|
|
400
|
+
* option commits immediately and closes -- there's no separate draft/Apply
|
|
401
|
+
* step; Escape and outside-click just close without side effects.
|
|
402
|
+
* @usage
|
|
403
|
+
* ```tsx
|
|
404
|
+
* <SelectFilter caption="Rank" value={rankFilter} onChange={setRankFilter} options={['All', 'VP / Director', 'Manager', 'Coordinator']} />
|
|
405
|
+
* <SelectFilter caption="Group by" value={groupBy} onChange={setGroupBy} options={['Parent', 'Advertiser', 'Brand']} fullWidth testIdPrefix="dropdown-group-by" />
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
interface SelectFilterProps {
|
|
409
|
+
caption: string;
|
|
410
|
+
value: string;
|
|
411
|
+
options: string[];
|
|
412
|
+
onChange: (v: string) => void;
|
|
413
|
+
/** Which edge of the trigger the popover aligns to. */
|
|
414
|
+
align?: "left" | "right";
|
|
415
|
+
/** Stretch the trigger (and the popover, when true) to fill its container. */
|
|
416
|
+
fullWidth?: boolean;
|
|
417
|
+
/** Extra classes merged onto the trigger button -- e.g. a background
|
|
418
|
+
* override for a filter bar that isn't sitting on a filter-tray canvas. */
|
|
419
|
+
className?: string;
|
|
420
|
+
/**
|
|
421
|
+
* Optional prefix for `data-testid` attributes on the trigger
|
|
422
|
+
* (`${testIdPrefix}-trigger`), the popover (`${testIdPrefix}-popover`),
|
|
423
|
+
* and each option (`${testIdPrefix}-option-<slugified-label>`). Omit
|
|
424
|
+
* entirely for no test-id wiring at all -- added specifically so an
|
|
425
|
+
* existing Playwright suite keyed off hardcoded ids (mr-clientapps'
|
|
426
|
+
* `GroupBySelect`, e.g. `dropdown-group-by-option-parent`) can be re-keyed
|
|
427
|
+
* to this shared component's ids rather than losing its locators outright.
|
|
428
|
+
*/
|
|
429
|
+
testIdPrefix?: string;
|
|
430
|
+
}
|
|
431
|
+
declare function SelectFilter({ caption, value, options, onChange, align, fullWidth, className, testIdPrefix, }: SelectFilterProps): react.JSX.Element;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* @pattern Card
|
|
435
|
+
* @description Layout primitive for grouping content in a bordered, elevated
|
|
436
|
+
* surface. Width is controlled by the parent container, not the Card itself,
|
|
437
|
+
* so it works unchanged as a fixed-width rail item or a grid cell. Applies an
|
|
438
|
+
* interactive hover treatment (deeper shadow) unless `staticCard` is set. An
|
|
439
|
+
* optional `title` (+ `subtitle`/`action`) renders a standard header row and
|
|
440
|
+
* switches the card into a padded content layout, replacing the header/body
|
|
441
|
+
* shell mr-clientapps-insights-studio's own `CardShell` used to hand-roll.
|
|
442
|
+
* @tokens rounded-card, border-border-default, bg-surface, shadow-raised,
|
|
443
|
+
* hover:shadow-overlay, p-card-padding, text-text-primary, text-text-tertiary
|
|
444
|
+
* @behavior Without `title`, children render directly as the card's only
|
|
445
|
+
* content -- no padding is applied, so callers that need a full-bleed image
|
|
446
|
+
* or gradient flush against the card edge (the common case in
|
|
447
|
+
* insights-studio-prototype's rail/grid cards) stay in control of their own
|
|
448
|
+
* inner layout, exactly as before this component grew a `title` prop. With
|
|
449
|
+
* `title`, the header and children are wrapped together in the card's
|
|
450
|
+
* standard padding, matching `CardShell`'s layout so it can be swapped in
|
|
451
|
+
* directly. `staticCard` drops the hover-shadow transition for non-clickable
|
|
452
|
+
* content (a KPI tile, a read-only summary) -- there's nothing to respond to.
|
|
453
|
+
* @variant interactive - Default. Clickable/hoverable content -- deeper
|
|
454
|
+
* shadow on hover signals it responds to the cursor.
|
|
455
|
+
* @variant static - Set `staticCard`. Non-clickable content -- no hover
|
|
456
|
+
* treatment.
|
|
457
|
+
* @usage
|
|
458
|
+
* ```tsx
|
|
459
|
+
* <Card>
|
|
460
|
+
* <h3>Title</h3>
|
|
461
|
+
* <p>Body content</p>
|
|
462
|
+
* </Card>
|
|
463
|
+
* <Card staticCard>Non-interactive content</Card>
|
|
464
|
+
* <Card title="Spend trend" subtitle="Last 90 days" action={<Link to="/spend">View all</Link>}>
|
|
465
|
+
* <SpendChart />
|
|
466
|
+
* </Card>
|
|
467
|
+
* ```
|
|
468
|
+
*/
|
|
469
|
+
interface CardProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
|
|
470
|
+
/** Disable interactive hover treatment (when card isn't clickable). */
|
|
471
|
+
staticCard?: boolean;
|
|
472
|
+
/**
|
|
473
|
+
* Optional header title. Omit it (the default) to keep Card a bare,
|
|
474
|
+
* unpadded surface -- set it to get a title/subtitle/action header row
|
|
475
|
+
* plus the card's standard content padding, matching `CardShell`.
|
|
476
|
+
*/
|
|
477
|
+
title?: string;
|
|
478
|
+
/** Shown under `title` in a muted tertiary color. Ignored without `title`. */
|
|
479
|
+
subtitle?: string;
|
|
480
|
+
/** Trailing header content, e.g. a "View all" link. Ignored without `title`. */
|
|
481
|
+
action?: ReactNode;
|
|
482
|
+
}
|
|
483
|
+
declare const Card: react.ForwardRefExoticComponent<CardProps & react.RefAttributes<HTMLElement>>;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* The one "no results" message, shared across every filtered table/list that
|
|
487
|
+
* can end up empty for the same two reasons -- see the component doc below
|
|
488
|
+
* for why they're deliberately not distinguished.
|
|
489
|
+
*/
|
|
490
|
+
declare const NO_RESULTS_TITLE = "No results match your filters";
|
|
491
|
+
declare const NO_RESULTS_BODY = "Your filters may be too narrow, conflict with each other, or have no activity in this period.";
|
|
492
|
+
/**
|
|
493
|
+
* The body for the active workspace: named while one is active, since its scope
|
|
494
|
+
* bounds the result as much as the filters do. Under "All data" (no name) the
|
|
495
|
+
* generic line stands alone.
|
|
496
|
+
*/
|
|
497
|
+
declare function noResultsBody(workspaceName?: string | null): string;
|
|
498
|
+
/** One caller-supplied recovery action, rendered after the two named handlers. */
|
|
499
|
+
interface NoResultsAction {
|
|
500
|
+
label: string;
|
|
501
|
+
onClick: () => void;
|
|
502
|
+
/** Rendered as the filled button; every other action renders as the quiet link/button. */
|
|
503
|
+
primary?: boolean;
|
|
504
|
+
}
|
|
505
|
+
declare const PRIMARY_ACTION_CLASS = "rounded-control bg-brand-primaryAction px-3.5 py-2 text-[13px] font-semibold text-white transition-colors hover:opacity-90";
|
|
506
|
+
declare const QUIET_ACTION_CLASS = "rounded-control text-[13px] font-medium text-text-link transition-colors hover:underline focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-border-focus";
|
|
507
|
+
/**
|
|
508
|
+
* @pattern NoResultsEmptyState
|
|
509
|
+
* @description Guided empty state for a query that returned nothing.
|
|
510
|
+
* Deliberately one state for two causes, because the user can't tell them
|
|
511
|
+
* apart and the fix is the same either way -- loosen the filters or widen the
|
|
512
|
+
* date range: (1) a filter combination that can't match anything, or (2) a
|
|
513
|
+
* valid combination with no activity in the selected period. So the copy
|
|
514
|
+
* names both possibilities instead of asserting one. Renders as a bare
|
|
515
|
+
* stack -- no border, background, or fixed height -- so it can sit directly
|
|
516
|
+
* in a colSpan table cell (or a card, or a chart body) that already owns
|
|
517
|
+
* that chrome, without imposing its own.
|
|
518
|
+
* @tokens bg-subtle, text-text-tertiary, text-base, text-text-primary,
|
|
519
|
+
* text-text-secondary, rounded-control, bg-brand-primaryAction, text-text-link,
|
|
520
|
+
* focus-visible:outline-border-focus
|
|
521
|
+
* @dependencies lucide-react
|
|
522
|
+
* @behavior The Clear button shows when `onClearFilters` is given AND
|
|
523
|
+
* `hasFilters` isn't explicitly `false` -- so a caller can either pass the
|
|
524
|
+
* handler conditionally (only when there's something to clear) or always
|
|
525
|
+
* pass it and gate visibility with `hasFilters` separately; both existing
|
|
526
|
+
* call patterns work unchanged. The Edit button shows whenever
|
|
527
|
+
* `onEditFilters` is given. `actions` appends further caller-defined
|
|
528
|
+
* recovery buttons (e.g. "Switch workspace") after the two named ones, for
|
|
529
|
+
* empty states whose fix isn't about filters at all -- primary gets the
|
|
530
|
+
* filled treatment, everything else the quiet one.
|
|
531
|
+
* @usage
|
|
532
|
+
* ```tsx
|
|
533
|
+
* <NoResultsEmptyState hasFilters={hasFilters} onClearFilters={clearFilters} onEditFilters={openFilterTray} />
|
|
534
|
+
* <NoResultsEmptyState
|
|
535
|
+
* title="Properties not set up in this workspace"
|
|
536
|
+
* body="Switch to a workspace that has them defined or create a new one."
|
|
537
|
+
* actions={[{ label: 'Switch workspace', onClick: openSwitcher, primary: true }, { label: 'Create workspace', onClick: createNew }]}
|
|
538
|
+
* />
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
interface NoResultsEmptyStateProps {
|
|
542
|
+
/** @default NO_RESULTS_TITLE */
|
|
543
|
+
title?: string;
|
|
544
|
+
/** Replaces the default body wholesale, workspace name included. */
|
|
545
|
+
body?: string;
|
|
546
|
+
/** The active workspace's name, or nothing under "All data". Shapes the default body only -- ignored when `body` is given. */
|
|
547
|
+
workspaceName?: string | null;
|
|
548
|
+
/**
|
|
549
|
+
* Explicit override for whether the Clear button can show. Omit to derive
|
|
550
|
+
* it from whether `onClearFilters` was passed; pass `false` to force it
|
|
551
|
+
* hidden even though a handler was given (e.g. the handler exists but
|
|
552
|
+
* nothing is currently applied).
|
|
553
|
+
*/
|
|
554
|
+
hasFilters?: boolean;
|
|
555
|
+
onClearFilters?: () => void;
|
|
556
|
+
onEditFilters?: () => void;
|
|
557
|
+
/** Extra recovery actions, rendered in order after Clear/Edit. */
|
|
558
|
+
actions?: NoResultsAction[];
|
|
559
|
+
/**
|
|
560
|
+
* Optional prefix for `data-testid` attributes on the container
|
|
561
|
+
* (`testIdPrefix` itself), the Clear button (`${testIdPrefix}-clear`), and
|
|
562
|
+
* the Edit button (`${testIdPrefix}-edit`). Omit for no test-id wiring.
|
|
563
|
+
* Pass `"no-results"` to reproduce mr-clientapps-insights-studio's current
|
|
564
|
+
* fixed ids (`no-results`, `no-results-clear`, `no-results-edit`) exactly.
|
|
565
|
+
*/
|
|
566
|
+
testIdPrefix?: string;
|
|
567
|
+
}
|
|
568
|
+
declare function NoResultsEmptyState({ title, body, workspaceName, hasFilters, onClearFilters, onEditFilters, actions, testIdPrefix, }: NoResultsEmptyStateProps): react.JSX.Element;
|
|
569
|
+
|
|
570
|
+
interface SummaryGroupItemValue {
|
|
571
|
+
id: string;
|
|
572
|
+
name: string;
|
|
573
|
+
/**
|
|
574
|
+
* Secondary line under the name. Takes a node rather than a plain string
|
|
575
|
+
* so a caller can render something richer than static text there -- e.g.
|
|
576
|
+
* mr-clientapps-insights-studio's Account Lists group swaps this for a
|
|
577
|
+
* "No accounts assigned -- click here to add accounts" link when a list is
|
|
578
|
+
* empty, which a plain `string` field couldn't hold.
|
|
579
|
+
*/
|
|
580
|
+
meta?: React.ReactNode;
|
|
581
|
+
}
|
|
582
|
+
interface SummaryGroup {
|
|
583
|
+
id: string;
|
|
584
|
+
label: string;
|
|
585
|
+
items: SummaryGroupItemValue[];
|
|
586
|
+
onRemove: (id: string) => void;
|
|
587
|
+
onRemoveAll: () => void;
|
|
588
|
+
/** Nested sub-sections rendered, indented, beneath this group's own items. */
|
|
589
|
+
subGroups?: SummaryGroup[];
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* @pattern WorkspaceSelectedCard
|
|
593
|
+
* @description Sticky recap card listing what each section's picker has
|
|
594
|
+
* added, as collapsible groups with per-item remove and "Remove all" --
|
|
595
|
+
* generic over the `SummaryGroup` shape, no domain coupling. Reconciled from
|
|
596
|
+
* two implementations: insights-studio-prototype's generic `SummaryGroup[]`
|
|
597
|
+
* (kept, since it cleanly covers mr-clientapps-insights-studio's own usage
|
|
598
|
+
* too -- that app's fixed `sets`/`accountLists` prop pair is just this same
|
|
599
|
+
* shape with the group list hardcoded at the call site instead of passed
|
|
600
|
+
* in) and mr-clientapps-insights-studio's `SelectedSummary`, whose
|
|
601
|
+
* `wasEmpty`-ref auto-expand tracking turned out to be the correct one (see
|
|
602
|
+
* `@behavior`) and whose `shadow-1` -- not a real `@mediaradar/design-tokens`
|
|
603
|
+
* class, confirmed against the built theme -- pointed at a real, deliberately
|
|
604
|
+
* omitted elevation this card was missing; the local `shadow-1`/`shadow-2`
|
|
605
|
+
* naming maps to this package's `shadow-raised`/`shadow-overlay` respectively
|
|
606
|
+
* (raised: a static card like this one; overlay: a floating popover/menu),
|
|
607
|
+
* not a 1:1 name swap.
|
|
608
|
+
* @tokens rounded-card, border-border, bg-surface, shadow-raised,
|
|
609
|
+
* text-text-secondary, text-text-primary, rounded-full, bg-subtle,
|
|
610
|
+
* rounded-control, bg-canvas, hover:bg-subtle, hover:text-text-danger,
|
|
611
|
+
* text-text-link, divide-border
|
|
612
|
+
* @dependencies lucide-react
|
|
613
|
+
* @behavior A group auto-expands only on the empty-to-nonempty transition
|
|
614
|
+
* (including via a nested sub-group's items) and auto-collapses when
|
|
615
|
+
* emptied; manual open/close toggles in between are preserved. This is
|
|
616
|
+
* mr-clientapps-insights-studio's `wasEmpty`-ref logic, not
|
|
617
|
+
* insights-studio-prototype's original `prevCount`-comparison one --
|
|
618
|
+
* verified the two differ: the original re-opened a group on ANY item
|
|
619
|
+
* increase, including one added to an already-open or already
|
|
620
|
+
* manually-collapsed nonempty group, silently undoing the user's own
|
|
621
|
+
* collapse. Sub-groups render indented beneath their parent's own items.
|
|
622
|
+
* @usage
|
|
623
|
+
* ```tsx
|
|
624
|
+
* <WorkspaceSelectedCard groups={[{ id: 'advertisers', label: 'Advertisers', items: selected, onRemove: removeOne, onRemoveAll: clearAll }]} />
|
|
625
|
+
* ```
|
|
626
|
+
*/
|
|
627
|
+
interface WorkspaceSelectedCardProps {
|
|
628
|
+
groups: SummaryGroup[];
|
|
629
|
+
/**
|
|
630
|
+
* Optional prefix for `data-testid` attributes on each group's toggle
|
|
631
|
+
* (`${testIdPrefix}-group-<group.id>`) and its "Remove all" button
|
|
632
|
+
* (`${testIdPrefix}-remove-all-<group.id>`), applied recursively to
|
|
633
|
+
* sub-groups too. Omit for no test-id wiring.
|
|
634
|
+
*
|
|
635
|
+
* NOTE: this does not reproduce mr-clientapps-insights-studio's current
|
|
636
|
+
* fixed ids byte-for-byte -- its container carries a separate, unrelated
|
|
637
|
+
* `workspace-selected-summary` id (this component has no single container
|
|
638
|
+
* element those groups sit inside, by design, so there's nothing to attach
|
|
639
|
+
* an equivalent id to) while its groups use a *different* prefix,
|
|
640
|
+
* `workspace-summary-group-<type>` / `workspace-summary-remove-all-<type>`.
|
|
641
|
+
* A consumer with existing Playwright locators keyed off those exact
|
|
642
|
+
* strings needs to update them, or the caller needs its own id mapping --
|
|
643
|
+
* not attempted here, same as `DateRangeFilter`'s documented preset-slug
|
|
644
|
+
* mismatch.
|
|
645
|
+
*/
|
|
646
|
+
testIdPrefix?: string;
|
|
647
|
+
}
|
|
648
|
+
declare function WorkspaceSelectedCard({ groups, testIdPrefix }: WorkspaceSelectedCardProps): react.JSX.Element;
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* @pattern BarChartPlaceholder
|
|
652
|
+
* @description Neutral, dataless stand-in for a ranked-bar chart --
|
|
653
|
+
* descending bar profile under a blank-labelled axis frame (dashed
|
|
654
|
+
* gridlines, blank y/x-tick boxes), so an unsatisfiable filter still reads
|
|
655
|
+
* as "a chart belongs here" rather than a failed load. No real value ever
|
|
656
|
+
* appears (no channel/league names, media types, dollar amounts,
|
|
657
|
+
* percentages, or series colors), and the shape holds the card's layout
|
|
658
|
+
* steady so nothing jumps when real data arrives.
|
|
659
|
+
* @tokens bg-subtle, border-border-default, rounded-xs
|
|
660
|
+
* @behavior Purely presentational -- `role="presentation"` and
|
|
661
|
+
* `aria-hidden="true"` throughout, since none of the boxes carry real
|
|
662
|
+
* content for assistive tech to announce. The bar count and profile are a
|
|
663
|
+
* fixed shape, not driven by `height`.
|
|
664
|
+
* @usage
|
|
665
|
+
* ```tsx
|
|
666
|
+
* <BarChartPlaceholder />
|
|
667
|
+
* <BarChartPlaceholder height={180} />
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
interface BarChartPlaceholderProps {
|
|
671
|
+
/** Chart height in pixels. */
|
|
672
|
+
height?: number;
|
|
673
|
+
}
|
|
674
|
+
declare function BarChartPlaceholder({ height }: BarChartPlaceholderProps): react.JSX.Element;
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* @pattern LineChartPlaceholder
|
|
678
|
+
* @description Neutral, dataless stand-in for a multi-series trend chart --
|
|
679
|
+
* four grey paths over the shared axis frame, each with a solid-then-dashed
|
|
680
|
+
* handoff for the live chart's forecast tail, and a point at every sample.
|
|
681
|
+
* Points are positioned rather than drawn in the SVG: the plot stretches to
|
|
682
|
+
* the card, and a stretched `<circle>` would go oval. Same no-results use
|
|
683
|
+
* as `BarChartPlaceholder` -- no real value ever appears, and the shape
|
|
684
|
+
* holds the card's layout steady so nothing jumps when real data arrives.
|
|
685
|
+
* @tokens border-border-strong, bg-subtle, border-border-default, rounded-xs
|
|
686
|
+
* @behavior Purely presentational -- `role="presentation"` and
|
|
687
|
+
* `aria-hidden="true"` throughout (inherited from `AxisFrame`). The series
|
|
688
|
+
* count and shapes are fixed; `height` is the only variable.
|
|
689
|
+
* @usage
|
|
690
|
+
* ```tsx
|
|
691
|
+
* <LineChartPlaceholder />
|
|
692
|
+
* <LineChartPlaceholder height={200} />
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
interface LineChartPlaceholderProps {
|
|
696
|
+
/** Chart height in pixels. */
|
|
697
|
+
height?: number;
|
|
698
|
+
}
|
|
699
|
+
declare function LineChartPlaceholder({ height }: LineChartPlaceholderProps): react.JSX.Element;
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* @pattern PieChartPlaceholder
|
|
703
|
+
* @description Neutral, dataless stand-in for a pie chart -- a grey disc
|
|
704
|
+
* seamed where slices would fall, beside a blank legend. No axis, matching
|
|
705
|
+
* the live pie it stands in for. Same no-results use as
|
|
706
|
+
* `BarChartPlaceholder`: an unsatisfiable filter reads as "a chart belongs
|
|
707
|
+
* here" rather than a failed load, with no real value (names, media types,
|
|
708
|
+
* dollar amounts, percentages, series colors) ever appearing.
|
|
709
|
+
* @tokens bg-subtle, border-border-default, rounded-xs
|
|
710
|
+
* @behavior Purely presentational -- `role="presentation"` and
|
|
711
|
+
* `aria-hidden="true"` throughout. `legendRows` controls the blank legend
|
|
712
|
+
* only; the disc's seam count is a fixed shape.
|
|
713
|
+
* @usage
|
|
714
|
+
* ```tsx
|
|
715
|
+
* <PieChartPlaceholder />
|
|
716
|
+
* <PieChartPlaceholder legendRows={4} />
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
interface PieChartPlaceholderProps {
|
|
720
|
+
/** Chart height in pixels. */
|
|
721
|
+
height?: number;
|
|
722
|
+
/** Number of blank legend rows to render beside the disc. */
|
|
723
|
+
legendRows?: number;
|
|
724
|
+
}
|
|
725
|
+
declare function PieChartPlaceholder({ height, legendRows }: PieChartPlaceholderProps): react.JSX.Element;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* @pattern StackedColumnPlaceholder
|
|
729
|
+
* @description Neutral, dataless stand-in for a 100% stacked column -- a
|
|
730
|
+
* single banded column beside a blank legend. `axis` follows the live card
|
|
731
|
+
* it stands in for: a share-of-total chart plotting on a 0-100% axis passes
|
|
732
|
+
* `axis`, a bare stack with no scale (e.g. a share-of-voice column) omits
|
|
733
|
+
* it. Same no-results use as `BarChartPlaceholder` -- no real value ever
|
|
734
|
+
* appears, and the shape holds the card's layout steady.
|
|
735
|
+
* @tokens bg-subtle, border-border-strong, border-border-default, rounded-control
|
|
736
|
+
* @behavior Purely presentational -- `role="presentation"` and
|
|
737
|
+
* `aria-hidden="true"` throughout. Band seams use `border-border-strong`
|
|
738
|
+
* rather than the softer `border-border-default` gridline color: on a
|
|
739
|
+
* `bg-subtle` fill the default-strength seam disappears and the stack reads
|
|
740
|
+
* as one undivided block. Legend rows are reversed (bottom-up) when `axis`
|
|
741
|
+
* is off, matching a bare stack's usual bottom-anchored reading order.
|
|
742
|
+
* @usage
|
|
743
|
+
* ```tsx
|
|
744
|
+
* <StackedColumnPlaceholder />
|
|
745
|
+
* <StackedColumnPlaceholder axis />
|
|
746
|
+
* ```
|
|
747
|
+
*/
|
|
748
|
+
interface StackedColumnPlaceholderProps {
|
|
749
|
+
/** Chart height in pixels. */
|
|
750
|
+
height?: number;
|
|
751
|
+
/** Column width in pixels. */
|
|
752
|
+
width?: number;
|
|
753
|
+
/** Number of bands (and matching blank legend rows). */
|
|
754
|
+
legendRows?: number;
|
|
755
|
+
/** Wrap the column in a 0-100% axis frame, matching a live share-of-total chart. */
|
|
756
|
+
axis?: boolean;
|
|
757
|
+
}
|
|
758
|
+
declare function StackedColumnPlaceholder({ height, width, legendRows, axis, }: StackedColumnPlaceholderProps): react.JSX.Element;
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* @pattern Tooltip
|
|
762
|
+
* @description Portalled hover/focus tooltip. Two ways to attach a trigger:
|
|
763
|
+
* wrap `children` (the default -- an inline trigger, e.g. a truncated table
|
|
764
|
+
* cell or a chart legend entry), or pass `anchorRef` for an element that
|
|
765
|
+
* already needs its own ref, or where wrapping in an extra inline box would
|
|
766
|
+
* disturb layout (an icon-only collapsed nav rail, for example) -- the two
|
|
767
|
+
* are mutually exclusive ways of specifying the trigger, and `children` is
|
|
768
|
+
* ignored when `anchorRef` is set. Positioned `fixed` from the trigger's
|
|
769
|
+
* bounding rect and rendered through a portal to `document.body`, so it
|
|
770
|
+
* escapes both `overflow` clipping inside scrollable panels and the broken
|
|
771
|
+
* `fixed` containing block a transformed ancestor creates.
|
|
772
|
+
* @tokens rounded-control, border-border-default, bg-surface, shadow-overlay,
|
|
773
|
+
* text-text-primary, bg-text-primary, text-surface
|
|
774
|
+
* @variant surface - Default. Themed surface/border bubble -- matches the
|
|
775
|
+
* Recharts chart tooltips this was originally built for.
|
|
776
|
+
* @variant inverted - Solid dark pill (no border) -- for a tooltip sitting
|
|
777
|
+
* over a nav rail or other icon-only surface, where the themed surface bubble
|
|
778
|
+
* doesn't read as clearly against a busy background.
|
|
779
|
+
* @behavior Shows on hover and on keyboard focus (so it stays reachable
|
|
780
|
+
* without a pointer) after `delay` ms (0 = immediate, the default). Hides
|
|
781
|
+
* immediately on mouseleave/blur, cancelling a pending delayed show if it
|
|
782
|
+
* hasn't fired yet -- except in `interactive` mode, which delays the hide
|
|
783
|
+
* briefly so the pointer can travel from the trigger into the tooltip's own
|
|
784
|
+
* content. Pass `whenTruncated` to only surface it once the trigger's content
|
|
785
|
+
* is actually clipped by ellipsis (`scrollWidth > clientWidth`). `placement`
|
|
786
|
+
* 'top'/'bottom' centre (or `align="start"` left-align) on the trigger;
|
|
787
|
+
* 'right' centres vertically beside the anchor and 'bottom-end' hangs below,
|
|
788
|
+
* right-aligned to the anchor's right edge -- both meant for `anchorRef`
|
|
789
|
+
* anchors near the viewport's right edge, where a centred box would run
|
|
790
|
+
* off-screen or read awkwardly beside a narrow icon.
|
|
791
|
+
* @usage
|
|
792
|
+
* ```tsx
|
|
793
|
+
* <Tooltip label="Full advertiser name" whenTruncated>
|
|
794
|
+
* <span className="truncate">{name}</span>
|
|
795
|
+
* </Tooltip>
|
|
796
|
+
*
|
|
797
|
+
* const ref = useRef<HTMLButtonElement>(null);
|
|
798
|
+
* <button ref={ref}>Sports</button>
|
|
799
|
+
* <Tooltip label="Sports" anchorRef={ref} placement="right" delay={500} variant="inverted" />
|
|
800
|
+
* ```
|
|
801
|
+
*/
|
|
802
|
+
interface TooltipProps {
|
|
803
|
+
/** Tooltip content shown on hover/focus. */
|
|
804
|
+
label: ReactNode;
|
|
805
|
+
/** The trigger content, wrapped inline. Omit when using `anchorRef`. */
|
|
806
|
+
children?: ReactNode;
|
|
807
|
+
/**
|
|
808
|
+
* Attach to an existing element instead of wrapping `children` in a
|
|
809
|
+
* trigger span -- see the pattern description. When set, `children` /
|
|
810
|
+
* `className` / `whenTruncated` all still apply to that element (its
|
|
811
|
+
* `scrollWidth`/`clientWidth` drive `whenTruncated`), just without this
|
|
812
|
+
* component rendering a wrapping element of its own.
|
|
813
|
+
*/
|
|
814
|
+
anchorRef?: RefObject<HTMLElement | null>;
|
|
815
|
+
/** Classes for the inline trigger wrapper (e.g. `truncate`, `min-w-0`). Ignored with `anchorRef`. */
|
|
816
|
+
className?: string;
|
|
817
|
+
/**
|
|
818
|
+
* Only reveal the tooltip when the trigger's content is actually clipped by
|
|
819
|
+
* ellipsis (`scrollWidth > clientWidth`). Off by default -- the tooltip always
|
|
820
|
+
* shows.
|
|
821
|
+
*/
|
|
822
|
+
whenTruncated?: boolean;
|
|
823
|
+
/**
|
|
824
|
+
* When true, the tooltip accepts pointer events so it can contain links and
|
|
825
|
+
* other interactive content.
|
|
826
|
+
*/
|
|
827
|
+
interactive?: boolean;
|
|
828
|
+
/**
|
|
829
|
+
* Which side of the trigger to render on. 'top' (default) / 'bottom'
|
|
830
|
+
* centre on the trigger (or left-align with `align="start"`). 'right' /
|
|
831
|
+
* 'bottom-end' are for an `anchorRef` anchor -- see the pattern description.
|
|
832
|
+
*/
|
|
833
|
+
placement?: "top" | "bottom" | "right" | "bottom-end";
|
|
834
|
+
/**
|
|
835
|
+
* How the box lines up with the trigger horizontally, for 'top'/'bottom'
|
|
836
|
+
* placement only. "center" (the default) centres it; "start" puts its left
|
|
837
|
+
* edge on the trigger so it grows rightward, which reads better for a small
|
|
838
|
+
* icon sitting beside the text it describes -- a centred box there hangs
|
|
839
|
+
* out over the content to the left.
|
|
840
|
+
*/
|
|
841
|
+
align?: "center" | "start";
|
|
842
|
+
/**
|
|
843
|
+
* Hover delay in ms before the tooltip appears. 0 (default) shows
|
|
844
|
+
* immediately. A collapsed nav rail passes 500 so a fast mouse pass over
|
|
845
|
+
* several icons doesn't flash a tooltip per icon.
|
|
846
|
+
*/
|
|
847
|
+
delay?: number;
|
|
848
|
+
/** 'surface' (default) or 'inverted' -- see the pattern description. */
|
|
849
|
+
variant?: "surface" | "inverted";
|
|
850
|
+
/** Optional prefix for a `data-testid` on the floating tooltip bubble. */
|
|
851
|
+
testIdPrefix?: string;
|
|
852
|
+
}
|
|
853
|
+
declare function Tooltip({ label, children, anchorRef, className, whenTruncated, interactive, placement, align, delay, variant, testIdPrefix, }: TooltipProps): react.JSX.Element | null;
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* @pattern AdvertiserNoResults
|
|
857
|
+
* @description Shown when a search-first picker's search returns nothing --
|
|
858
|
+
* gives an actionable next step rather than just a bare "no results"
|
|
859
|
+
* message. Named for its original (and still primary) use across
|
|
860
|
+
* `insights-studio-prototype`'s three advertiser-search filter trays
|
|
861
|
+
* (Creative Intel, Publisher, Sports), but the `hint` prop makes it reusable
|
|
862
|
+
* for any other search-first dimension. Distinct from the more general
|
|
863
|
+
* `NoResultsEmptyState` (a whole-board "no results match your filters"
|
|
864
|
+
* state) -- this one is scoped to a single search box returning nothing,
|
|
865
|
+
* inline within a picker's option list.
|
|
866
|
+
* @tokens text-text-primary, text-text-tertiary
|
|
867
|
+
* @usage
|
|
868
|
+
* ```tsx
|
|
869
|
+
* <AdvertiserNoResults query={query} />
|
|
870
|
+
* <AdvertiserNoResults query={query} hint="Try a different spelling, or switch the Agency Type." />
|
|
871
|
+
* ```
|
|
872
|
+
*/
|
|
873
|
+
interface AdvertiserNoResultsProps {
|
|
874
|
+
/** The search term that returned nothing. */
|
|
875
|
+
query: string;
|
|
876
|
+
/** The actionable next step under the message. */
|
|
877
|
+
hint?: string;
|
|
878
|
+
}
|
|
879
|
+
declare function AdvertiserNoResults({ query, hint, }: AdvertiserNoResultsProps): react.JSX.Element;
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* @pattern SelectAllVisible
|
|
883
|
+
* @description "Select all visible" row for a search-first picker's result
|
|
884
|
+
* list -- only render it once the caller decides the visible result set is
|
|
885
|
+
* long enough to warrant it (`insights-studio-prototype`'s three advertiser-
|
|
886
|
+
* search filter trays all gate it behind a 5-result threshold; mr-clientapps'
|
|
887
|
+
* `ScopedSearchSection` uses the identical threshold and copy for its own
|
|
888
|
+
* scoped search sections). Communicates how many are visible and toggles
|
|
889
|
+
* between "Select all" and "Deselect" based on `allSelected`.
|
|
890
|
+
* @tokens rounded-control, text-text-link, hover:bg-subtle
|
|
891
|
+
* @dependencies lucide-react
|
|
892
|
+
* @usage
|
|
893
|
+
* ```tsx
|
|
894
|
+
* {results.length > 5 && (
|
|
895
|
+
* <SelectAllVisible count={results.length} allSelected={allSelected} onToggle={toggleAll} />
|
|
896
|
+
* )}
|
|
897
|
+
* ```
|
|
898
|
+
*/
|
|
899
|
+
interface SelectAllVisibleProps {
|
|
900
|
+
/** Number of currently visible results. */
|
|
901
|
+
count: number;
|
|
902
|
+
/** Whether every visible result is already selected. */
|
|
903
|
+
allSelected: boolean;
|
|
904
|
+
onToggle: () => void;
|
|
905
|
+
/** Optional prefix for a `data-testid` on the row. */
|
|
906
|
+
testIdPrefix?: string;
|
|
907
|
+
}
|
|
908
|
+
declare function SelectAllVisible({ count, allSelected, onToggle, testIdPrefix }: SelectAllVisibleProps): react.JSX.Element;
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* @pattern SelectedSection
|
|
912
|
+
* @description Collapsible "Selected (n)" container for a search-first
|
|
913
|
+
* picker. Selections can pile up and crowd out the live search results, so
|
|
914
|
+
* the user can fold them away to refocus on the search. Controlled so the
|
|
915
|
+
* parent can auto-collapse it (e.g. when the search is cleared, or jointly
|
|
916
|
+
* with a sibling "parked" section) -- `insights-studio-prototype`'s three
|
|
917
|
+
* advertiser-search filter trays all drive `open` this way. The caller
|
|
918
|
+
* renders each selected row itself as `children` rather than this component
|
|
919
|
+
* owning row markup: the three real call sites render genuinely different
|
|
920
|
+
* row content (plain rows, opacity-dimmed "parked" rows, per-tray custom
|
|
921
|
+
* columns), so a fixed row shape would have forced a compromise none of them
|
|
922
|
+
* need. Self-hides at `count === 0` so a caller can render it unconditionally.
|
|
923
|
+
* @tokens rounded-control, text-text-tertiary, hover:text-text-primary
|
|
924
|
+
* @dependencies lucide-react
|
|
925
|
+
* @usage
|
|
926
|
+
* ```tsx
|
|
927
|
+
* <SelectedSection count={selected.length} open={open} onToggle={() => setOpen((o) => !o)}>
|
|
928
|
+
* {selected.map((s) => <Row key={s.id} item={s} />)}
|
|
929
|
+
* </SelectedSection>
|
|
930
|
+
* ```
|
|
931
|
+
*/
|
|
932
|
+
interface SelectedSectionProps {
|
|
933
|
+
count: number;
|
|
934
|
+
open: boolean;
|
|
935
|
+
onToggle: () => void;
|
|
936
|
+
/** The caller renders each selected row itself. */
|
|
937
|
+
children: React.ReactNode;
|
|
938
|
+
/** Optional prefix for a `data-testid` on the toggle button. */
|
|
939
|
+
testIdPrefix?: string;
|
|
940
|
+
}
|
|
941
|
+
declare function SelectedSection({ count, open, onToggle, children, testIdPrefix }: SelectedSectionProps): react.JSX.Element | null;
|
|
942
|
+
|
|
943
|
+
interface PickerRow {
|
|
944
|
+
id: string;
|
|
945
|
+
name: string;
|
|
946
|
+
children?: PickerRow[];
|
|
947
|
+
}
|
|
948
|
+
interface PickerColumn<T extends PickerRow> {
|
|
949
|
+
key: string;
|
|
950
|
+
header: string;
|
|
951
|
+
render: (row: T) => ReactNode;
|
|
952
|
+
className?: string;
|
|
953
|
+
}
|
|
954
|
+
interface PickerFilter<T extends PickerRow> {
|
|
955
|
+
label: string;
|
|
956
|
+
options: string[];
|
|
957
|
+
/** Returns the row's value for this filter (matched against options). */
|
|
958
|
+
value: (row: T) => string | undefined;
|
|
959
|
+
}
|
|
960
|
+
/** The subset of an account-list record this component actually renders in
|
|
961
|
+
* its "Account lists" tab -- a caller's own richer type (with members,
|
|
962
|
+
* createdBy, etc.) is passed in as-is; only these fields are read. */
|
|
963
|
+
interface SelectionPickerAccountList {
|
|
964
|
+
id: string;
|
|
965
|
+
name: string;
|
|
966
|
+
/** Number of accounts currently in the list. */
|
|
967
|
+
count: number;
|
|
968
|
+
/** Entity types represented in the list, for a quick descriptive line. */
|
|
969
|
+
entityTypes: string[];
|
|
970
|
+
updatedAt: string;
|
|
971
|
+
}
|
|
972
|
+
interface SelectionPickerProps<T extends PickerRow> {
|
|
973
|
+
/** Full catalog to search/filter over client-side. Ignored when `results`
|
|
974
|
+
* is given instead -- that's the async/paginated mode, see `results`. */
|
|
975
|
+
catalog?: T[];
|
|
976
|
+
selected: T[];
|
|
977
|
+
onChange: (next: T[]) => void;
|
|
978
|
+
columns: PickerColumn<T>[];
|
|
979
|
+
/** First filter is rendered as the primary entity-type group. */
|
|
980
|
+
filters: PickerFilter<T>[];
|
|
981
|
+
searchPlaceholder?: string;
|
|
982
|
+
/** Optional mock drill-down: returns child rows to reveal under a row. */
|
|
983
|
+
getChildren?: (row: T) => T[] | undefined;
|
|
984
|
+
helper?: string;
|
|
985
|
+
/** Trailing Info/drill-down column. Defaults to shown. */
|
|
986
|
+
showInfoColumn?: boolean;
|
|
987
|
+
/** Filter options pre-selected on mount, keyed by filter label. */
|
|
988
|
+
defaultFilters?: Record<string, string[]>;
|
|
989
|
+
/** When true, results appear only once the user types a query -- active
|
|
990
|
+
* filters alone don't reveal the table (used where a filter is pre-selected). */
|
|
991
|
+
requireQuery?: boolean;
|
|
992
|
+
/** When true, the full catalog shows immediately without a search -- for
|
|
993
|
+
* short, browsable lists (e.g. US states). */
|
|
994
|
+
showResultsWithoutSearch?: boolean;
|
|
995
|
+
/** When provided, a "Search / Account lists" source toggle appears and the
|
|
996
|
+
* Account lists tab lets the user pull in whole uploaded lists. */
|
|
997
|
+
accountLists?: SelectionPickerAccountList[];
|
|
998
|
+
selectedListIds?: string[];
|
|
999
|
+
onToggleList?: (id: string) => void;
|
|
1000
|
+
/** Rows that are already committed elsewhere and can't be toggled here --
|
|
1001
|
+
* they render dimmed with no checkbox at all (an "Add" picker shouldn't
|
|
1002
|
+
* offer to un-add something it isn't responsible for). Select-all skips
|
|
1003
|
+
* them, so it can't strip them out from under the caller. */
|
|
1004
|
+
lockedIds?: ReadonlySet<string>;
|
|
1005
|
+
/**
|
|
1006
|
+
* Externally-computed, already-filtered rows -- for a caller whose search
|
|
1007
|
+
* is server-side/async (loading, pagination) instead of a full in-memory
|
|
1008
|
+
* `catalog` filtered client-side. When given, this replaces `catalog`'s
|
|
1009
|
+
* own search/filter matching entirely: the component renders exactly
|
|
1010
|
+
* these rows, still gated by the same "has the user searched or filtered
|
|
1011
|
+
* yet" reveal rule as catalog mode. Pair with `onQueryChange` so the
|
|
1012
|
+
* caller knows what to search for, and with
|
|
1013
|
+
* `loading`/`error`/`hasMore`/`onLoadMore` for the rest of a typical async
|
|
1014
|
+
* search's states. Omit (the default) to keep the original catalog +
|
|
1015
|
+
* client-side search/filter behavior -- an existing caller's behavior
|
|
1016
|
+
* never changes just by picking up a new version of this component.
|
|
1017
|
+
*/
|
|
1018
|
+
results?: T[];
|
|
1019
|
+
/** Fires on every keystroke in the search box with the new query text --
|
|
1020
|
+
* for a caller in `results` mode that needs to know what to search for.
|
|
1021
|
+
* Fire-and-forget; this component still owns the input's value either way. */
|
|
1022
|
+
onQueryChange?: (query: string) => void;
|
|
1023
|
+
/** Shows a centered spinner in place of rows. Only meaningful in `results` mode. */
|
|
1024
|
+
loading?: boolean;
|
|
1025
|
+
/** Shows a "couldn't load" message (see `errorMessage`) in place of rows. */
|
|
1026
|
+
error?: boolean;
|
|
1027
|
+
/** @default "Couldn't load options. Please try again." */
|
|
1028
|
+
errorMessage?: string;
|
|
1029
|
+
/** Shows a "Load more" button below the results when another page is available. */
|
|
1030
|
+
hasMore?: boolean;
|
|
1031
|
+
/** Disables the Load more button and shows a small spinner in it while a page is already in flight. */
|
|
1032
|
+
loadingMore?: boolean;
|
|
1033
|
+
onLoadMore?: () => void;
|
|
1034
|
+
/** Extra toolbar content rendered after the built-in filter dropdowns --
|
|
1035
|
+
* e.g. a source/level multi-select the caller owns. A plain composition
|
|
1036
|
+
* slot; this component has no opinion on what it renders. */
|
|
1037
|
+
toolbarExtra?: ReactNode;
|
|
1038
|
+
/**
|
|
1039
|
+
* Optional prefix for `data-testid` attributes on the search input
|
|
1040
|
+
* (`${testIdPrefix}-search`), the source toggle buttons
|
|
1041
|
+
* (`${testIdPrefix}-source-search` / `-lists`), each filter dropdown
|
|
1042
|
+
* trigger (`${testIdPrefix}-filter-<slug>`) and its options, the
|
|
1043
|
+
* select-all checkbox (`${testIdPrefix}-select-all`), each row
|
|
1044
|
+
* (`${testIdPrefix}-row-<slug>`), the Load more button
|
|
1045
|
+
* (`${testIdPrefix}-load-more`), and each Account lists row/count
|
|
1046
|
+
* (`${testIdPrefix}-list-<id>` / `${testIdPrefix}-list-count`). Omit for
|
|
1047
|
+
* no test-id wiring.
|
|
1048
|
+
*/
|
|
1049
|
+
testIdPrefix?: string;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* @pattern SelectionPicker
|
|
1053
|
+
* @description Generic search-and-filter picker for building a selection out
|
|
1054
|
+
* of a larger catalog -- a search box plus filter dropdowns above a checkbox
|
|
1055
|
+
* table, with an optional "Search / Account lists" source toggle for pulling
|
|
1056
|
+
* in whole pre-built lists at once. The table stays empty until the user
|
|
1057
|
+
* actually searches or filters (no pre-loaded catalog dump), and checking a
|
|
1058
|
+
* row commits immediately rather than staging into a separate "Add
|
|
1059
|
+
* selected" step. Supports two data modes: a full in-memory `catalog`
|
|
1060
|
+
* filtered client-side (the default), or externally-computed `results` for a
|
|
1061
|
+
* caller whose search is server-side/async, with `loading`/`error`/
|
|
1062
|
+
* `hasMore`/`onLoadMore` covering that mode's states.
|
|
1063
|
+
* @tokens rounded-control, rounded-card, border-border-default, bg-input,
|
|
1064
|
+
* bg-canvas, bg-surface, shadow-raised, shadow-overlay, text-text-primary,
|
|
1065
|
+
* text-text-secondary, text-text-tertiary, text-text-link,
|
|
1066
|
+
* text-brand-primaryAction, border-brand-primaryAction, bg-brand-primaryAction,
|
|
1067
|
+
* bg-brand-primarySoft, bg-subtle, rounded-full, hover:bg-subtle,
|
|
1068
|
+
* hover:bg-canvas, hover:text-text-primary, placeholder:text-text-tertiary,
|
|
1069
|
+
* cursor-default
|
|
1070
|
+
* @dependencies lucide-react, this package's own CheckBox
|
|
1071
|
+
* @behavior "Select all" is scoped to the current search/filter results (or
|
|
1072
|
+
* the currently-loaded page(s) of `results`), not the whole catalog, and
|
|
1073
|
+
* skips lockedIds throughout so it can't toggle rows it isn't responsible
|
|
1074
|
+
* for. Filter dropdowns are portal-rendered and close on outside-click,
|
|
1075
|
+
* Escape, or scroll. Long option lists (>8) get an in-dropdown search box.
|
|
1076
|
+
* Row and select-all checkboxes use this package's own `CheckBox`, wired
|
|
1077
|
+
* with both an `onChange` and the row's own `onClick` -- `CheckBox` stops
|
|
1078
|
+
* click propagation before firing `onChange`, which is exactly what lets it
|
|
1079
|
+
* sit inside a row that toggles on its own click without double-firing.
|
|
1080
|
+
* @usage
|
|
1081
|
+
* ```tsx
|
|
1082
|
+
* <SelectionPicker
|
|
1083
|
+
* catalog={advertisers}
|
|
1084
|
+
* selected={selected}
|
|
1085
|
+
* onChange={setSelected}
|
|
1086
|
+
* columns={[{ key: 'name', header: 'Advertiser', render: (r) => r.name }]}
|
|
1087
|
+
* filters={[{ label: 'Category', options: categories, value: (r) => r.category }]}
|
|
1088
|
+
* />
|
|
1089
|
+
* <SelectionPicker
|
|
1090
|
+
* catalog={[]}
|
|
1091
|
+
* results={searchResults}
|
|
1092
|
+
* onQueryChange={setQuery}
|
|
1093
|
+
* loading={status === 'loading'}
|
|
1094
|
+
* error={status === 'error'}
|
|
1095
|
+
* hasMore={hasMore}
|
|
1096
|
+
* loadingMore={loadingMore}
|
|
1097
|
+
* onLoadMore={loadMore}
|
|
1098
|
+
* selected={selected}
|
|
1099
|
+
* onChange={setSelected}
|
|
1100
|
+
* columns={columns}
|
|
1101
|
+
* filters={[]}
|
|
1102
|
+
* toolbarExtra={<LevelMultiSelect checked={levels} onToggle={toggleLevel} options={levelOptions} />}
|
|
1103
|
+
* />
|
|
1104
|
+
* ```
|
|
1105
|
+
*/
|
|
1106
|
+
declare function SelectionPicker<T extends PickerRow>({ catalog, selected, onChange, columns, filters, searchPlaceholder, getChildren, helper, showInfoColumn, defaultFilters, requireQuery, showResultsWithoutSearch, accountLists, selectedListIds, onToggleList, lockedIds, results, onQueryChange, loading, error, errorMessage, hasMore, loadingMore, onLoadMore, toolbarExtra, testIdPrefix, }: SelectionPickerProps<T>): react.JSX.Element;
|
|
1107
|
+
|
|
1108
|
+
interface PubBreak {
|
|
1109
|
+
brand: string;
|
|
1110
|
+
owner: string;
|
|
1111
|
+
tags: string[];
|
|
1112
|
+
spendN: number;
|
|
1113
|
+
pct: number;
|
|
1114
|
+
yoy: number | "NEW";
|
|
1115
|
+
}
|
|
1116
|
+
interface PublisherBreakdownListProps {
|
|
1117
|
+
rows: PubBreak[];
|
|
1118
|
+
/** @default 10 */
|
|
1119
|
+
defaultVisibleCount?: number;
|
|
1120
|
+
/** Set false when the container already supplies its own padding. @default true */
|
|
1121
|
+
padded?: boolean;
|
|
1122
|
+
/** When provided, the truncated-list footer becomes a plain link instead of
|
|
1123
|
+
* the inline "see all" expand/collapse behavior. */
|
|
1124
|
+
viewAllHref?: string;
|
|
1125
|
+
/**
|
|
1126
|
+
* Custom per-row logo/avatar rendering -- e.g. a real logo-fetching hook or
|
|
1127
|
+
* a brand-name-to-domain favicon lookup. Defaults to a plain initials
|
|
1128
|
+
* monogram so the component works with zero configuration; this package
|
|
1129
|
+
* intentionally doesn't ship a brand -> logo/domain lookup table, since
|
|
1130
|
+
* that's fixture/business data, not a design-system concern.
|
|
1131
|
+
*/
|
|
1132
|
+
renderLogo?: (row: PubBreak) => ReactNode;
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* @pattern PublisherBreakdownList
|
|
1136
|
+
* @description Flat Current/Previous/YoY spend list, prop-driven end to end
|
|
1137
|
+
* -- no data fetching of its own. Row padding is configurable so it can sit
|
|
1138
|
+
* flush inside a bordered card or inline within an already-padded container.
|
|
1139
|
+
* mr-clientapps-insights-studio's analogous "Top publishers" UI
|
|
1140
|
+
* (`PublisherBreakdown.tsx`) was evaluated for reconciliation and
|
|
1141
|
+
* deliberately NOT merged into this component -- see the escape-hatch note
|
|
1142
|
+
* below. `renderLogo` lets a caller plug in real per-row logos; without it,
|
|
1143
|
+
* rows show a plain initials monogram.
|
|
1144
|
+
* @tokens text-text-primary, text-text-tertiary, text-text-secondary,
|
|
1145
|
+
* text-text-link, text-text-warning, text-text-success, text-text-danger,
|
|
1146
|
+
* border-border-default, bg-surface, bg-subtle, rounded-control, rounded-full,
|
|
1147
|
+
* shadow-overlay
|
|
1148
|
+
* @behavior Truncates to `defaultVisibleCount` rows with a "See all (N more)"
|
|
1149
|
+
* / "Show less" toggle, unless `viewAllHref` is given, in which case the
|
|
1150
|
+
* footer becomes a plain link out instead (e.g. to a full breakdown page).
|
|
1151
|
+
*
|
|
1152
|
+
* Escape hatch: mr-clientapps-insights-studio's own publisher breakdown
|
|
1153
|
+
* (`domains/reviews/src/components/media-spend/PublisherBreakdown.tsx`) is a
|
|
1154
|
+
* genuinely different component, not just a differently-styled copy of this
|
|
1155
|
+
* one -- a three-level Owner/Publisher/Property accordion, each level
|
|
1156
|
+
* independently paginated against a live API (`usePublisherBreakdown`/
|
|
1157
|
+
* `usePublisherBreakdownChildren`), with per-row logo fetching via a batched
|
|
1158
|
+
* hook. None of that -- the recursive expand/collapse tree, the live
|
|
1159
|
+
* fetching, the level toggle -- has an equivalent in this flat,
|
|
1160
|
+
* fully-synchronous, prop-driven list, and bolting it on would mean either
|
|
1161
|
+
* building the accordion machinery into every consumer of this component
|
|
1162
|
+
* (including ones that only ever wanted a flat list, like the Opportunities
|
|
1163
|
+
* battle card) or branching internally on which "mode" is active -- exactly
|
|
1164
|
+
* the kind of forked-with-extra-indirection outcome this package's README
|
|
1165
|
+
* calls out as not actually shared. Per that guidance, this component stays
|
|
1166
|
+
* the flat-list case only; mr-clientapps' accordion stays where it is, un-migrated.
|
|
1167
|
+
* @usage
|
|
1168
|
+
* ```tsx
|
|
1169
|
+
* <PublisherBreakdownList rows={publishers} defaultVisibleCount={10} />
|
|
1170
|
+
* ```
|
|
1171
|
+
*/
|
|
1172
|
+
declare function PublisherBreakdownList({ rows, defaultVisibleCount, padded, viewAllHref, renderLogo }: PublisherBreakdownListProps): react.JSX.Element;
|
|
1173
|
+
|
|
1174
|
+
export { AdvertiserNoResults, type AdvertiserNoResultsProps, AdvertiserQuickFilter, type AdvertiserQuickFilterProps, AdvertiserSearchPrompt, type AdvertiserSearchPromptProps, BarChartPlaceholder, type BarChartPlaceholderProps, Card, type CardProps, CheckBox, type CheckBoxProps, DEFAULT_SEARCH_MATCH, DataScopeFilter, type DataScopeFilterProps, DateRangeFilter, type DateRangeFilterProps, InfoTip, type InfoTipProps, LineChartPlaceholder, type LineChartPlaceholderProps, NO_RESULTS_BODY, NO_RESULTS_TITLE, type NoResultsAction, NoResultsEmptyState, type NoResultsEmptyStateProps, PRIMARY_ACTION_CLASS, type PickerColumn, type PickerFilter, type PickerRow, PieChartPlaceholder, type PieChartPlaceholderProps, type PubBreak, PublisherBreakdownList, type PublisherBreakdownListProps, QUIET_ACTION_CLASS, SearchBar, type SearchBarProps, type SearchBarVariant, type SearchMatchMode, type SearchMatchValue, SectionRail, type SectionRailProps, SelectAllVisible, type SelectAllVisibleProps, SelectFilter, type SelectFilterProps, SelectedSection, type SelectedSectionProps, SelectionPicker, type SelectionPickerAccountList, type SelectionPickerProps, StackedColumnPlaceholder, type StackedColumnPlaceholderProps, type SummaryGroup, type SummaryGroupItemValue, Tooltip, type TooltipProps, WorkspaceSelectedCard, type WorkspaceSelectedCardProps, noResultsBody };
|