@kolkrabbi/kol-component 0.72.0 → 0.74.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolkrabbi/kol-component",
3
- "version": "0.72.0",
3
+ "version": "0.74.0",
4
4
  "description": "KOL design-system components — atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,7 +13,9 @@
13
13
  * Every default class is a SEAM (the ContentText pattern): pass `labelClass`
14
14
  * / `bodyClass` / `actionsClass` to replace the string whole — that is how
15
15
  * SectionHero's eyebrow and SectionCta's rows wear their own voices on the
16
- * same anatomy. No casing transforms author strings in their final case.
16
+ * same anatomy. THE LABEL IS UPPERCASE BY ROLE (user ruling 2026-08-26):
17
+ * an eyebrow is caps by design language, so the span carries
18
+ * `.kol-section-text-eyebrow`; headline, body and actions render as authored.
17
19
  *
18
20
  * @param {ReactNode} label section header / kicker
19
21
  * @param {ReactNode} headline the heading
@@ -60,7 +62,9 @@ export default function SectionText({
60
62
  const alignCls = align === 'center' ? 'items-center text-center' : 'items-start text-left'
61
63
  return (
62
64
  <div className={`kol-section-text flex flex-col ${gap} ${alignCls} ${className}`.replace(/\s+/g, ' ').trim()}>
63
- {label && <span className={labelClass}>{label}</span>}
65
+ {/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
66
+ * `labelClass` is the voice riding beside it */}
67
+ {label && <span className={`kol-section-text-eyebrow ${labelClass}`}>{label}</span>}
64
68
  {headline && <Headline className={headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}>{headline}</Headline>}
65
69
  {body && (typeof body === 'string' ? <p className={bodyClass}>{body}</p> : <div className={bodyClass}>{body}</div>)}
66
70
  {children}
@@ -11,7 +11,7 @@ import SectionText, { HEADLINE_ROLE } from '../molecules/SectionText.jsx'
11
11
  * Every block is presence-gated (header on any text prop, cards on
12
12
  * `features.length`, actions on `actions`), so omitted slots emit no empty
13
13
  * nodes. Card links are plain anchors; pass `onNavigate` to intercept same-tab
14
- * navigations in an SPA. Casing is authored at the call site.
14
+ * navigations in an SPA. The label is uppercase by role; the rest as authored.
15
15
  *
16
16
  * @param {{title, icon, visual, description, href, backgroundColor, imageAspectRatio}[]} features
17
17
  * @param {ReactNode} label · headline · body the header (heading-03 + mono lede by default)
@@ -6,7 +6,7 @@ import SectionText from '../molecules/SectionText.jsx'
6
6
  * label-over-value rows, each a `SectionText` (label in the helper voice,
7
7
  * value as a heading-01; a row with `href` renders its value as a link).
8
8
  * `CtaGlobal` is this component under its old name (alias kept). Columns sit
9
- * side by side from `md` up and stack below. Casing is authored at the call site.
9
+ * side by side from `md` up and stack below. Row labels are uppercase by role.
10
10
  *
11
11
  * @param {ReactNode} eyebrow left-column display wordmark (kol-sans-display-01)
12
12
  * @param {ReactNode} promptLabel prompt-row label
@@ -2,7 +2,9 @@ import { isValidElement } from 'react'
2
2
  import HlsVideo from '../atoms/HlsVideo.jsx'
3
3
  import Image from '../atoms/Image.jsx'
4
4
  import OverlayGlassPanel from '../utilities/OverlayGlassPanel.jsx'
5
- import SectionText from '../molecules/SectionText.jsx'
5
+ import AssetPlaceholder from '../utilities/AssetPlaceholder.jsx'
6
+ import ContentMedia from '../molecules/ContentMedia.jsx'
7
+ import SectionText, { HEADLINE_ROLE } from '../molecules/SectionText.jsx'
6
8
 
7
9
  /** Height presets; anything else is passed through as a class string. */
8
10
  const HEIGHTS = {
@@ -66,7 +68,16 @@ function MediaLayer({ media }) {
66
68
  * the hero renders your node exactly as before — the escape hatch, and what
67
69
  * every existing call site does; nothing there moves.
68
70
  *
69
- * @param {ReactNode|{src, kind, poster, srcSet, alt}} media background
71
+ * TWO VARIANTS (user ask 2026-08-26). `media` (default): the cover-fit hero
72
+ * above. `split`: the whole viewport in two halves — media fills one edge to
73
+ * edge (no media → AssetPlaceholder), the text sits centred in the other, no
74
+ * glass panel, headline UPPERCASE by role (`.kol-section-text-caps`). It is
75
+ * SectionSplit blown up to 100vh/100vw with no padding, no ratio, no cap.
76
+ * `align` picks the media side there ('left' default · 'right'); below `md`
77
+ * the halves stack, media first.
78
+ *
79
+ * @param {'media'|'split'} variant
80
+ * @param {ReactNode|{src, kind, poster, srcSet, alt}} media background (media) / the half (split)
70
81
  * @param {number} overlayOpacity 0–100 surface-primary scrim over the media (default 0)
71
82
  * @param {string} height 'screen' | 'lg' | 'md' preset, or a height class string
72
83
  * @param {ReactNode} label · headline · body · actions the composed text (SectionText)
@@ -74,10 +85,14 @@ function MediaLayer({ media }) {
74
85
  * @param {string} [panelMaxWidth='max-w-[440px]'] the glass panel's cap on the composed route
75
86
  * @param {ReactNode} panel a caller-authored content node; wins over the text props and children
76
87
  * @param {ReactNode} children fallback content slot
77
- * @param {string} align 'center' | 'start' | 'end' — horizontal placement of the content
88
+ * @param {string} align media: 'center' | 'start' | 'end' — placement of the content ·
89
+ * split: 'left' | 'right' — the media side
90
+ * @param {boolean} fullBleed span the full viewport width (100vw breakout) — split
78
91
  * @param {string} className extra classes on the section
79
92
  */
80
93
  export default function SectionHero({
94
+ variant = 'media',
95
+ fullBleed = false,
81
96
  media,
82
97
  overlayOpacity = 0,
83
98
  height = 'lg',
@@ -92,6 +107,37 @@ export default function SectionHero({
92
107
  align = 'center',
93
108
  className = '',
94
109
  }) {
110
+ if (variant === 'split') {
111
+ const mediaFirst = align !== 'right'
112
+ const bleed = fullBleed ? 'w-screen ml-[calc(50%-50vw)]' : 'w-full'
113
+ const mediaNode = media == null
114
+ ? <AssetPlaceholder radius={false} className="h-full w-full" />
115
+ : isValidElement(media) || typeof media !== 'object'
116
+ ? <ContentMedia ratio={null} radius={false} fit="cover" className="h-full">{media}</ContentMedia>
117
+ : <MediaLayer media={media} />
118
+ return (
119
+ <section className={`kol-section-hero-split grid min-h-screen grid-cols-1 md:grid-cols-2 ${bleed} ${className}`.replace(/\s+/g, ' ').trim()}>
120
+ <div className={`relative min-h-[50vh] overflow-hidden md:min-h-0 ${mediaFirst ? '' : 'md:order-2'}`.trim()}>{mediaNode}</div>
121
+ <div className={`flex items-center justify-center p-10 ${mediaFirst ? '' : 'md:order-1'}`.trim()}>
122
+ <SectionText
123
+ label={label}
124
+ headline={headline}
125
+ headlineSize={headlineSize === 'display-04' ? 'heading-02' : headlineSize}
126
+ headlineClass={`${HEADLINE_ROLE[headlineSize === 'display-04' ? 'heading-02' : headlineSize] ?? HEADLINE_ROLE['heading-02']} kol-section-text-caps`}
127
+ body={body}
128
+ actions={actions}
129
+ align="center"
130
+ gap="gap-6"
131
+ labelClass="kol-helper-12 text-meta"
132
+ bodyClass="kol-sans-body-02 text-body"
133
+ actionsClass="flex flex-wrap gap-4 justify-center"
134
+ className="max-w-[var(--kol-content-column)]"
135
+ />
136
+ </div>
137
+ </section>
138
+ )
139
+ }
140
+
95
141
  const heightCls = HEIGHTS[height] || height
96
142
  const alignCls =
97
143
  align === 'start' ? 'justify-start'
@@ -15,7 +15,7 @@ import SectionText from '../molecules/SectionText.jsx'
15
15
  * `meta` and `actions` are mutually exclusive by intent (pick one); `actions`
16
16
  * is conventionally a row of KOL Buttons. The media column renders only when
17
17
  * `media` is passed, and `caption` gates both the gradient veil and the
18
- * caption element. Casing is authored at the call site.
18
+ * caption element. The label is uppercase by role; the rest renders as authored.
19
19
  *
20
20
  * @param {ReactNode} label mono eyebrow above the headline (accent)
21
21
  * @param {ReactNode} headline display pull; `<em>` renders as the italic accent