@kolkrabbi/kol-component 0.98.0 → 0.100.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 +3 -3
- package/src/atoms/Tag.jsx +1 -2
- package/src/molecules/ContentCard.jsx +6 -3
- package/src/molecules/ContentRow.jsx +15 -5
- package/src/molecules/ContentText.jsx +7 -2
- package/src/molecules/SectionText.jsx +1 -1
- package/src/organisms/ContentCollection.jsx +4 -0
- package/src/organisms/ContentFilters.jsx +15 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.100.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",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"framer-motion": "^12.0.0",
|
|
25
25
|
"gsap": "^3.13.0",
|
|
26
26
|
"hls.js": "^1.6.0",
|
|
27
|
-
"opentype.js": "^1.3.4",
|
|
27
|
+
"opentype.js": "^1.3.4 || ^2.0.0",
|
|
28
28
|
"react": "^18.3.0 || ^19.0.0",
|
|
29
29
|
"react-dom": "^18.3.0 || ^19.0.0",
|
|
30
30
|
"react-router-dom": "^6.0.0 || ^7.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@kolkrabbi/kol-icons": "^0.
|
|
33
|
+
"@kolkrabbi/kol-icons": "^0.20.0"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"src",
|
package/src/atoms/Tag.jsx
CHANGED
|
@@ -75,13 +75,12 @@ export default function Tag({
|
|
|
75
75
|
* SINGLE-LINE chrome — that is exactly the kol-helper-* ramp, whose defining
|
|
76
76
|
* property is `line-height: 1`. The sizes match the scale 1:1 (10/12/14), so
|
|
77
77
|
* this is adopting the existing system, not restating it. */
|
|
78
|
-
|
|
78
|
+
/* the type rides `.kol-tag--{size}` in kol-theme since 0.69.0 (WorkListingRowsAndFilters) */
|
|
79
79
|
|
|
80
80
|
const classes = [
|
|
81
81
|
'kol-tag',
|
|
82
82
|
VARIANTS[variant] ?? VARIANTS.primary,
|
|
83
83
|
`kol-tag--${size}`,
|
|
84
|
-
TYPE[size] ?? TYPE.sm,
|
|
85
84
|
active ? 'is-active' : '',
|
|
86
85
|
className
|
|
87
86
|
].filter(Boolean).join(' ')
|
|
@@ -44,7 +44,8 @@ const BOX = {
|
|
|
44
44
|
* like I've said that before" — the ListingCardThumbBorder ruling): the selected
|
|
45
45
|
* state reads from the checked ToggleCheckbox, not a fg-64 border */
|
|
46
46
|
default: { layout: 'stack', border: null, bg: 'var(--kol-fg-02)', pad: 'var(--kol-pad-card-sm)' },
|
|
47
|
-
|
|
47
|
+
/* frameHover: the frame steps fg-04 → fg-16 on hover (ShellHomeSystem, 2026-08-27 — the tertiary fill alone drowned the fg-04 border) */
|
|
48
|
+
catalog: { layout: 'fill-card', border: 'var(--kol-fg-04)', bg: 'var(--kol-fg-04)', pad: 'var(--kol-pad-card-sm) var(--kol-pad-card-md)', plateTop: true, plateBg: 'var(--kol-surface-primary)', frameHover: 'var(--kol-fg-16)' },
|
|
48
49
|
/* flip: PrintGridCard's 3D turn on `isFlipped` → `selected` (ContentRowsAndPrintCard, 2026-08-27) */
|
|
49
50
|
print: { layout: 'fill-card', border: null, bg: 'var(--kol-surface-secondary)', pad: 'var(--kol-pad-card-sm) var(--kol-pad-card-md)', plateTop: true, flip: true },
|
|
50
51
|
article: { layout: 'stack', border: null, bg: null, pad: '0', mediaGap: 'var(--kol-spacing-4)' },
|
|
@@ -196,7 +197,8 @@ export default function ContentCard({
|
|
|
196
197
|
frame: frame ?? MEDIA[variant]?.frame ?? false,
|
|
197
198
|
ring: ring ?? MEDIA[variant]?.ring ?? false,
|
|
198
199
|
borderHover: MEDIA[variant]?.borderHover ?? false,
|
|
199
|
-
|
|
200
|
+
/* a catalog card with REAL media is image-led: it zooms (ShellHomeSystem) */
|
|
201
|
+
zoom: zoom ?? (isHero ? 'hero' : variant === 'catalog' ? media != null : MEDIA[variant]?.zoom ?? false),
|
|
200
202
|
fade: MEDIA[variant]?.fade ?? false,
|
|
201
203
|
}
|
|
202
204
|
|
|
@@ -301,7 +303,7 @@ export default function ContentCard({
|
|
|
301
303
|
|
|
302
304
|
const common = {
|
|
303
305
|
'data-tags': isHero && Array.isArray(text.tags) && text.tags.length ? text.tags.join(' ') : undefined,
|
|
304
|
-
className: `kol-card group flex ${box.layout === 'canvas' && reveal != null ? 'has-reveal' : ''} ${expanded ? 'flex-row-reverse' : 'flex-col'} ${box.layout === 'drawer' ? 'relative overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${framed ? 'overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${box.border ? 'border' : ''} ${box.layout === 'canvas' ? 'relative' : ''} ${interactive ? 'cursor-pointer select-none' : ''} ${hoverBg && interactive ? 'kol-content-hover' : ''} ${className}`.trim(),
|
|
306
|
+
className: `kol-card group flex ${box.layout === 'canvas' && reveal != null ? 'has-reveal' : ''} ${expanded ? 'flex-row-reverse' : 'flex-col'} ${box.layout === 'drawer' ? 'relative overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${framed ? 'overflow-hidden rounded-[var(--kol-radius-sm)]' : ''} ${box.border ? 'border' : ''} ${box.layout === 'canvas' ? 'relative' : ''} ${interactive ? 'cursor-pointer select-none' : ''} ${hoverBg && interactive ? 'kol-content-hover' : ''} ${interactive && box.frameHover ? 'kol-content-hover-frame' : ''} ${className}`.trim(),
|
|
305
307
|
style: {
|
|
306
308
|
/* same reason as ContentRow: rest colours are PROPERTIES, because an
|
|
307
309
|
* inline background/borderColor outranks the hover class and the step
|
|
@@ -309,6 +311,7 @@ export default function ContentCard({
|
|
|
309
311
|
'--kol-card-bg': box.bg ?? undefined,
|
|
310
312
|
'--kol-card-border': box.border ? (selected ? 'var(--kol-fg-64)' : box.border) : undefined,
|
|
311
313
|
'--kol-content-hover-bg': hoverBg ?? undefined,
|
|
314
|
+
'--kol-content-hover-border': box.frameHover ?? undefined,
|
|
312
315
|
aspectRatio: box.height || expanded ? undefined : (box.layout !== 'stack' ? r : undefined),
|
|
313
316
|
height: box.height,
|
|
314
317
|
gridColumn: expanded ? 'span 2' : undefined,
|
|
@@ -12,7 +12,8 @@ import ContentText from './ContentText.jsx'
|
|
|
12
12
|
*
|
|
13
13
|
* @param {string} variant default | catalog | print | article | work | typeface
|
|
14
14
|
* @param {ReactNode} media thumb content (omit → placeholder)
|
|
15
|
-
* @param {number}
|
|
15
|
+
* @param {number|'fill'} thumb thumb edge px — overrides the ruled default; 0 hides; `'fill'` = a square
|
|
16
|
+
* the height of the row's content, whatever the rung (WorkListingRowsAndFilters)
|
|
16
17
|
* @param {string} ratio thumb aspect-ratio — overrides the ruled default
|
|
17
18
|
* @param {number} paddingY vertical padding px — overrides the ruled default
|
|
18
19
|
* @param {boolean} selected
|
|
@@ -38,7 +39,12 @@ const BOX = {
|
|
|
38
39
|
article: { thumb: 120, ratio: '1 / 1', pad: '0', gap: S6, align: 'items-start', thumbBg: 'var(--kol-fg-12)', hover: 'var(--kol-oq-02)' },
|
|
39
40
|
/* work and typeface step UP at md — the shipped rows both do, and a work row
|
|
40
41
|
* at a fixed 96 cannot hold the display-03 line it was ruled to carry. */
|
|
41
|
-
|
|
42
|
+
/* RULED ON /work (WorkListingRowsAndFilters, 2026-08-27): min-height 168 ("div
|
|
43
|
+
* 8"), 16 padding at every width ("16px padding, that's fine"), the thumb FILLS
|
|
44
|
+
* the content height (136 in a 168 row) with no hairline, the frame steps
|
|
45
|
+
* transparent → fg-08 on hover ("0 → 16 is a big jump"). No md step: the rows
|
|
46
|
+
* were approved at their base values. */
|
|
47
|
+
work: { thumb: 'fill', ratio: '1 / 1', pad: S4, gap: S4, frame: 'transparent', frameHover: 'var(--kol-fg-08)', bg: 'var(--kol-surface-secondary)', minH: 168, align: 'items-stretch', thumbRadius: 'var(--kol-radius-xs)', thumbBorder: false },
|
|
42
48
|
/* typeface's row is a COLUMN, not a line: a header (name/styles left,
|
|
43
49
|
* classification/year right) with a full-width specimen band under it. The
|
|
44
50
|
* shipped item is `flex-col gap-6`, and forcing it into the horizontal
|
|
@@ -63,6 +69,7 @@ export default function ContentRow({
|
|
|
63
69
|
}) {
|
|
64
70
|
const box = BOX[variant] ?? BOX.default
|
|
65
71
|
const thumbPx = thumb ?? box.thumb
|
|
72
|
+
const padY = (paddingY != null ? `${paddingY}px` : String(box.pad)).trim().split(/\s+/)[0]
|
|
66
73
|
|
|
67
74
|
/* The md: STEP is a custom property, not a Tailwind variant. Tailwind cannot
|
|
68
75
|
* generate `md:min-h-40` from package source (the SegmentedToggle rule), and
|
|
@@ -76,7 +83,10 @@ export default function ContentRow({
|
|
|
76
83
|
'--kol-row-gap-md': box.gapMd,
|
|
77
84
|
'--kol-row-min-h': box.minH != null ? `${box.minH}px` : undefined,
|
|
78
85
|
'--kol-row-min-h-md': box.minHMd != null ? `${box.minHMd}px` : undefined,
|
|
79
|
-
|
|
86
|
+
/* `fill` = the rung minus the vertical padding — a definite number, not a
|
|
87
|
+
* stretch (a min-height row has no definite cross size, so a stretched
|
|
88
|
+
* aspect-ratio square resolved to the image's intrinsic width) */
|
|
89
|
+
'--kol-row-thumb': thumbPx === 'fill' ? `calc(${box.minH ?? 96}px - 2 * ${padY})` : `${thumbPx}px`,
|
|
80
90
|
'--kol-row-thumb-md': box.thumbMd != null ? `${box.thumbMd}px` : undefined,
|
|
81
91
|
'--kol-content-hover-bg': box.hover,
|
|
82
92
|
'--kol-content-hover-border': box.frameHover,
|
|
@@ -103,7 +113,7 @@ export default function ContentRow({
|
|
|
103
113
|
</>
|
|
104
114
|
) : (
|
|
105
115
|
<>
|
|
106
|
-
{thumbPx > 0 && (
|
|
116
|
+
{(thumbPx === 'fill' || thumbPx > 0) && (
|
|
107
117
|
/* THE THUMB IS A FIXED SQUARE BOX (ContentRowsAndPrintCard, user
|
|
108
118
|
* 2026-08-27: "the image should not control height, image should fit
|
|
109
119
|
* the row image placeholder") — `--kol-row-thumb` wide, square, at the
|
|
@@ -111,7 +121,7 @@ export default function ContentRow({
|
|
|
111
121
|
* media object-covers into it; row height = max(thumb, text). This
|
|
112
122
|
* retires the 2026-08-15 fill-the-height ruling and the `thumbSquare`
|
|
113
123
|
* exception it needed. Rows never zoom their thumb — cards keep theirs. */
|
|
114
|
-
<div className=
|
|
124
|
+
<div className={`kol-row-thumb shrink-0 self-start ${thumbPx === 'fill' ? 'is-fill' : ''}`.trim()}>
|
|
115
125
|
<ContentMedia
|
|
116
126
|
ratio={null}
|
|
117
127
|
border={box.thumbBorder ?? false}
|
|
@@ -52,7 +52,9 @@ const RAMP = {
|
|
|
52
52
|
row: { title: 'kol-sans-heading-05 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta' },
|
|
53
53
|
},
|
|
54
54
|
catalog: {
|
|
55
|
-
|
|
55
|
+
/* detail truncates (ShellHomeSystem, 2026-08-27): a wrapping blurb set three
|
|
56
|
+
* cards' plates at three heights and misaligned the media */
|
|
57
|
+
card: { title: 'kol-mono-14 text-emphasis', detail: 'kol-mono-10 text-meta truncate' },
|
|
56
58
|
row: { title: 'kol-mono-12 text-emphasis', detail: 'kol-mono-10 text-meta' },
|
|
57
59
|
},
|
|
58
60
|
print: {
|
|
@@ -85,7 +87,10 @@ const RAMP = {
|
|
|
85
87
|
* `kol-mono-12 text-fg-64` · description `kol-sans-heading-03 text-auto`.
|
|
86
88
|
* Title and type are the pair — same rung, same full ink; only the year
|
|
87
89
|
* steps down. */
|
|
88
|
-
|
|
90
|
+
/* RULED ON /work beside the local WorkListItem (WorkListingRowsAndFilters,
|
|
91
|
+
* 2026-08-27): the typeface row's voices — title + type kol-mono-14 full
|
|
92
|
+
* ink, the year kol-mono-12 fg-64. Body unchanged (the site passes its face). */
|
|
93
|
+
row: { title: 'kol-mono-14 uppercase text-emphasis truncate', body: 'kol-sans-heading-03 leading-tight text-emphasis truncate', meta: 'kol-mono-14 text-emphasis', date: 'kol-mono-12 text-fg-64', tags: 'flex flex-wrap items-center gap-1.5' },
|
|
89
94
|
},
|
|
90
95
|
typeface: {
|
|
91
96
|
/* RULED ON SCREEN (TypefaceCardAndRow, kol-website 2026-08-27): name and
|
|
@@ -85,7 +85,7 @@ export default function SectionText({
|
|
|
85
85
|
{/* `kol-section-text-eyebrow` = uppercase by ROLE (kol-theme ≥0.55.0);
|
|
86
86
|
* `labelClass` is the voice riding beside it */}
|
|
87
87
|
{eb && <span className={cls(`kol-section-text-eyebrow ${ebClass}`, 'eyebrow')} style={slotStyle.eyebrow ?? slotStyle.label}>{eb}</span>}
|
|
88
|
-
{headline && <Headline className={cls(
|
|
88
|
+
{headline && <Headline className={cls(`kol-section-text-headline ${headlineClass ?? (HEADLINE_ROLE[headlineSize] ?? HEADLINE_ROLE['heading-02'])}${headlineCase === 'upper' ? ' kol-section-text-caps' : ''}`, 'headline')} style={slotStyle.headline}>{headline}</Headline>}
|
|
89
89
|
{body && (typeof body === 'string' ? <p className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</p> : <div className={cls(bodyClass, 'body')} style={slotStyle.body}>{body}</div>)}
|
|
90
90
|
{children}
|
|
91
91
|
{actions && <div className={cls(actionsClass, 'actions')} style={slotStyle.actions}>{actions}</div>}
|
|
@@ -98,6 +98,10 @@ export default function ContentCollection({
|
|
|
98
98
|
key={form}
|
|
99
99
|
className={`m-0 list-none p-0 ${colsCls} ${className}`.replace(/\s+/g, ' ').trim()}
|
|
100
100
|
style={{
|
|
101
|
+
/* the ROW's md step is a container query (kol-theme .kol-row) and nothing
|
|
102
|
+
* declared a container until 2026-08-27 (WorkListingRowsAndFilters) — the
|
|
103
|
+
* wall is one, so a row inside it steps on the wall's own width */
|
|
104
|
+
containerType: 'inline-size',
|
|
101
105
|
display: 'grid',
|
|
102
106
|
/* with `cols` the classes carry the tracks — an inline template would outrank them */
|
|
103
107
|
gridTemplateColumns: colsCls
|
|
@@ -42,6 +42,8 @@ import IconFrame from '../atoms/IconFrame.jsx'
|
|
|
42
42
|
* @param {boolean} props.titleUppercase — cases the title (default false)
|
|
43
43
|
* @param {string} props.labelClassName — filter-group label type/ink
|
|
44
44
|
* @param {boolean} props.labelUppercase — cases the group label (default true)
|
|
45
|
+
* groups: `{ label, key, values, stack?, className?, wrapClassName? }` — a group with ≤ 6 values stacks by
|
|
46
|
+
* default (`stack: false` opts out); `className` / `wrapClassName` are per-group seams
|
|
45
47
|
* @param {string} props.tagVariant — filter chip variant ('primary' grey fill)
|
|
46
48
|
* @param {string} props.tagSize — filter chip size
|
|
47
49
|
* @param {string} props.tagActiveClassName — chip ink, selected
|
|
@@ -240,8 +242,13 @@ const ContentFilters = ({
|
|
|
240
242
|
</div>
|
|
241
243
|
) : null
|
|
242
244
|
|
|
243
|
-
const renderFilterGroup = (group) =>
|
|
244
|
-
|
|
245
|
+
const renderFilterGroup = (group) => {
|
|
246
|
+
/* a SHORT group (≤ 6 values) stacks by default (WorkListingRowsAndFilters, 2026-08-27 —
|
|
247
|
+
* user: "why do I have to say it every time? why do you default to 50/50?");
|
|
248
|
+
* `stack: false` opts out, `stack: true` forces */
|
|
249
|
+
const stacked = group.stack ?? (group.values?.length ?? 0) <= 6
|
|
250
|
+
return (
|
|
251
|
+
<div key={group.key} className={`flex flex-col gap-3 ${stacked ? 'shrink-0' : 'min-w-0 flex-1'} ${group.className ?? ''}`.trim()}>
|
|
245
252
|
{/* THE CATEGORY LABEL IS THE ACTIVE INK — `kol-helper-12` at `text-fg-96`,
|
|
246
253
|
* the same full opacity a SELECTED layout item wears (user ruling
|
|
247
254
|
* 2026-08-15: "TAGS and other categories are ACTIVE state full opacity").
|
|
@@ -255,7 +262,9 @@ const ContentFilters = ({
|
|
|
255
262
|
<h4 className={labelClassName} style={labelUppercase ? { textTransform: 'uppercase' } : undefined}>
|
|
256
263
|
{group.label}
|
|
257
264
|
</h4>
|
|
258
|
-
|
|
265
|
+
{/* the fluid group keeps 48px of room on its right (it ran flush to the
|
|
266
|
+
* panel's edge); `group.wrapClassName` is the per-group seam */}
|
|
267
|
+
<div className={`${stacked ? 'flex flex-col items-start gap-2' : 'flex flex-wrap gap-2 pr-12'} ${group.wrapClassName ?? ''}`.trim()}>
|
|
259
268
|
{group.values.map((value) => (
|
|
260
269
|
<Tag
|
|
261
270
|
key={value}
|
|
@@ -272,6 +281,7 @@ const ContentFilters = ({
|
|
|
272
281
|
</div>
|
|
273
282
|
</div>
|
|
274
283
|
)
|
|
284
|
+
}
|
|
275
285
|
|
|
276
286
|
return (
|
|
277
287
|
/* minHeight 0 on both this root and the body below: without it a flex
|
|
@@ -466,7 +476,8 @@ const ContentFilters = ({
|
|
|
466
476
|
)}
|
|
467
477
|
|
|
468
478
|
<div style={{ marginTop: 'var(--kol-spacing-6)', display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
|
|
469
|
-
{
|
|
479
|
+
{/* the items panel is a CONTAINER (WorkListingRowsAndFilters, 2026-08-27) — rows step on its width */}
|
|
480
|
+
<div style={{ containerType: 'inline-size' }}>{renderItem(filteredItems, viewMode, layout)}</div>
|
|
470
481
|
</div>
|
|
471
482
|
</div>
|
|
472
483
|
)
|