@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 +1 -1
- package/src/molecules/SectionCardItem.jsx +6 -2
- package/src/molecules/SectionText.jsx +11 -4
- package/src/organisms/SectionCards.jsx +16 -1
- package/src/organisms/SectionCta.jsx +12 -5
- package/src/organisms/SectionFaq.jsx +7 -1
- package/src/organisms/SectionHero.jsx +7 -0
- package/src/organisms/SectionSplit.jsx +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
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}
|
|
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
|
-
|
|
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-[
|
|
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
|
-
{/*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
|
|
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
|
-
|
|
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 && (
|