@kolkrabbi/kol-component 0.77.1 → 0.79.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.77.1",
3
+ "version": "0.79.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",
@@ -44,6 +44,8 @@ export default function SectionCardItem({
44
44
  onNavigate,
45
45
  imageAspectRatio = 'auto',
46
46
  imagePosition = 'center',
47
+ className = '',
48
+ style,
47
49
  }) {
48
50
  const isSvgUrl = typeof visual === 'string' && visual.endsWith('.svg')
49
51
 
@@ -104,7 +106,7 @@ export default function SectionCardItem({
104
106
  </>
105
107
  )
106
108
 
107
- const baseClasses = `kol-card-feature w-full flex-1 h-[304px] md:h-72 p-4 md:p-6 gap-4 ${backgroundColor} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden`
109
+ const baseClasses = `kol-card-feature w-full flex-1 h-[304px] md:h-72 p-4 md:p-6 gap-4 ${backgroundColor} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden ${className}`.trim()
108
110
 
109
111
  if (href) {
110
112
  const isExternal = href.startsWith('http') || href.startsWith('mailto')
@@ -114,6 +116,7 @@ export default function SectionCardItem({
114
116
  <a
115
117
  href={href}
116
118
  className={`${baseClasses} hover:border-fg-32 transition-colors duration-300`}
119
+ style={style}
117
120
  target="_blank"
118
121
  rel="noreferrer noopener"
119
122
  >
@@ -127,11 +130,12 @@ export default function SectionCardItem({
127
130
  href={href}
128
131
  onClick={onNavigate}
129
132
  className={`${baseClasses} hover:border-fg-24 transition-colors duration-300`}
133
+ style={style}
130
134
  >
131
135
  {content}
132
136
  </a>
133
137
  )
134
138
  }
135
139
 
136
- return <div className={baseClasses}>{content}</div>
140
+ return <div className={baseClasses} style={style}>{content}</div>
137
141
  }
@@ -28,6 +28,10 @@
28
28
  * @param {ReactNode} children extra slots rendered between body and actions
29
29
  * (the split's stats strip)
30
30
  * @param {string} labelClass · bodyClass · actionsClass full class overrides (label default: the hero's `kol-helper-12 text-meta`)
31
+ * @param {object} slotClass extra classes per slot — `{ label, headline, body, actions }`
32
+ * (SectionRevealSeams, 2026-08-26: a consumer's reveal system
33
+ * stamps `reveal` on each part; nothing animates in the DS)
34
+ * @param {object} slotStyle inline style per slot — `{ headline: { '--reveal-delay': '0.1s' } }`
31
35
  * @param {string} className extra classes on the block
32
36
  */
33
37
  export const HEADLINE_ROLE = {
@@ -61,18 +65,21 @@ export default function SectionText({
61
65
  labelClass = 'kol-helper-12 text-meta',
62
66
  bodyClass = 'kol-section-text-body',
63
67
  actionsClass = 'flex flex-wrap gap-4',
68
+ slotClass = {},
69
+ slotStyle = {},
64
70
  className = '',
65
71
  }) {
72
+ const cls = (base, slot) => `${base} ${slotClass[slot] ?? ''}`.trim()
66
73
  const alignCls = align === 'center' ? 'items-center text-center' : 'items-start text-left'
67
74
  return (
68
75
  <div className={`kol-section-text flex flex-col ${gap} ${alignCls} ${className}`.replace(/\s+/g, ' ').trim()}>
69
76
  {/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
70
77
  * `labelClass` is the voice riding beside it */}
71
- {label && <span className={`kol-section-text-eyebrow ${labelClass}`}>{label}</span>}
72
- {headline && <Headline className={headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}>{headline}</Headline>}
73
- {body && (typeof body === 'string' ? <p className={bodyClass}>{body}</p> : <div className={bodyClass}>{body}</div>)}
78
+ {label && <span className={cls(`kol-section-text-eyebrow ${labelClass}`, 'label')} style={slotStyle.label}>{label}</span>}
79
+ {headline && <Headline className={cls(headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02']), 'headline')} style={slotStyle.headline}>{headline}</Headline>}
80
+ {body && (typeof body === 'string' ? <p className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</p> : <div className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</div>)}
74
81
  {children}
75
- {actions && <div className={actionsClass}>{actions}</div>}
82
+ {actions && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
76
83
  </div>
77
84
  )
78
85
  }
@@ -17,6 +17,9 @@ import SectionText, { HEADLINE_ROLE } from '../molecules/SectionText.jsx'
17
17
  * @param {ReactNode} label · headline · body the header (heading-03 + mono lede by default)
18
18
  * @param {ReactNode} actions centred action row under the cards
19
19
  * @param {Function} onNavigate (event, feature) => void
20
+ * @param {string} itemClassName extra classes on every card (reveal seam)
21
+ * @param {object|Function} itemStyle inline style per card, or `(index) => style`
22
+ * @param {object} slotClass · slotStyle per-slot class / style on the header text
20
23
  * @param {string} sectionClassName · wrapperClassName · cardsWrapperClassName · actionsClassName · headerClassName · headerTextWidthClass layout seams
21
24
  */
22
25
  export default function SectionCards({
@@ -27,8 +30,16 @@ export default function SectionCards({
27
30
  body,
28
31
  actions,
29
32
  onNavigate,
33
+ /* the reveal seams (SectionRevealSeams, kol-website 2026-08-26): a
34
+ * consumer's entrance system stamps a class + `--reveal-delay` per card;
35
+ * `itemStyle` is an object or `(index) => object`. Nothing animates here. */
36
+ itemClassName = '',
37
+ itemStyle,
38
+ slotClass,
39
+ slotStyle,
30
40
  sectionClassName = '',
31
- wrapperClassName = 'w-full flex flex-col gap-8 md:gap-10 max-w-[1400px] mx-auto',
41
+ /* the family's one cap the shell's --kol-container-max ladder (2026-08-26) */
42
+ wrapperClassName = 'w-full flex flex-col gap-8 md:gap-10 max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto',
32
43
  cardsWrapperClassName = 'self-stretch inline-flex flex-col md:flex-row md:h-72 justify-start items-center gap-6',
33
44
  actionsClassName = 'pt-10 pb-24',
34
45
  headerClassName = 'w-full pt-[224px]',
@@ -50,6 +61,8 @@ export default function SectionCards({
50
61
  bodyClass={`kol-mono-14 text-fg-64 ${headerTextWidthClass}`}
51
62
  labelClass="kol-helper-12 text-meta"
52
63
  gap="gap-3"
64
+ slotClass={slotClass}
65
+ slotStyle={slotStyle}
53
66
  className={headerClassName}
54
67
  />
55
68
  )}
@@ -67,6 +80,8 @@ export default function SectionCards({
67
80
  backgroundColor={feature.backgroundColor}
68
81
  imageAspectRatio={feature.imageAspectRatio}
69
82
  onNavigate={onNavigate ? (event) => onNavigate(event, feature) : undefined}
83
+ className={itemClassName}
84
+ style={typeof itemStyle === 'function' ? itemStyle(index) : itemStyle}
70
85
  />
71
86
  ))}
72
87
  </div>
@@ -35,12 +35,14 @@ export default function SectionCta({
35
35
  contactLabel,
36
36
  email,
37
37
  secondaryRows = [],
38
+ slotClass,
39
+ slotStyle,
38
40
  className = '',
39
41
  }) {
40
42
  if (variant === 'centered') {
41
43
  return (
42
44
  <section className={`w-full py-24 ${className}`.trim()}>
43
- <div className="max-w-[900px] mx-auto">
45
+ <div className="max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto">
44
46
  <div className="w-32 h-px bg-fg-24 mx-auto mb-8" />
45
47
  <SectionText
46
48
  align="center"
@@ -52,6 +54,8 @@ export default function SectionCta({
52
54
  actions={actions}
53
55
  actionsClass="pt-4 flex flex-col sm:flex-row gap-4 justify-center"
54
56
  gap="gap-8"
57
+ slotClass={slotClass}
58
+ slotStyle={slotStyle}
55
59
  />
56
60
  </div>
57
61
  </section>
@@ -65,10 +69,11 @@ export default function SectionCta({
65
69
  ]
66
70
  return (
67
71
  <section className={`w-full bg-auto ${className}`.trim()}>
68
- {/* capped at 1600 and centred (SectionCtaEditorial, user 2026-08-26:
69
- * "I would cap it at 1800 or 1600") the surface stays full width,
70
- * the columns stop hugging the viewport edges at 2000 */}
71
- <div className="w-full max-w-[1600px] mx-auto py-10 flex flex-col md:flex-row items-start gap-12 overflow-hidden">
72
+ {/* the family's ONE cap the shell's --kol-container-max ladder (user
73
+ * ruling 2026-08-26; the 1600 SectionCtaEditorial asked for was a third
74
+ * number beside split's 1200 and cards' 1400) the surface stays full
75
+ * width, the columns stop hugging the viewport edges */}
76
+ <div className="w-full max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto py-10 flex flex-col md:flex-row items-start gap-12 overflow-hidden">
72
77
  <div className="flex-1 self-stretch text-auto kol-sans-display-01">{eyebrow}</div>
73
78
  <div className="flex-1 self-stretch pb-6 md:pt-32 flex flex-col justify-end items-start gap-12">
74
79
  {rows.map((row, i) => (
@@ -81,6 +86,8 @@ export default function SectionCta({
81
86
  headlineAs="div"
82
87
  headlineClass="text-auto kol-sans-heading-01"
83
88
  gap="gap-2"
89
+ slotClass={slotClass}
90
+ slotStyle={slotStyle}
84
91
  className="self-stretch"
85
92
  />
86
93
  ))}
@@ -24,15 +24,20 @@ export default function SectionFaq({
24
24
  items = [],
25
25
  singleOpen = false,
26
26
  defaultOpen,
27
+ slotClass,
28
+ slotStyle,
27
29
  className = '',
30
+ /* the section sits on the family's cap; the list itself is a READING
31
+ * column inside it — a measure is a content decision, not the frame */
28
32
  innerClassName = 'max-w-[var(--kol-content-column)] flex flex-col gap-8',
29
33
  }) {
30
34
  const [open, setOpen] = useState(defaultOpen ?? null)
31
35
  return (
32
36
  <section className={`kol-section-faq w-full px-5 py-16 md:px-8 md:py-24 lg:px-14 ${className}`.trim()}>
37
+ <div className="max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto">
33
38
  <div className={innerClassName}>
34
39
  {(label || headline || body || actions) && (
35
- <SectionText label={label} headline={headline} headlineSize={headlineSize} body={body} actions={actions} />
40
+ <SectionText label={label} headline={headline} headlineSize={headlineSize} body={body} actions={actions} slotClass={slotClass} slotStyle={slotStyle} />
36
41
  )}
37
42
  {items.length > 0 && (
38
43
  <Accordion className="mt-0 mb-0">
@@ -51,6 +56,7 @@ export default function SectionFaq({
51
56
  </Accordion>
52
57
  )}
53
58
  </div>
59
+ </div>
54
60
  </section>
55
61
  )
56
62
  }
@@ -109,6 +109,7 @@ function MediaLayer({ media }) {
109
109
  * slide's title in the typeface's own font and routes the CTA through the SPA)
110
110
  * @param {string} height 'full' | '80' | '60' (viewport tiers) · 'lg' | 'md' | 'screen' (aliases) · or a height class string
111
111
  * @param {ReactNode} label · headline · body · actions the composed text (SectionText)
112
+ * @param {object} slotClass · slotStyle per-slot class / style, forwarded to SectionText (a consumer's reveal seam)
112
113
  * @param {string} [headlineSize='display-04']
113
114
  * @param {string} [panelMaxWidth='max-w-[440px]'] the glass panel's cap on the composed route
114
115
  * @param {object} panelProps everything else on the glass panel (user ask 2026-08-26):
@@ -150,6 +151,8 @@ export default function SectionHero({
150
151
  actions,
151
152
  panelMaxWidth = 'max-w-[440px]',
152
153
  panelProps,
154
+ slotClass,
155
+ slotStyle,
153
156
  panel,
154
157
  children,
155
158
  align = 'center',
@@ -179,6 +182,8 @@ export default function SectionHero({
179
182
  bodyClass="kol-sans-body-02 text-body"
180
183
  actionsClass="flex flex-wrap gap-4 justify-center"
181
184
  className="max-w-[var(--kol-content-column)]"
185
+ slotClass={slotClass}
186
+ slotStyle={slotStyle}
182
187
  />
183
188
  </div>
184
189
  </section>
@@ -239,6 +244,8 @@ export default function SectionHero({
239
244
  bodyClass="kol-sans-body-02 text-body"
240
245
  actionsClass={`flex flex-wrap gap-4${align === 'center' ? ' justify-center' : ''}`}
241
246
  className={media == null ? `${panelMaxWidth} mx-auto` : ''}
247
+ slotClass={slotClass}
248
+ slotStyle={slotStyle}
242
249
  />
243
250
  ) : null
244
251
  /* no media → the TEXT-ONLY hero: the composed text on the surface, no glass
@@ -36,6 +36,7 @@ import SectionText from '../molecules/SectionText.jsx'
36
36
  * @param {ReactNode} caption mono caption + gradient veil over the media
37
37
  * @param {string} bgImage inline cover background on the section
38
38
  * @param {boolean} fullBleed span the full viewport width
39
+ * @param {object} slotClass · slotStyle per-slot class / style on the text block (reveal seam)
39
40
  * @param {string} className · innerClassName · columnClassName layout seams
40
41
  */
41
42
  export default function SectionSplit({
@@ -54,6 +55,8 @@ export default function SectionSplit({
54
55
  caption,
55
56
  bgImage,
56
57
  fullBleed = false,
58
+ slotClass,
59
+ slotStyle,
57
60
  className = '',
58
61
  innerClassName = '',
59
62
  columnClassName = '',
@@ -72,7 +75,10 @@ export default function SectionSplit({
72
75
  className={`kol-section-split px-5 py-16 md:px-8 md:py-24 lg:px-14 lg:py-32 ${bleed} ${className}`.replace(/\s+/g, ' ').trim()}
73
76
  style={sectionStyle}
74
77
  >
75
- <div className={`max-w-[1200px] mx-auto ${grid} ${innerClassName}`.replace(/\s+/g, ' ').trim()}>
78
+ {/* ONE cap for the whole section family (user ruling 2026-08-26): the shell's
79
+ * --kol-container-max ladder — 100% → 1400 → 1600 → 1800. Split ran 1200,
80
+ * cards 1400, the CTA 1600: three numbers for one job, unflagged. */}
81
+ <div className={`max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto ${grid} ${innerClassName}`.replace(/\s+/g, ' ').trim()}>
76
82
  {/* `order` rather than `flex-row-reverse`: the grid is one column below
77
83
  * 901px, and DOM order is what decides the stack there. */}
78
84
  <SectionText
@@ -84,6 +90,8 @@ export default function SectionSplit({
84
90
  actions={actions}
85
91
  actionsClass={`flex flex-wrap gap-4 pt-2${centred ? ' justify-center' : ''}`}
86
92
  align={centred ? 'center' : 'start'}
93
+ slotClass={slotClass}
94
+ slotStyle={slotStyle}
87
95
  className={`max-w-[640px] ${mediaFirst ? 'order-2' : ''} ${columnClassName}`}
88
96
  >
89
97
  {meta && meta.length > 0 && (