@kolkrabbi/kol-component 0.96.0 → 0.97.1

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.96.0",
3
+ "version": "0.97.1",
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",
@@ -25,10 +25,10 @@ import { Icon } from '@kolkrabbi/kol-icons'
25
25
  * author strings in their final case at the call site.
26
26
  *
27
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
28
+ * @param {string} icon Icon name for the header (16px)
29
+ * @param {string|ReactNode} visual image URL, `.svg` mask URL, or inline node; NONE = a text-only tile (title + description, min-h 180, the .feature-card frame/hover)
30
30
  * @param {ReactNode} description footer line (kol-mono-12, muted)
31
- * @param {string} backgroundColor card background utility class
31
+ * @param {string} backgroundColor card background utility class (default: bg-surface-primary with a visual, transparent text-only)
32
32
  * @param {string} href link target; `http*`/`mailto` → new tab, else plain same-tab anchor
33
33
  * @param {Function} onNavigate (event) => void — click seam on the same-tab anchor (SPA intercept)
34
34
  * @param {'auto'|'9/6'|'10/6'|'16/9'|'1/1'} imageAspectRatio aspect class on the visual middle
@@ -39,7 +39,7 @@ export default function SectionCardItem({
39
39
  icon,
40
40
  visual,
41
41
  description,
42
- backgroundColor = 'bg-surface-primary',
42
+ backgroundColor,
43
43
  href,
44
44
  onNavigate,
45
45
  imageAspectRatio = 'auto',
@@ -48,6 +48,11 @@ export default function SectionCardItem({
48
48
  style,
49
49
  }) {
50
50
  const isSvgUrl = typeof visual === 'string' && visual.endsWith('.svg')
51
+ /* TEXT-ONLY (FoundrySpecimenSections, 2026-08-27): no `visual` = a title +
52
+ * description tile — the frame and hover of kol-website's .feature-card, no
53
+ * 96px icon fallback. Hover only, never a persistent selected state. */
54
+ const textOnly = visual == null
55
+ const bg = backgroundColor ?? (textOnly ? 'bg-transparent' : 'bg-surface-primary')
51
56
 
52
57
  const aspectClasses = {
53
58
  'auto': '',
@@ -58,7 +63,15 @@ export default function SectionCardItem({
58
63
  }
59
64
  const aspectClass = aspectClasses[imageAspectRatio] || ''
60
65
 
61
- const content = (
66
+ const content = textOnly ? (
67
+ <>
68
+ <div className="w-full flex items-start justify-between gap-2">
69
+ <h3 className="kol-helper-16">{title}</h3>
70
+ {icon && <Icon name={icon} size={16} className="shrink-0" />}
71
+ </div>
72
+ <p className="kol-mono-12 text-fg-48">{description}</p>
73
+ </>
74
+ ) : (
62
75
  <>
63
76
  <div className="w-full flex items-center justify-between gap-2">
64
77
  <h3 className="kol-helper-16">{title}</h3>
@@ -106,7 +119,9 @@ export default function SectionCardItem({
106
119
  </>
107
120
  )
108
121
 
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()
122
+ const baseClasses = textOnly
123
+ ? `kol-card-feature kol-card-feature--text w-full flex-1 min-h-[180px] p-4 md:p-5 lg:p-6 gap-2 ${bg} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden ${className}`
124
+ : `kol-card-feature w-full flex-1 h-[304px] md:h-72 p-4 md:p-6 gap-4 ${bg} rounded border border-fg-08 flex flex-col justify-between items-start overflow-hidden ${className}`.trim()
110
125
 
111
126
  if (href) {
112
127
  const isExternal = href.startsWith('http') || href.startsWith('mailto')
@@ -151,6 +151,10 @@ export default function ColumnBrowser({
151
151
  const [picked, setPicked] = useState(null)
152
152
  // Keyboard cursor: which column, which row. Clicks keep it in sync.
153
153
  const [cursor, setCursor] = useState({ col: 0, idx: 0 })
154
+ /* the cursor is NOT drawn until the keyboard is used (ColumnBrowserCursorStart,
155
+ * kol-r2b2 2026-08-27): at rest it sat on row 0 beside the open folder and
156
+ * read as a second selection. Arrows arm it; a click seeds it. */
157
+ const [cursorActive, setCursorActive] = useState(false)
154
158
  const rootRef = useRef(null)
155
159
 
156
160
  const levelsOf = (pfx) => {
@@ -181,6 +185,7 @@ export default function ColumnBrowser({
181
185
  }
182
186
  if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) return
183
187
  e.preventDefault()
188
+ setCursorActive(true)
184
189
  const lv = levelsOf(prefix)
185
190
  const col = Math.min(cursor.col, lv.length - 1)
186
191
  const items = itemsAt(lv[col])
@@ -260,9 +265,9 @@ export default function ColumnBrowser({
260
265
  icon="folder"
261
266
  label={f.replace(/\/$/, '')}
262
267
  active={f === activeFolder}
263
- cursor={cursor.col === k && cursor.idx === i}
268
+ cursor={cursorActive && cursor.col === k && cursor.idx === i}
264
269
  trailing={<Icon name="chevron-right" size={12} className="text-fg-32" />}
265
- onClick={() => { setCursor({ col: k, idx: i }); rootRef.current?.focus(); setPicked(null); onPrefix(level + f) }}
270
+ onClick={() => { setCursor({ col: k, idx: i }); setCursorActive(true); rootRef.current?.focus(); setPicked(null); onPrefix(level + f) }}
266
271
  />
267
272
  ))}
268
273
  {files.map((o, i) => (
@@ -272,8 +277,8 @@ export default function ColumnBrowser({
272
277
  label={o.displayKey ?? o.key}
273
278
  muted={shown?.key !== o.key}
274
279
  active={shown?.key === o.key}
275
- cursor={cursor.col === k && cursor.idx === folders.length + i}
276
- onClick={() => { setCursor({ col: k, idx: folders.length + i }); rootRef.current?.focus(); setPicked(o) }}
280
+ cursor={cursorActive && cursor.col === k && cursor.idx === folders.length + i}
281
+ onClick={() => { setCursor({ col: k, idx: folders.length + i }); setCursorActive(true); rootRef.current?.focus(); setPicked(o) }}
277
282
  />
278
283
  ))}
279
284
  {folders.length === 0 && files.length === 0 && (
@@ -295,7 +295,8 @@ const ContentFilters = ({
295
295
  * nothing, so it must not wear a button's chrome. The atom exists
296
296
  * for exactly this (lobby ruling 2026-07-30 — "icons only, NO
297
297
  * states"); the span here was the same defect that promoted it. */}
298
- <h2 className="flex items-center gap-2">
298
+ {/* the icon gap matches the specimen header — 16 from md (FoundrySpecimenSections, 2026-08-27) */}
299
+ <h2 className="flex items-center gap-2 md:gap-4">
299
300
  {titleIcon && <IconFrame name={titleIcon} variant="secondary" size="md" />}
300
301
  {/* `pr-4` by rule (ContentFiltersTitleGap, kol-website 2026-08-27): the
301
302
  * divider sat 24px from the title's text edge but 32 from the