@kolkrabbi/kol-component 0.41.0 → 0.42.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.41.0",
3
+ "version": "0.42.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",
@@ -16,12 +16,37 @@
16
16
  * `text-transform: uppercase` was dropped per KOL casing rules; author those
17
17
  * strings in their final case at the call site.
18
18
  *
19
+ * THE SECTION ANATOMY, ONCE (SectionSplit ruling, 2026-08-15). The brief asked
20
+ * for a new `SectionSplit` — media slot beside kicker/heading/body/actions,
21
+ * with a flip — and named eleven hand-built kol-website sections as evidence.
22
+ * That is this component: the anatomy already shipped here, and a second one
23
+ * beside it would be the very duplication the brief exists to end. It grew the
24
+ * three things it genuinely lacked instead — `flip`, `titleSize`, `mediaAspect`.
25
+ *
26
+ * ON THREADING PER-SITE TYPE CLASSES — the brief's named "core design
27
+ * question". The answer is that you do not thread one: a consumer picks a ROLE
28
+ * (`titleSize`), and the component emits exactly one type class for the
29
+ * heading. Passing `kol-sans-heading-01` in alongside `.kol-feature-split-pull`
30
+ * would put two equal-specificity rules on one element and let sheet load order
31
+ * decide the winner — the failure ARCHITECTURE §5 records, and the 2026-07-30
32
+ * law that a component's type lives in its own rule. `columnClassName` and
33
+ * friends remain, for LAYOUT.
34
+ *
19
35
  * @param {ReactNode} kicker mono eyebrow above the title (accent color)
20
36
  * @param {ReactNode} title display pull headline; `<em>` renders as the italic accent
37
+ * @param {'pull'|'display-01'|'display-02'|'display-03'|'heading-01'|'heading-02'|'heading-03'|'heading-04'|'heading-05'} [titleSize='pull']
38
+ * which type ROLE the heading wears — one class, never stacked
21
39
  * @param {ReactNode} body lede paragraph
22
40
  * @param {{num: ReactNode, label: string}[]} meta stats strip (mutually exclusive with `ctas`)
23
41
  * @param {ReactNode} ctas button row (mutually exclusive with `meta`)
24
- * @param {ReactNode} media image/video for the visual column; omit to hide it
42
+ * @param {ReactNode} media image/video/interactive node for the visual
43
+ * column; omit for the text-only section (the /CONNECT band shape)
44
+ * @param {string} [mediaAspect='4/5'] aspect ratio of the media frame
45
+ * @param {boolean} [mediaHover=false] zoom the media on frame hover — the same
46
+ * 1.03 / 300ms / reduced-motion-safe treatment CardFeatureItem uses, so the
47
+ * estate has one motion vocabulary instead of a per-section re-decision
48
+ * @param {boolean} [flip=false] media first, text second — the order swaps at
49
+ * both widths, so the stacked layout leads with the media too
25
50
  * @param {ReactNode} caption mono caption + gradient veil over the media
26
51
  * @param {string} bgImage URL for an inline cover background on the section
27
52
  * @param {boolean} fullBleed span the full viewport width (100vw breakout)
@@ -29,13 +54,29 @@
29
54
  * @param {string} innerClassName extra classes on the grid wrapper
30
55
  * @param {string} columnClassName extra classes on the text column
31
56
  */
57
+ const TITLE_ROLE = {
58
+ pull: 'kol-feature-split-pull',
59
+ 'display-01': 'kol-sans-display-01',
60
+ 'display-02': 'kol-sans-display-02',
61
+ 'display-03': 'kol-sans-display-03',
62
+ 'heading-01': 'kol-sans-heading-01',
63
+ 'heading-02': 'kol-sans-heading-02',
64
+ 'heading-03': 'kol-sans-heading-03',
65
+ 'heading-04': 'kol-sans-heading-04',
66
+ 'heading-05': 'kol-sans-heading-05',
67
+ }
68
+
32
69
  export default function FeatureSplit({
33
70
  kicker,
34
71
  title,
72
+ titleSize = 'pull',
35
73
  body,
36
74
  meta,
37
75
  ctas,
38
76
  media,
77
+ mediaAspect = '4/5',
78
+ mediaHover = false,
79
+ flip = false,
39
80
  caption,
40
81
  bgImage,
41
82
  fullBleed = false,
@@ -53,9 +94,14 @@ export default function FeatureSplit({
53
94
  style={sectionStyle}
54
95
  >
55
96
  <div className={`max-w-[1200px] mx-auto grid grid-cols-1 min-[901px]:grid-cols-2 items-center gap-[clamp(48px,6vw,96px)] ${innerClassName}`.trim()}>
56
- <div className={`flex flex-col gap-4 max-w-[640px] ${columnClassName}`.trim()}>
97
+ {/* `order` rather than `flex-row-reverse`: the grid is one column below
98
+ * 901px, and DOM order is what decides the stack there. Reversing a row
99
+ * would flip the wide layout and leave the narrow one text-first. */}
100
+ <div
101
+ className={`flex flex-col gap-4 max-w-[640px] ${flip ? 'order-2' : ''} ${columnClassName}`.replace(/\s+/g, ' ').trim()}
102
+ >
57
103
  {kicker && <span className="kol-feature-split-kicker">{kicker}</span>}
58
- {title && <h1 className="kol-feature-split-pull">{title}</h1>}
104
+ {title && <h1 className={TITLE_ROLE[titleSize] ?? TITLE_ROLE.pull}>{title}</h1>}
59
105
  {body && <p className="kol-feature-split-body">{body}</p>}
60
106
  {meta && meta.length > 0 && (
61
107
  <div className="kol-feature-split-meta flex flex-wrap gap-y-7 gap-x-12 pt-4">
@@ -70,7 +116,10 @@ export default function FeatureSplit({
70
116
  {ctas && <div className="flex flex-wrap gap-4 pt-2">{ctas}</div>}
71
117
  </div>
72
118
  {media && (
73
- <div className="kol-feature-split-visual relative aspect-[4/5] rounded-[var(--kol-radius-sm)] overflow-hidden">
119
+ <div
120
+ className={`kol-feature-split-visual relative rounded-[var(--kol-radius-sm)] overflow-hidden ${mediaHover ? 'is-hoverable' : ''} ${flip ? 'order-1' : ''}`.replace(/\s+/g, ' ').trim()}
121
+ style={{ aspectRatio: mediaAspect }}
122
+ >
74
123
  {media}
75
124
  {caption && <div className="kol-feature-split-visual-veil" aria-hidden="true" />}
76
125
  {caption && <span className="kol-feature-split-visual-caption">{caption}</span>}