@kolkrabbi/kol-component 0.99.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
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",
|
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(' ')
|
|
@@ -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}
|
|
@@ -87,7 +87,10 @@ const RAMP = {
|
|
|
87
87
|
* `kol-mono-12 text-fg-64` · description `kol-sans-heading-03 text-auto`.
|
|
88
88
|
* Title and type are the pair — same rung, same full ink; only the year
|
|
89
89
|
* steps down. */
|
|
90
|
-
|
|
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' },
|
|
91
94
|
},
|
|
92
95
|
typeface: {
|
|
93
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
|
)
|