@actioneer/ads 1.0.8 → 1.0.9

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/DESIGN.md CHANGED
@@ -179,7 +179,7 @@ components:
179
179
 
180
180
  TASTE.md puts it in one line: On a quiet, neutral ground, colour and motion are spent like money — rarely, deliberately, and only where the eye should go.
181
181
 
182
- ADS 1.0.8 is a library first and a look second. 323 components across 10 families (Primitives / utilities, Foundations, Form controls, Overlays, Data display, Feedback, Navigation, Layout, AI Elements, Chat) ship from one barrel, and every product that consumes them inherits the judgment encoded in 56 semantic tokens and 11 principles rather than re-deciding it per screen.
182
+ ADS 1.0.9 is a library first and a look second. 325 components across 10 families (Primitives / utilities, Foundations, Form controls, Overlays, Data display, Feedback, Navigation, Layout, AI Elements, Chat) ship from one barrel, and every product that consumes them inherits the judgment encoded in 56 semantic tokens and 15 principles rather than re-deciding it per screen.
183
183
 
184
184
  The ground is warm grey and the ink is warm near-black. One hue is spent by default, the Studio teal CTA family, and four more carry semantic status. Everything else stays neutral. A consumer product may narrow that further, and one already does: Baby Sentinel drops the teal and runs every action on ink.
185
185
 
@@ -385,7 +385,7 @@ Soft geometry on a 5-step ladder: `xs` 2px, `sm` 4px, `md` 6px, `lg` 8px, `xl` 8
385
385
 
386
386
  ## Components
387
387
 
388
- The catalog is the contract: 323 components, 10 hooks and 98 utilities behind one barrel (`@actioneer/ads`). Import exact, unaliased names. Native elements are for semantic structure only: `<div>`, `<form>`, `<main>`, `<section>`. Never recreate a visual control.
388
+ The catalog is the contract: 325 components, 10 hooks and 98 utilities behind one barrel (`@actioneer/ads`). Import exact, unaliased names. Native elements are for semantic structure only: `<div>`, `<form>`, `<main>`, `<section>`. Never recreate a visual control.
389
389
 
390
390
  ### Sourcing a component
391
391
 
@@ -513,9 +513,9 @@ Hand-rolling a visual control, or importing a UI library at a call site, is neve
513
513
 
514
514
  **The ADS First Rule.** Every UI decision stays synchronized with ADS. Search the full catalog before deciding a component is missing, and use the exact exported name from the public barrel. When the barrel genuinely has no primitive, the sanctioned fallback is a shadcn/ui component vendored into src/components/ui through the repo's components.json recipe — never a hand-rolled control and never a UI library imported at a call site. Enforced by `npm run lint:ads`.
515
515
 
516
- **The Back Leads Rule.** A page-level Back control sits immediately before the page title in the leading header cluster. It is icon-only with an accessible label and never appears on the right with forward/page actions.
516
+ **The Back Leads Rule.** A record or drill-down page (a campaign inside Voice campaigns, a connector inside Data Integrations) is titled by its Breadcrumbs trail — parent / record at 16px — with no back arrow and no repeated H1; the page's one primary action sits at the right end of the same row. A standalone page-level Back control, when one is needed outside a hierarchy, sits immediately before the title and never on the right.
517
517
 
518
- **The Tabs Lead, Search Trails Rule.** When tabs and a search field control the same content, they share one responsive control row: the tab list leads with its triggers left-aligned at their natural width over a baseline that fades from WG3 to transparent, and search sits at the trailing edge. Stack only when the container cannot fit both.
518
+ **The Tabs Lead, Search Trails Rule.** When tabs and a search field control the same content, they share one responsive control row: the tab list leads with its triggers left-aligned at their natural width over a baseline that fades from WG3 to transparent, and search sits at the trailing edge. On collection screens the trailing controls are icon triggers — SearchDisclosure (expands into the field) then Filters — and tab labels are 14px with a quieter count after each label.
519
519
 
520
520
  ## Do's and Don'ts
521
521
 
@@ -550,14 +550,33 @@ Every line below is a `do` or `dont` recorded on a principle in `ads-manifest.js
550
550
  - **Do** prefer more, smaller steps over dense ones; ask one thing at a time. *(minimal-information)*
551
551
  - **Do** derive instead of asking whenever possible (a name from a work email, the company from its domain). *(minimal-information)*
552
552
  - **Do** prefer clear text-only buttons; add an icon only when it communicates essential meaning the label cannot carry alone, or for a compact conventional icon-only control. *(minimal-information)*
553
- - **Do** render multi-step flows full-screen — a single content container that holds the inputs and its own Back/Continue is fine. *(minimal-information)*
553
+ - **Do** open a multi-step create task launched from a collection (New campaign) as a Modal over that list: title-only header, one scrolling form (for New campaign: Campaign name, Caller numbers, Audience), and Cancel + Create together in the footer — the user never loses their place. Add a Stepper only when the task truly has separate steps. Form modals use a fixed height (Modal height="fixed", 640px) so the frame never resizes: the header and footer stay put and ModalBody scrolls. Standalone onboarding flows render full-screen with a single content container holding the inputs and its own Back/Continue. *(minimal-information)*
554
554
  - **Do** use @container on the pane and @lg:/@xl: variants on children inside components. *(responsive-container-fluid)*
555
555
  - **Do** set pane widths in shells/pages through width tokens (thread-max-width, sidebar-width, aside-width, connector-card-min/-max). *(responsive-container-fluid)*
556
556
  - **Do** cap prose at its measure; let tables/charts/tool cards break out wider. Use auto-fill + minmax(min, 1fr) for grids. *(responsive-container-fluid)*
557
- - **Do** use PageHeader with onBack so the left arrow is anchored directly before the title. *(leading-back-navigation)*
558
- - **Do** reserve the trailing header area for forward or page-level actions such as Add, Save, or Share. *(leading-back-navigation)*
559
- - **Do** keep tab triggers left-aligned at their natural width, then place the scoped search field opposite them in the same toolbar. *(tabs-search-opposed)*
557
+ - **Do** title a drill-down with Breadcrumbs (16px trail: muted parent links, Medium current item) and put its primary action (e.g. Pause campaign) right-aligned on the same row. *(leading-back-navigation)*
558
+ - **Do** use PageHeader with onBack only for a standalone page that has no parent hierarchy to show. *(leading-back-navigation)*
559
+ - **Do** reserve the trailing header area for forward or page-level actions such as Add, Save, Pause, or Share. *(leading-back-navigation)*
560
+ - **Do** keep tab triggers left-aligned at their natural width, then place the scoped search opposite them in the same toolbar. *(tabs-search-opposed)*
561
+ - **Do** on collection screens use SearchDisclosure followed by Filters as 32px icon triggers at the trailing edge; keep the page header for the title and one primary action only. *(tabs-search-opposed)*
562
+ - **Do** give each status tab its count (All 42 · Active 14 · Paused 4); the count renders Regular in tertiary grey, teal on the active tab. *(tabs-search-opposed)*
560
563
  - **Do** on constrained containers, stack tabs first and search second while preserving their relationship. *(tabs-search-opposed)*
564
+ - **Do** size the page so the rows fill the panel to its 24px bottom inset and Pagination sits at the bottom; let only the rows scroll. *(collection-screens)*
565
+ - **Do** show row checkboxes only on hover/focus (and while checked or mixed); keep the header select-all checkbox always visible. *(collection-screens)*
566
+ - **Do** when a row's details are open, mark it with TableRow isActive (the soft-teal fade) — no radio and no selection column for that purpose. *(collection-screens)*
567
+ - **Do** put status in a Chip using business language (see status-language) and keep cell values at text-m. *(collection-screens)*
568
+ - **Do** show the customer's workspace logo in the Sidebar brand slot (SidebarWorkspace) when the product runs inside a customer workspace. *(collection-screens)*
569
+ - **Do** put the status Chip and its context ("Finished Oct 21") in the header, 4px under the title, so status never floats on its own. *(record-detail-panels)*
570
+ - **Do** group numbers in small outlined stat cards (WG3 edge, radius-l, 12px padding, 8px apart) and the trend in its own chart card. *(record-detail-panels)*
571
+ - **Do** list attributes (owner, started, calling windows) as label–value rows: label secondary left, value primary right, inset WG3 dividers. *(record-detail-panels)*
572
+ - **Do** separate sections with 32px and keep items inside a section ≤16px apart; give each section a 14px Medium title. *(record-detail-panels)*
573
+ - **Do** stat card: label (14px secondary) → number (32px) with its scope ("of 4,800 calls") 4px apart, then a Progress bar 12px below that fills the card width. *(analytics-surfaces)*
574
+ - **Do** chart card: title (16px Medium) + scope subtitle (12px secondary) on the left, the legend right-aligned on the same row, ~40px from the title block to the plot. *(analytics-surfaces)*
575
+ - **Do** bar charts print each bar's value; stacked bars print each segment that fits plus the stack total. Line charts print every point's value in the series colour (secondary grey for light amber/grey series). *(analytics-surfaces)*
576
+ - **Do** size charts so the cards end on the panel's 24px bottom inset. *(analytics-surfaces)*
577
+ - **Do** say Active, not Dialing; name the tab Active too (All · Draft · Active · Paused · Completed). *(status-language)*
578
+ - **Do** use Chip tones consistently for the same status everywhere (table cell, detail header, filters). *(status-language)*
579
+ - **Do** use the plain Chip appearance (square dot + 14px text) where a column of filled chips would shout louder than the record names. *(status-language)*
561
580
 
562
581
  ### Don't:
563
582
 
@@ -584,8 +603,23 @@ Every line below is a `do` or `dont` recorded on a principle in `ads-manifest.js
584
603
  - **Don't** nest panel/step-rail chrome inside a multi-step flow. *(minimal-information)*
585
604
  - **Don't** use sm:/md: viewport variants inside src/components, or hardcode a pane width there. *(responsive-container-fluid)*
586
605
  - **Don't** cap a grid at a hard column count so it stops growing on wide displays. *(responsive-container-fluid)*
606
+ - **Don't** stack a back arrow and a large H1 above a page whose parent is already known — the breadcrumb says both. *(leading-back-navigation)*
587
607
  - **Don't** place Back on the right side of a page header. *(leading-back-navigation)*
588
608
  - **Don't** render a redundant visible Back label when the standard left arrow and accessible label are sufficient. *(leading-back-navigation)*
589
609
  - **Don't** stretch the tab track to fill the space between its labels and the search field. *(tabs-search-opposed)*
590
610
  - **Don't** put a scoped search field on a separate row above its tabs when both controls fit side by side. *(tabs-search-opposed)*
591
611
  - **Don't** place search before tabs in the reading or keyboard order. *(tabs-search-opposed)*
612
+ - **Don't** put a Search button or a full-width search field in the page header of a collection screen. *(tabs-search-opposed)*
613
+ - **Don't** leave an empty band under a short table, or float Pagination mid-panel. *(collection-screens)*
614
+ - **Don't** use single-selection radios to mean 'this row is open'. *(collection-screens)*
615
+ - **Don't** show a checkbox on every resting row of a multi-select table. *(collection-screens)*
616
+ - **Don't** open record details in a modal over the list you are scanning. *(collection-screens)*
617
+ - **Don't** mix attributes (owner) into the metric grid. *(record-detail-panels)*
618
+ - **Don't** leave a lone chip or line of metadata between the title and the first section. *(record-detail-panels)*
619
+ - **Don't** repeat the same fact (finish date) in the header and in Details. *(record-detail-panels)*
620
+ - **Don't** separate stats with vertical hairlines while their bars run into the next stat — proximity breaks. *(analytics-surfaces)*
621
+ - **Don't** hide numbers behind a hover tooltip. *(analytics-surfaces)*
622
+ - **Don't** give a legend its own row under the subtitle when it fits beside the title. *(analytics-surfaces)*
623
+ - **Don't** put the same spacing between a label and its value as between two separate cards. *(analytics-surfaces)*
624
+ - **Don't** colour a status with the primary teal — it competes with the primary action and the selected-row fade. *(status-language)*
625
+ - **Don't** use different words for the same state in the tab, the chip and the filter. *(status-language)*
package/README.md CHANGED
@@ -158,9 +158,21 @@ Reference the semantic tokens (e.g. `bg-brand`, `text-muted-foreground`,
158
158
 
159
159
  **Sample screens:** full screens built only from ADS exports ship as readable
160
160
  source in `node_modules/@actioneer/ads/examples/` (`AgentsScreen`,
161
- `SettingsScreen`, `WorkspaceHeaderScreen`). Open them to see how components are
162
- meant to be composed, or copy one into your app and swap in your own data; the
163
- folder's `README.md` lists what each one shows.
161
+ `SettingsScreen`, `WorkspaceHeaderScreen`, `CampaignsScreen`,
162
+ `CampaignAnalysisScreen`). Open them to see how components are meant to be
163
+ composed, or copy one into your app and swap in your own data; the folder's
164
+ `README.md` lists what each one shows.
165
+
166
+ **Screen recipes:** when you build a screen CLOSE to a collection (a list of
167
+ records with a detail panel and a create modal) or a record's analytics page,
168
+ start from `CampaignsScreen` / `CampaignAnalysisScreen` and follow the
169
+ `collection-screens`, `record-detail-panels`, `analytics-surfaces` and
170
+ `status-language` principles (`get_principles`) and the `collection-screen` /
171
+ `record-analytics` productions (`get_ui_grammar`). In short: rows fill the panel
172
+ with pagination at the bottom, row checkboxes on hover, `TableRow isActive` for
173
+ the open row, status under the detail title, outlined stat and chart cards with
174
+ legends in the title row and printed values, a 16px breadcrumb titling
175
+ drill-downs, business-language statuses, and fixed-height form modals.
164
176
 
165
177
  Shared motion presets are named exports from `@actioneer/ads`. Components already
166
178
  include their own interaction motion; use the exported presets for additional
@@ -203,7 +215,7 @@ every consuming repo honest.
203
215
 
204
216
  ## Documentation and license
205
217
 
206
- - [Storybook: components and recipes](https://actioneer-ads-storybook.himanshu-563.workers.dev)
218
+ - [Storybook: components](https://actioneer-ads-storybook.himanshu-563.workers.dev)
207
219
  - [ADS MCP: component discovery for coding agents](https://www.npmjs.com/package/@actioneer/ads-mcp)
208
220
  - Bundled references: `@actioneer/ads/manifest.json`, `@actioneer/ads/llms.txt`,
209
221
  and `@actioneer/ads/DESIGN.md`.
@@ -39,6 +39,7 @@ import "react-aria-components";
39
39
  import "../autocomplete.js";
40
40
  import "../slider.js";
41
41
  import "../segmented.js";
42
+ import "../filters.js";
42
43
  import "../modal.js";
43
44
  import "../drawer.js";
44
45
  import "../popover.js";
@@ -127,7 +128,7 @@ const m = "application/x-actioneer-harness", S = [
127
128
  instructions: "Write a concise executive brief: lead with the decision or insight, use only decision-relevant evidence, and end with the recommended next action."
128
129
  }
129
130
  ];
130
- function wi() {
131
+ function Si() {
131
132
  const r = d(), t = u((i) => i.attachments), o = new Set(
132
133
  t.filter((i) => i.contentType === m).map((i) => i.name)
133
134
  ), s = (i) => {
@@ -170,7 +171,7 @@ function wi() {
170
171
  ] })
171
172
  ] });
172
173
  }
173
- class Si {
174
+ class Ai {
174
175
  constructor() {
175
176
  p(this, "accept", m);
176
177
  }
@@ -203,7 +204,7 @@ ${o}
203
204
  }
204
205
  }
205
206
  export {
206
- wi as ComposerHarness,
207
+ Si as ComposerHarness,
207
208
  m as HARNESS_MIME,
208
- Si as HarnessAttachmentAdapter
209
+ Ai as HarnessAttachmentAdapter
209
210
  };
@@ -15,13 +15,13 @@ function B(a) {
15
15
  return null;
16
16
  }
17
17
  const C = r.forwardRef(
18
- ({ className: a, children: n, sticky: e, actions: i, center: c, meta: f, ...p }, o) => {
18
+ ({ className: a, children: n, sticky: e, actions: i, center: c, meta: f, ...p }, l) => {
19
19
  const h = r.Children.toArray(n), b = r.useRef(null), m = r.useCallback(
20
20
  (s) => {
21
- b.current = s, typeof o == "function" ? o(s) : o && (o.current = s);
21
+ b.current = s, typeof l == "function" ? l(s) : l && (l.current = s);
22
22
  },
23
- [o]
24
- ), [l, g] = r.useState(!1);
23
+ [l]
24
+ ), [o, g] = r.useState(!1);
25
25
  return r.useEffect(() => {
26
26
  if (!e) return;
27
27
  const s = B(b.current), u = s ?? window, v = () => g((s ? s.scrollTop : window.scrollY) > 2);
@@ -31,7 +31,7 @@ const C = r.forwardRef(
31
31
  {
32
32
  ref: m,
33
33
  "aria-label": "Breadcrumb",
34
- "data-stuck": e ? l ? "true" : "false" : void 0,
34
+ "data-stuck": e ? o ? "true" : "false" : void 0,
35
35
  className: d(
36
36
  "relative flex w-full items-center",
37
37
  e && "sticky top-0 z-40 bg-card transition-[border-color] duration-150 ease-out motion-reduce:transition-none",
@@ -41,13 +41,13 @@ const C = r.forwardRef(
41
41
  // background over that gap (a no-op at rest, where the strip is the
42
42
  // panel's empty padding).
43
43
  e && "before:absolute before:inset-x-0 before:-top-8 before:h-8 before:bg-inherit before:content-['']",
44
- e && (l ? "border-b border-border" : "border-b border-transparent"),
44
+ e && (o ? "border-b border-border" : "border-b border-transparent"),
45
45
  a
46
46
  ),
47
47
  ...p,
48
48
  children: [
49
49
  /* @__PURE__ */ x("div", { className: "flex min-w-0 shrink items-baseline", children: [
50
- /* @__PURE__ */ t("ol", { className: "flex min-w-0 shrink items-center gap-1.5 text-base", children: /* @__PURE__ */ t(N.Provider, { value: { count: h.length }, children: h.map((s, u) => /* @__PURE__ */ t(w.Provider, { value: u, children: s }, u)) }) }),
50
+ /* @__PURE__ */ t("ol", { className: "flex min-w-0 shrink items-center gap-1.5 text-l", children: /* @__PURE__ */ t(N.Provider, { value: { count: h.length }, children: h.map((s, u) => /* @__PURE__ */ t(w.Provider, { value: u, children: s }, u)) }) }),
51
51
  f && /* @__PURE__ */ t("div", { className: "ml-3 flex shrink-0 items-baseline gap-3 text-s text-tertiary", children: f })
52
52
  ] }),
53
53
  c && /* @__PURE__ */ t("div", { className: "absolute left-1/2 top-1/2 z-50 flex -translate-x-1/2 -translate-y-1/2 items-center", children: c }),
@@ -59,15 +59,15 @@ const C = r.forwardRef(
59
59
  );
60
60
  C.displayName = "Breadcrumbs";
61
61
  const R = r.forwardRef(({ className: a, href: n, isCurrent: e, icon: i, children: c, ...f }, p) => {
62
- const { count: o } = r.useContext(N), b = r.useContext(w) === o - 1, m = "inline-flex items-center gap-1.5", l = /* @__PURE__ */ x(k, { children: [
62
+ const { count: l } = r.useContext(N), b = r.useContext(w) === l - 1, m = "inline-flex items-center gap-1.5", o = /* @__PURE__ */ x(k, { children: [
63
63
  i && /* @__PURE__ */ t("span", { "aria-hidden": !0, className: "flex shrink-0 items-center [&_svg]:size-4", children: i }),
64
64
  e ? /* @__PURE__ */ t("span", { className: "min-w-0 truncate", children: c }) : c
65
65
  ] }), g = e ? /* @__PURE__ */ t(
66
66
  "span",
67
67
  {
68
68
  "aria-current": "page",
69
- className: d(m, "min-w-0 text-base font-medium text-primary"),
70
- children: l
69
+ className: d(m, "min-w-0 text-l font-medium text-primary"),
70
+ children: o
71
71
  }
72
72
  ) : n ? /* @__PURE__ */ t(
73
73
  "a",
@@ -78,9 +78,9 @@ const R = r.forwardRef(({ className: a, href: n, isCurrent: e, icon: i, children
78
78
  "font-normal text-muted-foreground transition-colors hover:text-foreground",
79
79
  "outline-none focus-visible:ring-3 focus-visible:ring-ring rounded-sm"
80
80
  ),
81
- children: l
81
+ children: o
82
82
  }
83
- ) : /* @__PURE__ */ t("span", { className: d(m, "text-muted-foreground"), children: l });
83
+ ) : /* @__PURE__ */ t("span", { className: d(m, "text-muted-foreground"), children: o });
84
84
  return /* @__PURE__ */ x(
85
85
  "li",
86
86
  {
@@ -4,7 +4,22 @@ export declare const buttonVariants: (props?: ({
4
4
  variant?: "link" | "danger" | "outline" | "solid" | "secondary" | "ghost" | null | undefined;
5
5
  size?: "xs" | "sm" | "md" | "lg" | "icon" | "icon-sm" | "icon-xs" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
- export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
7
+ /** Every accepted `size` value, including the deprecated aliases. */
8
+ export type ButtonSize = "md" | "lg" | "icon" | "icon-sm" | "icon-xs"
9
+ /** @deprecated Renders identically to `md` (32px). Use `md`. */
10
+ | "xs"
11
+ /** @deprecated Renders identically to `md` (32px). Use `md`. */
12
+ | "sm";
13
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, Omit<VariantProps<typeof buttonVariants>, "size"> {
14
+ /**
15
+ * Button height. ADS has exactly two text-button heights: `md` (32px, the
16
+ * default) and `lg` (48px, 20px inline padding, radius-l). Icon-only sizes
17
+ * (`icon`, `icon-sm`, `icon-xs`) stay 32px tall.
18
+ *
19
+ * `xs` and `sm` are deprecated aliases kept for backward compatibility —
20
+ * they render identically to `md`.
21
+ */
22
+ size?: ButtonSize | null;
8
23
  isLoading?: boolean;
9
24
  startContent?: React.ReactNode;
10
25
  endContent?: React.ReactNode;
@@ -32,11 +32,14 @@ const x = v(
32
32
  danger: "bg-danger text-danger-foreground enabled:hover:bg-danger/90 enabled:active:bg-danger/90 focus-visible:ring-danger/40"
33
33
  },
34
34
  size: {
35
- // Each size has its own height; the 14px Medium label never changes.
36
- xs: "h-6 rounded-sm px-2 [&_svg]:size-3.5",
37
- sm: "h-7 rounded-sm px-3 [&_svg]:size-4",
35
+ // ADS has exactly two button heights: md (32px, default) and lg
36
+ // (48px). The 14px Medium label never changes between them.
37
+ // xs/sm are deprecated aliases kept for backward compatibility —
38
+ // they render identically to md.
39
+ xs: "h-8 rounded-md px-3.5 [&_svg]:size-4",
40
+ sm: "h-8 rounded-md px-3.5 [&_svg]:size-4",
38
41
  md: "h-8 rounded-md px-3.5 [&_svg]:size-4",
39
- lg: "h-10 rounded-md px-5 [&_svg]:size-5",
42
+ lg: "h-12 rounded-lg px-5 [&_svg]:size-5",
40
43
  icon: "h-8 w-9 rounded-md [&_svg]:size-4",
41
44
  "icon-sm": "h-8 w-8 rounded-sm [&_svg]:size-4",
42
45
  "icon-xs": "h-8 w-7 rounded-sm [&_svg]:size-4"
@@ -2,6 +2,8 @@ import * as React from "react";
2
2
  /** One row of chart data. Gaps (null/undefined) render as breaks in the series. */
3
3
  export type ChartDatum = Record<string, string | number | null | undefined>;
4
4
  export type ChartColor = "primary" | "success" | "warning" | "danger" | "info";
5
+ /** Where a cartesian chart's series legend sits — see `BaseChartProps.legendPlacement`. */
6
+ export type ChartLegendPlacement = "header" | "top";
5
7
  /** Bounded Recharts domain surface — enough for fixed and data-driven axes. */
6
8
  export type ChartAxisDomain = [string | number, string | number];
7
9
  export type ChartTickFormatter = (value: string | number) => string | number;
@@ -82,6 +84,22 @@ interface BaseChartProps {
82
84
  grid?: boolean;
83
85
  /** Defaults to on when there is more than one series. */
84
86
  legend?: boolean;
87
+ /**
88
+ * Chart title, rendered in the chart's own header row (16px Medium). Spend
89
+ * the words on what the plot measures; scope goes in `subtitle`.
90
+ */
91
+ title?: React.ReactNode;
92
+ /** Scope line under the title — count, range, share (e.g. "442 calls · past 7 days"). */
93
+ subtitle?: React.ReactNode;
94
+ /**
95
+ * Where the series legend sits.
96
+ * • `"header"` — right-aligned at the end of the header row, opposite the
97
+ * title block, with 36px between the header and the plot. The default
98
+ * whenever a `title` or `subtitle` is set.
99
+ * • `"top"` — its own right-aligned row directly above the plot (12px
100
+ * gap). The default for an untitled chart (unchanged behavior).
101
+ */
102
+ legendPlacement?: ChartLegendPlacement;
85
103
  showYAxis?: boolean;
86
104
  formatValue?: (value: number) => string;
87
105
  /** Numeric/time x-axis scale. Time data must use numeric timestamps. */
@@ -103,9 +121,11 @@ export interface LineChartProps extends BaseChartProps {
103
121
  /** When set, only these series keys animate on mount/update. */
104
122
  animatedSeriesKeys?: string[];
105
123
  /**
106
- * Print each observation's value above its dot. On by default for a SINGLE
107
- * series (the reader shouldn't have to hover to read a number that fits);
108
- * off once there are several, where the labels would collide.
124
+ * Print each observation's value above its dot — ON by default for every
125
+ * series, so the numbers read without hovering (tooltips still work). The
126
+ * label takes its series color; light series (amber chart-2, grey chart-6,
127
+ * the context grey) use the secondary text color for contrast. Resting
128
+ * labels step aside while the plot is hovered. Pass `false` to opt out.
109
129
  */
110
130
  showValues?: boolean;
111
131
  }
@@ -123,7 +143,14 @@ export interface BarChartProps extends BaseChartProps {
123
143
  stacked?: boolean;
124
144
  /** Visual bar direction: vertical columns (default) or horizontal bars. */
125
145
  layout?: "vertical" | "horizontal";
126
- /** Print each bar's value past its end. On by default for a single series. */
146
+ /**
147
+ * Print values without hovering — ON by default (pass `false` to opt out).
148
+ * Single-series and grouped bars print their value past the bar's end in
149
+ * secondary text. Stacked bars print each segment's value centred inside it
150
+ * when the segment is at least 20px along the value axis (white ink on dark
151
+ * series, primary ink on light amber/grey), plus the stack total past the
152
+ * stack in Medium primary text. Tooltips still work on hover.
153
+ */
127
154
  showValues?: boolean;
128
155
  }
129
156
  export interface ComboChartProps extends BaseChartProps {