@kolkrabbi/kol-component 0.78.0 → 0.80.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 +1 -1
- package/src/molecules/SectionCardItem.jsx +6 -2
- package/src/molecules/SectionText.jsx +15 -4
- package/src/organisms/SectionCards.jsx +14 -0
- package/src/organisms/SectionCta.jsx +6 -0
- package/src/organisms/SectionFaq.jsx +3 -1
- package/src/organisms/SectionHero.jsx +28 -14
- package/src/organisms/SectionSplit.jsx +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.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
|
}
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* @param {ReactNode} headline the heading
|
|
22
22
|
* @param {'pull'|'display-01'|'display-02'|'display-03'|'display-04'|'heading-01'|'heading-02'|'heading-03'|'heading-04'|'heading-05'} [headlineSize='heading-02']
|
|
23
23
|
* @param {string} [headlineAs='h2'] element for the headline
|
|
24
|
+
* @param {'auto'|'upper'} [headlineCase='auto'] `upper` = the caps role (`.kol-section-text-caps`) —
|
|
25
|
+
* a title card; the split hero sets it (SectionHeroNoOverrides, 2026-08-26:
|
|
26
|
+
* a ROLE on the molecule, never a hero-side class)
|
|
24
27
|
* @param {ReactNode} body lede / paragraph(s)
|
|
25
28
|
* @param {ReactNode} actions row of Buttons / links / Tags
|
|
26
29
|
* @param {'start'|'center'} [align='start'] text-align + item alignment
|
|
@@ -28,6 +31,10 @@
|
|
|
28
31
|
* @param {ReactNode} children extra slots rendered between body and actions
|
|
29
32
|
* (the split's stats strip)
|
|
30
33
|
* @param {string} labelClass · bodyClass · actionsClass full class overrides (label default: the hero's `kol-helper-12 text-meta`)
|
|
34
|
+
* @param {object} slotClass extra classes per slot — `{ label, headline, body, actions }`
|
|
35
|
+
* (SectionRevealSeams, 2026-08-26: a consumer's reveal system
|
|
36
|
+
* stamps `reveal` on each part; nothing animates in the DS)
|
|
37
|
+
* @param {object} slotStyle inline style per slot — `{ headline: { '--reveal-delay': '0.1s' } }`
|
|
31
38
|
* @param {string} className extra classes on the block
|
|
32
39
|
*/
|
|
33
40
|
export const HEADLINE_ROLE = {
|
|
@@ -48,6 +55,7 @@ export default function SectionText({
|
|
|
48
55
|
headline,
|
|
49
56
|
headlineSize = 'heading-02',
|
|
50
57
|
headlineAs: Headline = 'h2',
|
|
58
|
+
headlineCase = 'auto',
|
|
51
59
|
headlineClass,
|
|
52
60
|
body,
|
|
53
61
|
actions,
|
|
@@ -61,18 +69,21 @@ export default function SectionText({
|
|
|
61
69
|
labelClass = 'kol-helper-12 text-meta',
|
|
62
70
|
bodyClass = 'kol-section-text-body',
|
|
63
71
|
actionsClass = 'flex flex-wrap gap-4',
|
|
72
|
+
slotClass = {},
|
|
73
|
+
slotStyle = {},
|
|
64
74
|
className = '',
|
|
65
75
|
}) {
|
|
76
|
+
const cls = (base, slot) => `${base} ${slotClass[slot] ?? ''}`.trim()
|
|
66
77
|
const alignCls = align === 'center' ? 'items-center text-center' : 'items-start text-left'
|
|
67
78
|
return (
|
|
68
79
|
<div className={`kol-section-text flex flex-col ${gap} ${alignCls} ${className}`.replace(/\s+/g, ' ').trim()}>
|
|
69
80
|
{/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
|
|
70
81
|
* `labelClass` is the voice riding beside it */}
|
|
71
|
-
{label && <span className={`kol-section-text-eyebrow ${labelClass}
|
|
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>)}
|
|
82
|
+
{label && <span className={cls(`kol-section-text-eyebrow ${labelClass}`, 'label')} style={slotStyle.label}>{label}</span>}
|
|
83
|
+
{headline && <Headline className={cls(`${headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}${headlineCase === 'upper' ? ' kol-section-text-caps' : ''}`, 'headline')} style={slotStyle.headline}>{headline}</Headline>}
|
|
84
|
+
{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
85
|
{children}
|
|
75
|
-
{actions && <div className={actionsClass}>{actions}</div>}
|
|
86
|
+
{actions && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
|
|
76
87
|
</div>
|
|
77
88
|
)
|
|
78
89
|
}
|
|
@@ -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,6 +30,13 @@ 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
41
|
/* the family's one cap — the shell's --kol-container-max ladder (2026-08-26) */
|
|
32
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',
|
|
@@ -51,6 +61,8 @@ export default function SectionCards({
|
|
|
51
61
|
bodyClass={`kol-mono-14 text-fg-64 ${headerTextWidthClass}`}
|
|
52
62
|
labelClass="kol-helper-12 text-meta"
|
|
53
63
|
gap="gap-3"
|
|
64
|
+
slotClass={slotClass}
|
|
65
|
+
slotStyle={slotStyle}
|
|
54
66
|
className={headerClassName}
|
|
55
67
|
/>
|
|
56
68
|
)}
|
|
@@ -68,6 +80,8 @@ export default function SectionCards({
|
|
|
68
80
|
backgroundColor={feature.backgroundColor}
|
|
69
81
|
imageAspectRatio={feature.imageAspectRatio}
|
|
70
82
|
onNavigate={onNavigate ? (event) => onNavigate(event, feature) : undefined}
|
|
83
|
+
className={itemClassName}
|
|
84
|
+
style={typeof itemStyle === 'function' ? itemStyle(index) : itemStyle}
|
|
71
85
|
/>
|
|
72
86
|
))}
|
|
73
87
|
</div>
|
|
@@ -35,6 +35,8 @@ 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') {
|
|
@@ -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>
|
|
@@ -82,6 +86,8 @@ export default function SectionCta({
|
|
|
82
86
|
headlineAs="div"
|
|
83
87
|
headlineClass="text-auto kol-sans-heading-01"
|
|
84
88
|
gap="gap-2"
|
|
89
|
+
slotClass={slotClass}
|
|
90
|
+
slotStyle={slotStyle}
|
|
85
91
|
className="self-stretch"
|
|
86
92
|
/>
|
|
87
93
|
))}
|
|
@@ -24,6 +24,8 @@ export default function SectionFaq({
|
|
|
24
24
|
items = [],
|
|
25
25
|
singleOpen = false,
|
|
26
26
|
defaultOpen,
|
|
27
|
+
slotClass,
|
|
28
|
+
slotStyle,
|
|
27
29
|
className = '',
|
|
28
30
|
/* the section sits on the family's cap; the list itself is a READING
|
|
29
31
|
* column inside it — a measure is a content decision, not the frame */
|
|
@@ -35,7 +37,7 @@ export default function SectionFaq({
|
|
|
35
37
|
<div className="max-w-[var(--kol-container-max,var(--kol-content-shell,1800px))] mx-auto">
|
|
36
38
|
<div className={innerClassName}>
|
|
37
39
|
{(label || headline || body || actions) && (
|
|
38
|
-
<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} />
|
|
39
41
|
)}
|
|
40
42
|
{items.length > 0 && (
|
|
41
43
|
<Accordion className="mt-0 mb-0">
|
|
@@ -4,7 +4,7 @@ import Image from '../atoms/Image.jsx'
|
|
|
4
4
|
import OverlayGlassPanel from '../utilities/OverlayGlassPanel.jsx'
|
|
5
5
|
import AssetPlaceholder from '../utilities/AssetPlaceholder.jsx'
|
|
6
6
|
import ContentMedia from '../molecules/ContentMedia.jsx'
|
|
7
|
-
import SectionText
|
|
7
|
+
import SectionText from '../molecules/SectionText.jsx'
|
|
8
8
|
import FeaturedCarousel from './FeaturedCarousel.jsx'
|
|
9
9
|
|
|
10
10
|
/* Height presets (SectionHeroRound2, user ruling 2026-08-26): three tiers in
|
|
@@ -109,7 +109,9 @@ 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 {
|
|
112
|
+
* @param {object} slotClass · slotStyle per-slot class / style, forwarded to SectionText (a consumer's reveal seam)
|
|
113
|
+
* @param {string} headlineSize role; defaults per variant — media 'display-04', split 'heading-02'
|
|
114
|
+
* @param {string} gap SectionText's inner rhythm, forwarded as given (no hero default)
|
|
113
115
|
* @param {string} [panelMaxWidth='max-w-[440px]'] the glass panel's cap on the composed route
|
|
114
116
|
* @param {object} panelProps everything else on the glass panel (user ask 2026-08-26):
|
|
115
117
|
* `padding` ('px-6 py-8') · `maxWidth` · `surfaceOpacity`
|
|
@@ -145,11 +147,17 @@ export default function SectionHero({
|
|
|
145
147
|
height = 'lg',
|
|
146
148
|
label,
|
|
147
149
|
headline,
|
|
148
|
-
|
|
150
|
+
/* per-variant DEFAULT only — the hero has no say in the text's voice
|
|
151
|
+
* (SectionHeroNoOverrides, 2026-08-26): media 'display-04', split
|
|
152
|
+
* 'heading-02'; the consumer's value wins */
|
|
153
|
+
headlineSize,
|
|
154
|
+
gap,
|
|
149
155
|
body,
|
|
150
156
|
actions,
|
|
151
157
|
panelMaxWidth = 'max-w-[440px]',
|
|
152
158
|
panelProps,
|
|
159
|
+
slotClass,
|
|
160
|
+
slotStyle,
|
|
153
161
|
panel,
|
|
154
162
|
children,
|
|
155
163
|
align = 'center',
|
|
@@ -167,18 +175,20 @@ export default function SectionHero({
|
|
|
167
175
|
<section className={`kol-section-hero-split grid grid-cols-1 md:grid-cols-2 ${(HEIGHTS[height === 'lg' ? 'full' : height] || height).replace(/\bh-/g, 'min-h-')} ${bleed} ${className}`.replace(/\s+/g, ' ').trim()}>
|
|
168
176
|
<div className={`relative min-h-[50vh] overflow-hidden md:min-h-0 ${mediaFirst ? '' : 'md:order-2'}`.trim()}>{mediaNode}</div>
|
|
169
177
|
<div className={`flex items-center justify-center p-10 ${mediaFirst ? '' : 'md:order-1'}`.trim()}>
|
|
178
|
+
{/* BARE — the molecule's own voice; `headlineCase="upper"` is the
|
|
179
|
+
* split hero's one trait, a role on SectionText (SectionHeroNoOverrides) */}
|
|
170
180
|
<SectionText
|
|
171
181
|
label={label}
|
|
172
182
|
headline={headline}
|
|
173
|
-
headlineSize={headlineSize
|
|
174
|
-
|
|
183
|
+
headlineSize={headlineSize ?? 'heading-02'}
|
|
184
|
+
headlineCase="upper"
|
|
175
185
|
body={body}
|
|
176
186
|
actions={actions}
|
|
177
187
|
align="center"
|
|
178
|
-
gap=
|
|
179
|
-
bodyClass="kol-sans-body-02 text-body"
|
|
180
|
-
actionsClass="flex flex-wrap gap-4 justify-center"
|
|
188
|
+
gap={gap}
|
|
181
189
|
className="max-w-[var(--kol-content-column)]"
|
|
190
|
+
slotClass={slotClass}
|
|
191
|
+
slotStyle={slotStyle}
|
|
182
192
|
/>
|
|
183
193
|
</div>
|
|
184
194
|
</section>
|
|
@@ -226,19 +236,23 @@ export default function SectionHero({
|
|
|
226
236
|
}
|
|
227
237
|
|
|
228
238
|
const text = composed ? (
|
|
239
|
+
/* BARE (SectionHeroNoOverrides, user 2026-08-26: "you think the purpose
|
|
240
|
+
* of unifying components is to make OVERWRITES?") — the hero passes the
|
|
241
|
+
* slots and nothing about their voice. The old glass hero's gap-6 /
|
|
242
|
+
* body-02 / caps were pixel parity with a retired component, and parity
|
|
243
|
+
* with it is not the bar; one voice across the set is. `gap` is the
|
|
244
|
+
* consumer's if they want it. */
|
|
229
245
|
<SectionText
|
|
230
246
|
label={label}
|
|
231
247
|
headline={headline}
|
|
232
|
-
headlineSize={headlineSize}
|
|
248
|
+
headlineSize={headlineSize ?? 'display-04'}
|
|
233
249
|
body={body}
|
|
234
250
|
actions={actions}
|
|
235
251
|
align={align === 'center' ? 'center' : 'start'}
|
|
236
|
-
|
|
237
|
-
* so the composed route lands on the same pixels */
|
|
238
|
-
gap="gap-6"
|
|
239
|
-
bodyClass="kol-sans-body-02 text-body"
|
|
240
|
-
actionsClass={`flex flex-wrap gap-4${align === 'center' ? ' justify-center' : ''}`}
|
|
252
|
+
gap={gap}
|
|
241
253
|
className={media == null ? `${panelMaxWidth} mx-auto` : ''}
|
|
254
|
+
slotClass={slotClass}
|
|
255
|
+
slotStyle={slotStyle}
|
|
242
256
|
/>
|
|
243
257
|
) : null
|
|
244
258
|
/* 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 = '',
|
|
@@ -87,6 +90,8 @@ export default function SectionSplit({
|
|
|
87
90
|
actions={actions}
|
|
88
91
|
actionsClass={`flex flex-wrap gap-4 pt-2${centred ? ' justify-center' : ''}`}
|
|
89
92
|
align={centred ? 'center' : 'start'}
|
|
93
|
+
slotClass={slotClass}
|
|
94
|
+
slotStyle={slotStyle}
|
|
90
95
|
className={`max-w-[640px] ${mediaFirst ? 'order-2' : ''} ${columnClassName}`}
|
|
91
96
|
>
|
|
92
97
|
{meta && meta.length > 0 && (
|