@breathe-ds/ui 0.5.1 → 0.5.4

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
@@ -304,8 +304,9 @@ from color, not from extra markup.
304
304
  keep working) to open its details. By tone: no border, no shadow, no lift.
305
305
  - **Events** (after Luma and Meetup, for the school's classes): **EventList**
306
306
  (days down a dotted rail, each with its rows), **EventRow** (time with an
307
- accented end, title as the stretched link, organizers, location, who is going
308
- with "+N", square cover, an optional badge), **EventFacts** (date on a
307
+ accented end, title as the stretched link, or a button with `onSelect` to
308
+ open the details in a SidePanel, organizers, location, who is going with
309
+ "+N", square cover, an optional badge), **EventFacts** (date on a
309
310
  calendar tile, venue on a pin tile with its link), **DateBadge** and
310
311
  **RegistrationBox** (a strip title, ticket options as a radio group with the
311
312
  remaining pill and the price, the one button at the foot).
@@ -345,7 +346,7 @@ from color, not from extra markup.
345
346
  **Data rule:** every professional MUST have a **CREFITO** attached (COFFITO requirement).
346
347
  - [x] **Pill / PillList** — the Atalho of novo.creeta (white 44px pill, arrow bubble: ↓ anchor, ↗ other page, optional meta); wrapping row + "See all"
347
348
  - [x] **AnchorNav** (floating capsule of anchors, scroll-spy + sliding indicator) — extracted from ttcab (10/29)
348
- - [x] **Footer** · **PageVideoHero** (loop, and player from a YouTube id or the site's own mp4) · **Header** (dropdown, house) · **BottomNav** · **Curtain** · **Lockup** (5 arrangements) · **BreatheMark**
349
+ - [x] **Footer** · **PageVideoHero** (loop, and player from a YouTube id or the site's own mp4; `panel.actions` puts two or more calls to action inside the panel) · **Header** (dropdown, house) · **BottomNav** · **Curtain** · **Lockup** (5 arrangements) · **BreatheMark**
349
350
  - [x] **Site brand marks** (`components/marks/`): real symbol + wordmark for TTCAB and Creeta (10/29)
350
351
  - [ ] **ThreadRail** (side graphic device — a thread running down the left linking sections) — approved,
351
352
  **already exists in ttcab** → extract.
@@ -11,6 +11,9 @@ type Labels = {
11
11
  };
12
12
  const EN: Labels = { more: (n) => `+${n}`, by: "By" };
13
13
 
14
+ // the hit area of the row: the title's link or button, stretched over it
15
+ const STRETCH = "after:absolute after:inset-0 after:rounded-[24px] focus-visible:outline-none focus-visible:after:outline focus-visible:after:outline-2 focus-visible:after:-outline-offset-2 focus-visible:after:outline-ink";
16
+
14
17
  export type EventRowProps = {
15
18
  /** The time line ("09:00 · 12:00 GMT+0", or "3 to 9 Nov · 08:30–17:30"). */
16
19
  time: ReactNode;
@@ -18,7 +21,10 @@ export type EventRowProps = {
18
21
  * time this way). Optional. */
19
22
  timeAccent?: ReactNode;
20
23
  title: ReactNode;
21
- href: string;
24
+ /** The event's page. With `onSelect` instead, the row opens its details in place. */
25
+ href?: string;
26
+ /** Makes the title a button stretched over the row (a SidePanel opens, say). */
27
+ onSelect?: () => void;
22
28
  /** Who organizes or teaches: small portrait(s) and the name(s). */
23
29
  organizers?: Person[];
24
30
  location?: ReactNode;
@@ -41,7 +47,7 @@ export type EventRowProps = {
41
47
  * where, who is going, and the square cover on the right. Lives in an
42
48
  * EventList, under a date heading.
43
49
  */
44
- export function EventRow({ time, timeAccent, title, href, organizers, location, people, peopleMore, cover, badge, linkComponent, labels, className = "" }: EventRowProps) {
50
+ export function EventRow({ time, timeAccent, title, href, onSelect, organizers, location, people, peopleMore, cover, badge, linkComponent, labels, className = "" }: EventRowProps) {
45
51
  const L: ElementType = linkComponent ?? "a";
46
52
  const t = { ...EN, ...labels };
47
53
  const shown = people?.slice(0, 4) ?? [];
@@ -58,9 +64,15 @@ export function EventRow({ time, timeAccent, title, href, organizers, location,
58
64
  )}
59
65
  </p>
60
66
  <h3 className="mt-1.5 font-heading text-xl font-medium leading-snug text-ink md:text-[1.375rem]">
61
- <L href={href} className="after:absolute after:inset-0 after:rounded-[24px] focus-visible:outline-none focus-visible:after:outline focus-visible:after:outline-2 focus-visible:after:-outline-offset-2 focus-visible:after:outline-ink">
62
- {title}
63
- </L>
67
+ {onSelect ? (
68
+ <button type="button" onClick={onSelect} className={`text-left ${STRETCH}`}>
69
+ {title}
70
+ </button>
71
+ ) : (
72
+ <L href={href} className={STRETCH}>
73
+ {title}
74
+ </L>
75
+ )}
64
76
  {badge && <span className="ml-2 inline-flex translate-y-[-2px] items-center rounded-pill bg-ink/[0.08] px-2.5 py-0.5 align-middle text-xs font-semibold text-ink">{badge}</span>}
65
77
  </h3>
66
78
  {organizers && organizers.length > 0 && (
@@ -50,8 +50,10 @@ export function List({ items, marker = "check", linkComponent, newTabLabel = "(o
50
50
  const mark = marker === "check" ? bubble : <CornerDownRight />;
51
51
  const gap = marker === "check" ? "gap-3" : "gap-2.5";
52
52
 
53
+ // leading-snug (1.375): in a two-line item the lines stay closer than the
54
+ // items are to each other; relaxed leading read as air inside the item (2026-10-29)
53
55
  return (
54
- <ul className={`list-none ${marker === "check" ? "space-y-3 text-ink" : "space-y-2 leading-relaxed text-ink/80"} ${className}`}>
56
+ <ul className={`list-none leading-snug ${marker === "check" ? "space-y-3 text-ink" : "space-y-2.5 text-ink/80"} ${className}`}>
55
57
  {items.map((it, i) => (
56
58
  <li key={`${i}-${it.href ?? ""}`}>
57
59
  {it.href ? (
@@ -3,7 +3,10 @@
3
3
  import { useEffect, useRef, useState, type ReactNode } from "react";
4
4
  import { BackgroundVideo } from "./BackgroundVideo";
5
5
 
6
- type Panel = { kicker?: ReactNode; title: ReactNode; lead?: ReactNode };
6
+ /** The glass panel: kicker, title, lead and, when the page has more than one
7
+ * call to action, the buttons inside the panel (the bottom-right slot fits
8
+ * one button beside the panel; two would run into the title). */
9
+ type Panel = { kicker?: ReactNode; title: ReactNode; lead?: ReactNode; actions?: ReactNode };
7
10
 
8
11
  /** Local background clip (loop mode): muted, looping. */
9
12
  type LoopPreview = {
@@ -247,6 +250,7 @@ export function PageVideoHero({
247
250
  posterLarge={(video as VideoLoop).preview.posterLarge}
248
251
  trimEdges={(video as VideoLoop).preview.trimEdges}
249
252
  objectPosition={(video as VideoLoop).preview.objectPosition}
253
+ labels={previewLabels}
250
254
  className="absolute inset-0"
251
255
  />
252
256
  )}
@@ -265,7 +269,7 @@ export function PageVideoHero({
265
269
  {/* panel / caption — in flow (justify-end), m-6 */}
266
270
  <div className="relative z-10 p-6 md:m-6 md:p-0">
267
271
  {panel ? (
268
- <div className="inline-flex max-w-full items-center rounded-[24px] bg-ink/75 px-6 py-4 backdrop-blur-md md:max-w-[calc(100%-16rem)] lg:px-7 lg:py-5">
272
+ <div className={`inline-flex max-w-full items-center rounded-[24px] bg-ink/75 px-6 py-4 backdrop-blur-md lg:px-7 lg:py-5 ${panel.actions && actions === undefined ? "" : "md:max-w-[calc(100%-16rem)]"}`}>
269
273
  <div>
270
274
  {panel.kicker && (
271
275
  <p className="mb-3 font-heading text-[0.82rem] font-bold tracking-[0.1em] text-surface/80 uppercase">{panel.kicker}</p>
@@ -274,6 +278,7 @@ export function PageVideoHero({
274
278
  {panel.lead && (
275
279
  <p className="mt-4 max-w-[46ch] text-base leading-[1.55] text-pretty text-surface/85 md:text-lg">{panel.lead}</p>
276
280
  )}
281
+ {panel.actions && <div className="mt-6 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">{panel.actions}</div>}
277
282
  </div>
278
283
  </div>
279
284
  ) : caption ? (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breathe-ds/ui",
3
- "version": "0.5.1",
3
+ "version": "0.5.4",
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",