@breathe-ds/ui 0.6.10 → 0.6.11

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/README.md CHANGED
@@ -207,6 +207,10 @@ from color, not from extra markup.
207
207
  - **TestimonialsSection**: the testimonials block, as on ttcab (the standard): "Depoimentos /
208
208
  Histórias de sucesso" (pt/en/es), masonry in two columns, video first, pull-quote cards that
209
209
  open the side panel. Sites pass only `items`.
210
+ - **EvidenceSection**: the evidence block, as on ttcab (the standard): "Evidência /
211
+ Referências científicas" (pt/en/es), heading sticky on the left, article rows on the right,
212
+ authors joined by " · " and cleaned (`authorNames`, `authorLine`). Sites pass `items`,
213
+ `allHref` and the `lead` (what the collection is about).
210
214
  - **Texture & rhythm (opt-in)**: `.cg-grain` adds a barely-there paper grain (noise in
211
215
  the ink color) to any background — the body, a band. `bg-surface-alt` (token
212
216
  `--cg-surface-alt`, falls back to `surface`) is a band one step deeper than the page,
@@ -0,0 +1,107 @@
1
+ import type { ReactNode } from "react";
2
+ import { Button } from "./Button";
3
+ import { SectionHeading } from "./Heading";
4
+ import { LinkRow, RowList } from "./Rows";
5
+
6
+ /**
7
+ * Evidence block of the family (0.6.11, 06/11/2026): the ttcab section made the
8
+ * standard. Two columns: heading, lead and "see all" on the left (sticky while
9
+ * reading), the articles on the right, one row each: title in bold, authors
10
+ * below joined by " · ", without affiliation marks or degrees. Sites pass the
11
+ * articles, the link to the full list and the lead (what the collection is
12
+ * about); the eyebrow, title and link text are the ones validated on ttcab.
13
+ * Put it inside your section's Container.
14
+ */
15
+ const COPY = {
16
+ pt: { eyebrow: "Evidência", title: "Referências científicas", all: "Ver todos os artigos" },
17
+ en: { eyebrow: "Evidence", title: "Scientific references", all: "See all articles" },
18
+ es: { eyebrow: "Evidencia", title: "Referencias científicas", all: "Ver todos los artículos" },
19
+ } as const;
20
+
21
+ // degrees the PDFs glue to the names ("Nathan Watemberg MDa", "Stephanie Nichter, DPT, PT, CCI1")
22
+ const DEGREES = new Set(["MD", "MDa", "PhD", "PT", "DPT", "MS", "MSN", "MSPT", "RN", "CRNP", "AuD", "AS", "CCI", "R.T.", "(R)", "(CT)", "RT"]);
23
+
24
+ /**
25
+ * Authors as a clean list (the ttcab cleaning): no affiliation numbers or
26
+ * symbols (1,2 * †), no loose affiliation letters (",a"), no degrees, and the
27
+ * mixed separators of the records (& • · ; , and e) unified.
28
+ */
29
+ export function authorNames(raw: string | undefined): string[] {
30
+ if (!raw) return [];
31
+ const clean = raw
32
+ .replace(/[\d*†‡§]+/g, "")
33
+ .replace(/,\s*[a-d](?=[\s,]|$)/g, ",")
34
+ .replace(/\s+/g, " ");
35
+ return clean
36
+ .split(/\s*(?:;|•|·|&|,\s*(?:and|e)\s+|\s+and\s+|\s+e\s+(?=[A-ZÀ-Ú])|,)\s*/)
37
+ .map((name) =>
38
+ name
39
+ .split(" ")
40
+ .filter((w) => w && !DEGREES.has(w) && !DEGREES.has(w.replace(/[,.]$/, "")))
41
+ .join(" ")
42
+ .replace(/[,.\s]+$/, "")
43
+ .trim(),
44
+ )
45
+ .filter((name) => name.split(" ").length >= 2 || /^[A-ZÀ-Ú][a-zà-ú]+[A-Z]/.test(name));
46
+ }
47
+
48
+ /** "Ana Lima1, Beto Souza PhD e Caio Reis." → "Ana Lima · Beto Souza · Caio Reis" */
49
+ export const authorLine = (raw: string | string[] | undefined) =>
50
+ (Array.isArray(raw) ? raw : authorNames(raw)).join(" · ");
51
+
52
+ export type EvidenceArticle = {
53
+ href: string;
54
+ title: string;
55
+ /** Raw authors from the record, or an already clean list. */
56
+ authors?: string | string[];
57
+ /** Language of the title when it differs from the page (e.g. "en"). */
58
+ lang?: string;
59
+ };
60
+
61
+ type Props = {
62
+ items: EvidenceArticle[];
63
+ /** Where "see all" points (the site's article list). */
64
+ allHref: string;
65
+ /** What the collection is about (one sentence, from the site). */
66
+ lead?: ReactNode;
67
+ locale?: "pt" | "en" | "es";
68
+ /** Override only when the client asks; the default is the standard. */
69
+ eyebrow?: string;
70
+ title?: string;
71
+ allLabel?: string;
72
+ headingId?: string;
73
+ };
74
+
75
+ export function EvidenceSection({ items, allHref, lead, locale = "pt", eyebrow, title, allLabel, headingId }: Props) {
76
+ const c = COPY[locale];
77
+ return (
78
+ <div className="grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16">
79
+ <div className="lg:sticky lg:top-44 lg:self-start">
80
+ <SectionHeading id={headingId} eyebrow={eyebrow ?? c.eyebrow} title={title ?? c.title} lead={lead} />
81
+ <div className="mt-6">
82
+ <Button variant="link" href={allHref}>
83
+ {allLabel ?? c.all} →
84
+ </Button>
85
+ </div>
86
+ </div>
87
+ <RowList>
88
+ {items.map((a) => {
89
+ const authors = authorLine(a.authors);
90
+ return (
91
+ <LinkRow
92
+ key={a.href}
93
+ className="py-4"
94
+ href={a.href}
95
+ label={
96
+ <>
97
+ <span className="block" lang={a.lang}>{a.title}</span>
98
+ {authors && <span className="mt-1 block text-sm font-normal text-ink-soft">{authors}</span>}
99
+ </>
100
+ }
101
+ />
102
+ );
103
+ })}
104
+ </RowList>
105
+ </div>
106
+ );
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breathe-ds/ui",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "Breathe DS — the design system of the Cuidado Global family of sites (creeta, ttcab, metodorta). Themeable tokens + React components.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -102,6 +102,7 @@
102
102
  "./components/ProfileSpotlight": "./components/ProfileSpotlight.tsx",
103
103
  "./components/FounderSpotlight": "./components/FounderSpotlight.tsx",
104
104
  "./components/TestimonialsSection": "./components/TestimonialsSection.tsx",
105
+ "./components/EvidenceSection": "./components/EvidenceSection.tsx",
105
106
  "./components/FilterBar": "./components/FilterBar.tsx",
106
107
  "./components/Callout": "./components/Callout.tsx",
107
108
  "./components/KenBurnsImage": "./components/KenBurnsImage.tsx"