@kolkrabbi/kol-component 0.76.4 → 0.77.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.76.4",
3
+ "version": "0.77.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",
package/src/index.js CHANGED
@@ -70,7 +70,7 @@ export { default as AlignmentGrid } from './molecules/AlignmentGrid.jsx'
70
70
  export { default as ImageBlock } from './molecules/ImageBlock.jsx'
71
71
  export { default as SelectionOverlay } from './atoms/SelectionOverlay.jsx'
72
72
  export { default as VideoBlock, getEmbedUrl } from './molecules/VideoBlock.jsx'
73
- export { default as CardFeatureItem } from './molecules/CardFeatureItem.jsx'
73
+ export { default as SectionCardItem, default as CardFeatureItem } from './molecules/SectionCardItem.jsx'
74
74
  export { default as CodeBlock } from './molecules/CodeBlock.jsx'
75
75
  export { default as ColorInputRow } from './molecules/ColorInputRow.jsx'
76
76
  export { default as ColorRamp } from './molecules/ColorRamp.jsx'
@@ -1,133 +1,6 @@
1
- import { Icon } from '@kolkrabbi/kol-icons'
2
-
3
- /* taxonomy-ok: nests only kol-icons's Icon (a package import the
4
- * relative-import check can't see). */
5
-
6
1
  /**
7
- * CardFeatureItem — fixed-height feature card: a title + optional icon
8
- * header, a flexible visual middle, and a mono description footer. The
9
- * card's visual is polymorphic: an `.svg` URL string renders as a
10
- * `mask-image` block tinted with `currentColor` (theme-correct line-art),
11
- * any other string renders as a cover-fit `<img>`, a ReactNode renders
12
- * as-is, and no visual falls back to a large 96px `Icon` (the header's
13
- * `icon` name). The grid child of FeaturesCardSection.
14
- *
15
- * Optionally the whole card is a link. `http*` / `mailto` hrefs open in a
16
- * new tab; any other href renders a plain same-tab anchor with an
17
- * `onNavigate(event)` seam — an SPA consumer intercepts there
18
- * (preventDefault + its router) instead of this component importing one.
19
- *
20
- * Title and description render exactly as authored — no casing transforms;
21
- * author strings in their final case at the call site.
22
- *
23
- * @param {ReactNode} title header heading (kol-helper-16)
24
- * @param {string} icon Icon name for the header (16px); also the 96px no-visual fallback
25
- * @param {string|ReactNode} visual image URL, `.svg` mask URL, or inline node
26
- * @param {ReactNode} description footer line (kol-mono-12, muted)
27
- * @param {string} backgroundColor card background utility class
28
- * @param {string} href link target; `http*`/`mailto` → new tab, else plain same-tab anchor
29
- * @param {Function} onNavigate (event) => void — click seam on the same-tab anchor (SPA intercept)
30
- * @param {'auto'|'9/6'|'10/6'|'16/9'|'1/1'} imageAspectRatio aspect class on the visual middle
31
- * @param {string} imagePosition `<img>` object-position
2
+ * @deprecated 2026-08-26 `CardFeatureItem` is `SectionCardItem` under its old
3
+ * name (the section family: the composition is the card, its parts are card
4
+ * items). Alias kept; removed at the next major.
32
5
  */
33
- export default function CardFeatureItem({
34
- title,
35
- icon,
36
- visual,
37
- description,
38
- backgroundColor = 'bg-surface-primary',
39
- href,
40
- onNavigate,
41
- imageAspectRatio = 'auto',
42
- imagePosition = 'center',
43
- }) {
44
- const isSvgUrl = typeof visual === 'string' && visual.endsWith('.svg')
45
-
46
- const aspectClasses = {
47
- 'auto': '',
48
- '9/6': 'aspect-[9/6]',
49
- '10/6': 'aspect-[10/6]',
50
- '16/9': 'aspect-video',
51
- '1/1': 'aspect-square',
52
- }
53
- const aspectClass = aspectClasses[imageAspectRatio] || ''
54
-
55
- const content = (
56
- <>
57
- <div className="w-full flex items-center justify-between gap-2">
58
- <h3 className="kol-helper-16">{title}</h3>
59
- {icon && <Icon name={icon} size={16} className="shrink-0" />}
60
- </div>
61
-
62
- {/* kol-card-feature-visual: zooms 1.03 on card hover (chrome in
63
- * kol-theme — CardFeatureHoverZoom 2026-08-12); all three visual
64
- * forms ride the same wrapper, reduced-motion opts out. */}
65
- <div className={`kol-card-feature-visual w-full flex-1 flex items-center justify-center overflow-hidden ${aspectClass}`.trim()}>
66
- {visual ? (
67
- typeof visual === 'string' ? (
68
- isSvgUrl ? (
69
- /* currentColor line-art: the SVG paints as a mask over bg-current */
70
- <div
71
- className="w-full h-full bg-current rounded"
72
- style={{
73
- maskImage: `url(${visual})`,
74
- maskSize: 'contain',
75
- maskRepeat: 'no-repeat',
76
- maskPosition: 'center',
77
- WebkitMaskImage: `url(${visual})`,
78
- WebkitMaskSize: 'contain',
79
- WebkitMaskRepeat: 'no-repeat',
80
- WebkitMaskPosition: 'center',
81
- }}
82
- />
83
- ) : (
84
- <img
85
- src={visual}
86
- alt={typeof title === 'string' ? title : ''}
87
- className="w-full h-full object-cover rounded"
88
- style={{ objectPosition: imagePosition }}
89
- />
90
- )
91
- ) : (
92
- visual
93
- )
94
- ) : (
95
- <Icon name={icon} size={96} />
96
- )}
97
- </div>
98
-
99
- <p className="kol-mono-12 text-fg-48">{description}</p>
100
- </>
101
- )
102
-
103
- 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`
104
-
105
- if (href) {
106
- const isExternal = href.startsWith('http') || href.startsWith('mailto')
107
-
108
- if (isExternal) {
109
- return (
110
- <a
111
- href={href}
112
- className={`${baseClasses} hover:border-fg-32 transition-colors duration-300`}
113
- target="_blank"
114
- rel="noreferrer noopener"
115
- >
116
- {content}
117
- </a>
118
- )
119
- }
120
-
121
- return (
122
- <a
123
- href={href}
124
- onClick={onNavigate}
125
- className={`${baseClasses} hover:border-fg-24 transition-colors duration-300`}
126
- >
127
- {content}
128
- </a>
129
- )
130
- }
131
-
132
- return <div className={baseClasses}>{content}</div>
133
- }
6
+ export { default } from './SectionCardItem.jsx'
@@ -0,0 +1,137 @@
1
+ import { Icon } from '@kolkrabbi/kol-icons'
2
+
3
+ /* taxonomy-ok: nests only kol-icons's Icon (a package import the
4
+ * relative-import check can't see). */
5
+
6
+ /**
7
+ * SectionCardItem — the card SectionCards is made of. Renamed from
8
+ * CardFeatureItem 2026-08-26 (user: the composition is the card, so its parts
9
+ * are card ITEMS); `CardFeatureItem` stays as a deprecated alias.
10
+ *
11
+ * SectionCardItem — fixed-height feature card: a title + optional icon
12
+ * header, a flexible visual middle, and a mono description footer. The
13
+ * card's visual is polymorphic: an `.svg` URL string renders as a
14
+ * `mask-image` block tinted with `currentColor` (theme-correct line-art),
15
+ * any other string renders as a cover-fit `<img>`, a ReactNode renders
16
+ * as-is, and no visual falls back to a large 96px `Icon` (the header's
17
+ * `icon` name). The grid child of FeaturesCardSection.
18
+ *
19
+ * Optionally the whole card is a link. `http*` / `mailto` hrefs open in a
20
+ * new tab; any other href renders a plain same-tab anchor with an
21
+ * `onNavigate(event)` seam — an SPA consumer intercepts there
22
+ * (preventDefault + its router) instead of this component importing one.
23
+ *
24
+ * Title and description render exactly as authored — no casing transforms;
25
+ * author strings in their final case at the call site.
26
+ *
27
+ * @param {ReactNode} title header heading (kol-helper-16)
28
+ * @param {string} icon Icon name for the header (16px); also the 96px no-visual fallback
29
+ * @param {string|ReactNode} visual image URL, `.svg` mask URL, or inline node
30
+ * @param {ReactNode} description footer line (kol-mono-12, muted)
31
+ * @param {string} backgroundColor card background utility class
32
+ * @param {string} href link target; `http*`/`mailto` → new tab, else plain same-tab anchor
33
+ * @param {Function} onNavigate (event) => void — click seam on the same-tab anchor (SPA intercept)
34
+ * @param {'auto'|'9/6'|'10/6'|'16/9'|'1/1'} imageAspectRatio aspect class on the visual middle
35
+ * @param {string} imagePosition `<img>` object-position
36
+ */
37
+ export default function SectionCardItem({
38
+ title,
39
+ icon,
40
+ visual,
41
+ description,
42
+ backgroundColor = 'bg-surface-primary',
43
+ href,
44
+ onNavigate,
45
+ imageAspectRatio = 'auto',
46
+ imagePosition = 'center',
47
+ }) {
48
+ const isSvgUrl = typeof visual === 'string' && visual.endsWith('.svg')
49
+
50
+ const aspectClasses = {
51
+ 'auto': '',
52
+ '9/6': 'aspect-[9/6]',
53
+ '10/6': 'aspect-[10/6]',
54
+ '16/9': 'aspect-video',
55
+ '1/1': 'aspect-square',
56
+ }
57
+ const aspectClass = aspectClasses[imageAspectRatio] || ''
58
+
59
+ const content = (
60
+ <>
61
+ <div className="w-full flex items-center justify-between gap-2">
62
+ <h3 className="kol-helper-16">{title}</h3>
63
+ {icon && <Icon name={icon} size={16} className="shrink-0" />}
64
+ </div>
65
+
66
+ {/* kol-card-feature-visual: zooms 1.03 on card hover (chrome in
67
+ * kol-theme — CardFeatureHoverZoom 2026-08-12); all three visual
68
+ * forms ride the same wrapper, reduced-motion opts out. */}
69
+ <div className={`kol-card-feature-visual w-full flex-1 flex items-center justify-center overflow-hidden ${aspectClass}`.trim()}>
70
+ {visual ? (
71
+ typeof visual === 'string' ? (
72
+ isSvgUrl ? (
73
+ /* currentColor line-art: the SVG paints as a mask over bg-current */
74
+ <div
75
+ className="w-full h-full bg-current rounded"
76
+ style={{
77
+ maskImage: `url(${visual})`,
78
+ maskSize: 'contain',
79
+ maskRepeat: 'no-repeat',
80
+ maskPosition: 'center',
81
+ WebkitMaskImage: `url(${visual})`,
82
+ WebkitMaskSize: 'contain',
83
+ WebkitMaskRepeat: 'no-repeat',
84
+ WebkitMaskPosition: 'center',
85
+ }}
86
+ />
87
+ ) : (
88
+ <img
89
+ src={visual}
90
+ alt={typeof title === 'string' ? title : ''}
91
+ className="w-full h-full object-cover rounded"
92
+ style={{ objectPosition: imagePosition }}
93
+ />
94
+ )
95
+ ) : (
96
+ visual
97
+ )
98
+ ) : (
99
+ <Icon name={icon} size={96} />
100
+ )}
101
+ </div>
102
+
103
+ <p className="kol-mono-12 text-fg-48">{description}</p>
104
+ </>
105
+ )
106
+
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`
108
+
109
+ if (href) {
110
+ const isExternal = href.startsWith('http') || href.startsWith('mailto')
111
+
112
+ if (isExternal) {
113
+ return (
114
+ <a
115
+ href={href}
116
+ className={`${baseClasses} hover:border-fg-32 transition-colors duration-300`}
117
+ target="_blank"
118
+ rel="noreferrer noopener"
119
+ >
120
+ {content}
121
+ </a>
122
+ )
123
+ }
124
+
125
+ return (
126
+ <a
127
+ href={href}
128
+ onClick={onNavigate}
129
+ className={`${baseClasses} hover:border-fg-24 transition-colors duration-300`}
130
+ >
131
+ {content}
132
+ </a>
133
+ )
134
+ }
135
+
136
+ return <div className={baseClasses}>{content}</div>
137
+ }
@@ -1,9 +1,9 @@
1
- import CardFeatureItem from '../molecules/CardFeatureItem.jsx'
1
+ import SectionCardItem from '../molecules/SectionCardItem.jsx'
2
2
  import SectionText, { HEADLINE_ROLE } from '../molecules/SectionText.jsx'
3
3
 
4
4
  /**
5
5
  * SectionCards — the "N-up feature cards" band: a `SectionText` header over a
6
- * responsive row of `CardFeatureItem` cards, capped by an optional centred
6
+ * responsive row of `SectionCardItem` cards, capped by an optional centred
7
7
  * action row. `FeaturesCardSection` is this component under its old name and
8
8
  * prop names (`headerLabel` → `headline`, `headerDescription` → `body`,
9
9
  * `ctas` → `actions`; alias kept).
@@ -57,7 +57,7 @@ export default function SectionCards({
57
57
  {features.length > 0 && (
58
58
  <div className={cardsWrapperClassName}>
59
59
  {features.map((feature, index) => (
60
- <CardFeatureItem
60
+ <SectionCardItem
61
61
  key={index}
62
62
  title={feature.title}
63
63
  icon={feature.icon}