@kolkrabbi/kol-component 0.220.0 → 0.221.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.
|
|
3
|
+
"version": "0.221.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
|
@@ -152,7 +152,6 @@ export { default as ErrorBoundary } from './utilities/ErrorBoundary.jsx'
|
|
|
152
152
|
export { default as FeatureSplit } from './organisms/FeatureSplit.jsx'
|
|
153
153
|
export { default as FeaturedCarousel } from './organisms/FeaturedCarousel.jsx'
|
|
154
154
|
export { default as FeaturesCardSection } from './organisms/FeaturesCardSection.jsx'
|
|
155
|
-
export { default as FoundryCTA } from './organisms/FoundryCTA.jsx'
|
|
156
155
|
export { default as SectionHero, default as FullBleedHero } from './organisms/SectionHero.jsx'
|
|
157
156
|
export { default as SectionSplit } from './organisms/SectionSplit.jsx'
|
|
158
157
|
export { default as SectionCards } from './organisms/SectionCards.jsx'
|
|
@@ -462,6 +462,11 @@ export default function ColumnBrowser({
|
|
|
462
462
|
quickLook = null,
|
|
463
463
|
onQuickLook,
|
|
464
464
|
onPick,
|
|
465
|
+
/* `picked` — the file the CALLER holds picked (a key or an object; kol-client-olina 2026-09-25:
|
|
466
|
+
* a pick made in rows or grid was lost on the switch to columns, because this browser kept its
|
|
467
|
+
* own). Given, the browser opens on that file's level with its row selected and previewed, and
|
|
468
|
+
* follows it when it changes; absent (`undefined`), the pick is internal exactly as before. */
|
|
469
|
+
picked: pickedIn,
|
|
465
470
|
urlOf,
|
|
466
471
|
kindOf = defaultKindOf,
|
|
467
472
|
kindLabel = DEFAULT_KIND_LABEL,
|
|
@@ -559,6 +564,16 @@ export default function ColumnBrowser({
|
|
|
559
564
|
if (prefix !== level) { keepCursor.current = true; pickedByCollapse.current = true; onPrefix(level) }
|
|
560
565
|
}
|
|
561
566
|
const pickedByCollapse = useRef(false)
|
|
567
|
+
const pickedInKey = typeof pickedIn === 'string' ? pickedIn : pickedIn?.key ?? null
|
|
568
|
+
useEffect(() => {
|
|
569
|
+
if (pickedIn === undefined || (picked?.key ?? null) === pickedInKey) return
|
|
570
|
+
const o = typeof pickedIn === 'string' ? objects.find((x) => x.key === pickedIn) : pickedIn
|
|
571
|
+
if (!o) { setPicked(null); return }
|
|
572
|
+
const level = o.key.slice(0, o.key.lastIndexOf('/') + 1)
|
|
573
|
+
setPicked(o)
|
|
574
|
+
setCursor(seedCursor(level))
|
|
575
|
+
if (prefix !== level) { keepCursor.current = true; pickedByCollapse.current = true; onPrefix(level) }
|
|
576
|
+
}, [pickedInKey]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
562
577
|
useEffect(() => { if (pickedByCollapse.current) { pickedByCollapse.current = false; return } if (picked) pick(null) }, [prefix]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
563
578
|
useEffect(() => { setExpanded((prev) => (prev.size ? new Set() : prev)) }, [prefix])
|
|
564
579
|
const levelsOf = (pfx) => {
|
|
@@ -580,6 +595,12 @@ export default function ColumnBrowser({
|
|
|
580
595
|
* internal moves land on the same spot, so nothing jumps. */
|
|
581
596
|
const seedCursor = (pfx) => {
|
|
582
597
|
const lv = levelsOf(pfx)
|
|
598
|
+
/* a caller's `picked` file at this level is where the keyboard starts — on the file, not on
|
|
599
|
+
* the folder that opened its column */
|
|
600
|
+
if (pickedInKey?.startsWith(pfx) && !pickedInKey.slice(pfx.length).includes('/')) {
|
|
601
|
+
const idx = itemsAt(pfx).findIndex((it) => it.type === 'file' && it.o.key === pickedInKey)
|
|
602
|
+
if (idx >= 0) return { col: lv.length - 1, idx }
|
|
603
|
+
}
|
|
583
604
|
if (lv.length < 2) return { col: 0, idx: 0 }
|
|
584
605
|
const col = lv.length - 2
|
|
585
606
|
const opened = lv[col + 1].slice(lv[col].length)
|
|
@@ -195,12 +195,19 @@ function useBucketLibrary({ client, bucket, defaults, settings: controlled, onSe
|
|
|
195
195
|
|
|
196
196
|
/* ── shared pieces, kol-r2b2's ─────────────────────────────────────────────── */
|
|
197
197
|
|
|
198
|
-
/* The frame is square until the file reports its size, then snaps to its nearest preset.
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
/* The frame is square until the file reports its size, then snaps to its nearest preset. A photo
|
|
199
|
+
* near a preset fills it (`cover`, a few percent cropped); a vector or anything past the ladder's
|
|
200
|
+
* ends — a 2.8:1 wordmark, an 11:1 nav logo — shows whole (`contain`, kol-client-olina 2026-09-25). */
|
|
201
|
+
function ImageFrame({ src, vector }) {
|
|
202
|
+
const [frame, setFrame] = useState({ ratio: '1 / 1', contain: !!vector })
|
|
203
|
+
const onLoad = (e) => {
|
|
204
|
+
const { naturalWidth: w, naturalHeight: h } = e.target
|
|
205
|
+
const r = w && h ? w / h : 1
|
|
206
|
+
setFrame({ ratio: nearestRatio(w, h), contain: vector || r > 16 / 9 || r < 9 / 16 })
|
|
207
|
+
}
|
|
201
208
|
return (
|
|
202
|
-
<div className="w-full rounded overflow-hidden flex items-center justify-center" style={{ aspectRatio: ratio }}>
|
|
203
|
-
<img src={src} alt="" className=
|
|
209
|
+
<div className="w-full rounded overflow-hidden flex items-center justify-center" style={{ aspectRatio: frame.ratio }}>
|
|
210
|
+
<img src={src} alt="" className={`w-full h-full ${frame.contain ? 'object-contain' : 'object-cover'}`} loading="lazy" onLoad={onLoad} />
|
|
204
211
|
</div>
|
|
205
212
|
)
|
|
206
213
|
}
|
|
@@ -1453,7 +1460,7 @@ export function MediaLibraryBrowse({
|
|
|
1453
1460
|
<ColumnPreview key={previewFile.key} o={previewFile} urlOf={(o) => mediaUrl(o.key)} kindOf={kindOf} kindLabel={KIND_LABEL}
|
|
1454
1461
|
formatSize={formatSize} formatDate={formatDate} width={previewWidth}
|
|
1455
1462
|
renderPreview={(o) => {
|
|
1456
|
-
if (isImage(o.contentType)) return <ImageFrame src={mediaUrl(o.key)} />
|
|
1463
|
+
if (isImage(o.contentType)) return <ImageFrame src={mediaUrl(o.key)} vector={o.contentType === 'image/svg+xml'} />
|
|
1457
1464
|
const poster = posterFor(o.key, keySet)
|
|
1458
1465
|
return <KindPreview o={o} urlOf={(x) => mediaUrl(x.key)} poster={poster ? mediaUrl(poster) : undefined} kindOf={kindOf} kindLabel={KIND_LABEL} />
|
|
1459
1466
|
}} />
|
|
@@ -1631,6 +1638,7 @@ export function MediaLibraryBrowse({
|
|
|
1631
1638
|
const real = rest.join('/')
|
|
1632
1639
|
if (target && target.id !== bucketMeta.id) switchBucket(target.id, real); else setPrefix(real)
|
|
1633
1640
|
}}
|
|
1641
|
+
picked={pickedFile ? { ...pickedFile, key: `${VROOT}${pickedFile.key}` } : null}
|
|
1634
1642
|
onPick={(o) => setPickedFile(o ? { ...o, key: o.key.slice(VROOT.length) } : null)}
|
|
1635
1643
|
quickLook={quickLook && { ...quickLook, files: quickLook.files.map((o) => ({ ...o, key: `${VROOT}${o.key}` })) }}
|
|
1636
1644
|
onQuickLook={(q) => openQuickLook(q && { ...q, files: q.files.map((o) => ({ ...o, key: o.key.slice(VROOT.length) })) })}
|
|
@@ -1646,7 +1654,7 @@ export function MediaLibraryBrowse({
|
|
|
1646
1654
|
stackView={stackView ?? settings.stackView ?? 'list'}
|
|
1647
1655
|
renderPreview={(o) => {
|
|
1648
1656
|
const real = { ...o, key: o.key.slice(VROOT.length) }
|
|
1649
|
-
if (isImage(real.contentType)) return <ImageFrame src={mediaUrl(real.key)} />
|
|
1657
|
+
if (isImage(real.contentType)) return <ImageFrame src={mediaUrl(real.key)} vector={real.contentType === 'image/svg+xml'} />
|
|
1650
1658
|
const poster = posterFor(real.key, keySet)
|
|
1651
1659
|
return <KindPreview o={real} urlOf={(x) => mediaUrl(x.key)} poster={poster ? mediaUrl(poster) : undefined} kindOf={kindOf} kindLabel={KIND_LABEL} />
|
|
1652
1660
|
}}
|
|
@@ -15,7 +15,7 @@ import { minHeightClass } from './sectionHeights.js'
|
|
|
15
15
|
* TWO VARIANTS. `editorial` (default): the two-column band above. `centered`
|
|
16
16
|
* (SectionHeroRound2, 2026-08-26): the quiet mid-page tier — a short rule, a
|
|
17
17
|
* heading, a mono line and Buttons, centred — which was `FoundryCTA`, a second
|
|
18
|
-
* way to render SectionText. FoundryCTA
|
|
18
|
+
* way to render SectionText. FoundryCTA was its alias until 2026-09-25, when it dropped.
|
|
19
19
|
*
|
|
20
20
|
* @param {'full'|'80'|'60'|string} [height='60'] min-height on the family's ladder — full = 100dvh,
|
|
21
21
|
* 80 = 70svh / 80vh, 60 = 50svh / 60vh (default), 40 = 35svh / 40vh; content stays vertically centred inside it
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import Button from '../atoms/Button.jsx'
|
|
2
|
-
import SectionCta from './SectionCta.jsx'
|
|
3
|
-
|
|
4
|
-
/* taxonomy-ok: nests component's Button */
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @deprecated 2026-08-26 — `FoundryCTA` is `SectionCta variant="centered"`
|
|
8
|
-
* (SectionHeroRound2: a second way to render SectionText). Same render; the
|
|
9
|
-
* `action` object(s) become Buttons. Alias kept; removed at the next major.
|
|
10
|
-
*
|
|
11
|
-
* @param {ReactNode} heading · description
|
|
12
|
-
* @param {Object|Array} action one {href, label, variant?, target?, rel?} or an array
|
|
13
|
-
* @param {Function} onNavigate (href, event) => void
|
|
14
|
-
*/
|
|
15
|
-
export default function FoundryCTA({ heading, description, action, onNavigate, className = '' }) {
|
|
16
|
-
const actions = (Array.isArray(action) ? action : [action]).filter(Boolean)
|
|
17
|
-
return (
|
|
18
|
-
<SectionCta
|
|
19
|
-
variant="centered"
|
|
20
|
-
headline={heading}
|
|
21
|
-
body={description}
|
|
22
|
-
className={className}
|
|
23
|
-
actions={actions.length > 0 ? actions.map((act, i) => (
|
|
24
|
-
<Button
|
|
25
|
-
key={i}
|
|
26
|
-
variant={act.variant === 'secondary' ? 'outline' : 'primary'}
|
|
27
|
-
size="md"
|
|
28
|
-
href={act.href}
|
|
29
|
-
target={act.target}
|
|
30
|
-
rel={act.rel}
|
|
31
|
-
onClick={onNavigate ? (e) => onNavigate(act.href, e) : undefined}
|
|
32
|
-
>
|
|
33
|
-
{act.label}
|
|
34
|
-
</Button>
|
|
35
|
-
)) : undefined}
|
|
36
|
-
/>
|
|
37
|
-
)
|
|
38
|
-
}
|