@kolkrabbi/kol-component 0.41.0 → 0.43.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 +1 -1
- package/src/atoms/AudioPlayer.jsx +51 -0
- package/src/index.js +4 -0
- package/src/organisms/FeatureSplit.jsx +53 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioPlayer — the interactive audio atom: one native <audio> with the UA's
|
|
3
|
+
* own control strip, and an optional label line above it.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT EXISTS. Audio was the one media kind the design system had nothing for,
|
|
6
|
+
* so every consumer hand-rolled a bare `<audio controls>` and inherited whatever
|
|
7
|
+
* the browser painted. The MediaLibrary widening (kol-component 0.39.0) made
|
|
8
|
+
* audio objects arrive — 116 sound files in `kol-vault-media` alone — and left
|
|
9
|
+
* minting this as the open taxonomy call; this is that call made: an ATOM,
|
|
10
|
+
* beside HlsVideo.
|
|
11
|
+
*
|
|
12
|
+
* THE CONTRAST WITH HlsVideo, which is the thing to read before touching either.
|
|
13
|
+
* Same tier, same shape — one native media element, all layout via `className`,
|
|
14
|
+
* no composition — and **inverted intent**. HlsVideo is deliberately inert:
|
|
15
|
+
* `pointer-events: none`, `controls={false}`, plus the hardening set (no PiP, no
|
|
16
|
+
* download, no fullscreen, no remote playback, no context menu). It is
|
|
17
|
+
* decorative. This atom exists **to be operated**, so none of that hardening
|
|
18
|
+
* crosses over, and `controls` is not a prop — an audio player without controls
|
|
19
|
+
* is not a variant, it is a different component.
|
|
20
|
+
*
|
|
21
|
+
* The native strip is UA-painted and not themeable past `color-scheme`. Do not
|
|
22
|
+
* reach for pseudo-element hacks; a branded transport would be a separate
|
|
23
|
+
* `AudioTransport` molecule built on this atom.
|
|
24
|
+
*
|
|
25
|
+
* Layout is the call site's: the padding, the centring and the width that lived
|
|
26
|
+
* in the consumer source are all call-site concerns and none of them are baked
|
|
27
|
+
* in here.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} src audio URL; renders nothing when absent (the guard
|
|
30
|
+
* style the other media atoms use)
|
|
31
|
+
* @param {string} [label] line above the player; the element is omitted entirely
|
|
32
|
+
* when absent. Authored casing — no text-transform
|
|
33
|
+
* @param {'none'|'metadata'|'auto'} [preload='metadata'] native preload hint
|
|
34
|
+
* @param {string} [className] all layout/sizing (consumer-supplied)
|
|
35
|
+
*/
|
|
36
|
+
export default function AudioPlayer({
|
|
37
|
+
src,
|
|
38
|
+
label,
|
|
39
|
+
preload = 'metadata',
|
|
40
|
+
className = '',
|
|
41
|
+
...props
|
|
42
|
+
}) {
|
|
43
|
+
if (!src) return null
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<figure className={`kol-audio-player flex flex-col gap-3 ${className}`.trim()}>
|
|
47
|
+
{label && <figcaption className="kol-mono-12 text-fg-48">{label}</figcaption>}
|
|
48
|
+
<audio src={src} preload={preload} controls className="w-full" {...props} />
|
|
49
|
+
</figure>
|
|
50
|
+
)
|
|
51
|
+
}
|
package/src/index.js
CHANGED
|
@@ -33,6 +33,10 @@ export { default as ExitPreview } from './utilities/ExitPreview.jsx'
|
|
|
33
33
|
export { default as Figure } from './atoms/Figure.jsx'
|
|
34
34
|
export { default as FullscreenOverlay } from './utilities/FullscreenOverlay.jsx'
|
|
35
35
|
export { default as HlsVideo } from './atoms/HlsVideo.jsx'
|
|
36
|
+
/* AudioPlayer — HlsVideo's opposite number: same tier and shape, inverted
|
|
37
|
+
* intent (built to be operated, not decorative). See its header before editing
|
|
38
|
+
* either one. */
|
|
39
|
+
export { default as AudioPlayer } from './atoms/AudioPlayer.jsx'
|
|
36
40
|
export { default as Input } from './atoms/Input.jsx'
|
|
37
41
|
export { default as Label } from './atoms/Label.jsx'
|
|
38
42
|
export { default as LabeledControl } from './molecules/LabeledControl.jsx'
|
|
@@ -16,12 +16,37 @@
|
|
|
16
16
|
* `text-transform: uppercase` was dropped per KOL casing rules; author those
|
|
17
17
|
* strings in their final case at the call site.
|
|
18
18
|
*
|
|
19
|
+
* THE SECTION ANATOMY, ONCE (SectionSplit ruling, 2026-08-15). The brief asked
|
|
20
|
+
* for a new `SectionSplit` — media slot beside kicker/heading/body/actions,
|
|
21
|
+
* with a flip — and named eleven hand-built kol-website sections as evidence.
|
|
22
|
+
* That is this component: the anatomy already shipped here, and a second one
|
|
23
|
+
* beside it would be the very duplication the brief exists to end. It grew the
|
|
24
|
+
* three things it genuinely lacked instead — `flip`, `titleSize`, `mediaAspect`.
|
|
25
|
+
*
|
|
26
|
+
* ON THREADING PER-SITE TYPE CLASSES — the brief's named "core design
|
|
27
|
+
* question". The answer is that you do not thread one: a consumer picks a ROLE
|
|
28
|
+
* (`titleSize`), and the component emits exactly one type class for the
|
|
29
|
+
* heading. Passing `kol-sans-heading-01` in alongside `.kol-feature-split-pull`
|
|
30
|
+
* would put two equal-specificity rules on one element and let sheet load order
|
|
31
|
+
* decide the winner — the failure ARCHITECTURE §5 records, and the 2026-07-30
|
|
32
|
+
* law that a component's type lives in its own rule. `columnClassName` and
|
|
33
|
+
* friends remain, for LAYOUT.
|
|
34
|
+
*
|
|
19
35
|
* @param {ReactNode} kicker mono eyebrow above the title (accent color)
|
|
20
36
|
* @param {ReactNode} title display pull headline; `<em>` renders as the italic accent
|
|
37
|
+
* @param {'pull'|'display-01'|'display-02'|'display-03'|'heading-01'|'heading-02'|'heading-03'|'heading-04'|'heading-05'} [titleSize='pull']
|
|
38
|
+
* which type ROLE the heading wears — one class, never stacked
|
|
21
39
|
* @param {ReactNode} body lede paragraph
|
|
22
40
|
* @param {{num: ReactNode, label: string}[]} meta stats strip (mutually exclusive with `ctas`)
|
|
23
41
|
* @param {ReactNode} ctas button row (mutually exclusive with `meta`)
|
|
24
|
-
* @param {ReactNode} media image/video for the visual
|
|
42
|
+
* @param {ReactNode} media image/video/interactive node for the visual
|
|
43
|
+
* column; omit for the text-only section (the /CONNECT band shape)
|
|
44
|
+
* @param {string} [mediaAspect='4/5'] aspect ratio of the media frame
|
|
45
|
+
* @param {boolean} [mediaHover=false] zoom the media on frame hover — the same
|
|
46
|
+
* 1.03 / 300ms / reduced-motion-safe treatment CardFeatureItem uses, so the
|
|
47
|
+
* estate has one motion vocabulary instead of a per-section re-decision
|
|
48
|
+
* @param {boolean} [flip=false] media first, text second — the order swaps at
|
|
49
|
+
* both widths, so the stacked layout leads with the media too
|
|
25
50
|
* @param {ReactNode} caption mono caption + gradient veil over the media
|
|
26
51
|
* @param {string} bgImage URL for an inline cover background on the section
|
|
27
52
|
* @param {boolean} fullBleed span the full viewport width (100vw breakout)
|
|
@@ -29,13 +54,29 @@
|
|
|
29
54
|
* @param {string} innerClassName extra classes on the grid wrapper
|
|
30
55
|
* @param {string} columnClassName extra classes on the text column
|
|
31
56
|
*/
|
|
57
|
+
const TITLE_ROLE = {
|
|
58
|
+
pull: 'kol-feature-split-pull',
|
|
59
|
+
'display-01': 'kol-sans-display-01',
|
|
60
|
+
'display-02': 'kol-sans-display-02',
|
|
61
|
+
'display-03': 'kol-sans-display-03',
|
|
62
|
+
'heading-01': 'kol-sans-heading-01',
|
|
63
|
+
'heading-02': 'kol-sans-heading-02',
|
|
64
|
+
'heading-03': 'kol-sans-heading-03',
|
|
65
|
+
'heading-04': 'kol-sans-heading-04',
|
|
66
|
+
'heading-05': 'kol-sans-heading-05',
|
|
67
|
+
}
|
|
68
|
+
|
|
32
69
|
export default function FeatureSplit({
|
|
33
70
|
kicker,
|
|
34
71
|
title,
|
|
72
|
+
titleSize = 'pull',
|
|
35
73
|
body,
|
|
36
74
|
meta,
|
|
37
75
|
ctas,
|
|
38
76
|
media,
|
|
77
|
+
mediaAspect = '4/5',
|
|
78
|
+
mediaHover = false,
|
|
79
|
+
flip = false,
|
|
39
80
|
caption,
|
|
40
81
|
bgImage,
|
|
41
82
|
fullBleed = false,
|
|
@@ -53,9 +94,14 @@ export default function FeatureSplit({
|
|
|
53
94
|
style={sectionStyle}
|
|
54
95
|
>
|
|
55
96
|
<div className={`max-w-[1200px] mx-auto grid grid-cols-1 min-[901px]:grid-cols-2 items-center gap-[clamp(48px,6vw,96px)] ${innerClassName}`.trim()}>
|
|
56
|
-
|
|
97
|
+
{/* `order` rather than `flex-row-reverse`: the grid is one column below
|
|
98
|
+
* 901px, and DOM order is what decides the stack there. Reversing a row
|
|
99
|
+
* would flip the wide layout and leave the narrow one text-first. */}
|
|
100
|
+
<div
|
|
101
|
+
className={`flex flex-col gap-4 max-w-[640px] ${flip ? 'order-2' : ''} ${columnClassName}`.replace(/\s+/g, ' ').trim()}
|
|
102
|
+
>
|
|
57
103
|
{kicker && <span className="kol-feature-split-kicker">{kicker}</span>}
|
|
58
|
-
{title && <h1 className=
|
|
104
|
+
{title && <h1 className={TITLE_ROLE[titleSize] ?? TITLE_ROLE.pull}>{title}</h1>}
|
|
59
105
|
{body && <p className="kol-feature-split-body">{body}</p>}
|
|
60
106
|
{meta && meta.length > 0 && (
|
|
61
107
|
<div className="kol-feature-split-meta flex flex-wrap gap-y-7 gap-x-12 pt-4">
|
|
@@ -70,7 +116,10 @@ export default function FeatureSplit({
|
|
|
70
116
|
{ctas && <div className="flex flex-wrap gap-4 pt-2">{ctas}</div>}
|
|
71
117
|
</div>
|
|
72
118
|
{media && (
|
|
73
|
-
<div
|
|
119
|
+
<div
|
|
120
|
+
className={`kol-feature-split-visual relative rounded-[var(--kol-radius-sm)] overflow-hidden ${mediaHover ? 'is-hoverable' : ''} ${flip ? 'order-1' : ''}`.replace(/\s+/g, ' ').trim()}
|
|
121
|
+
style={{ aspectRatio: mediaAspect }}
|
|
122
|
+
>
|
|
74
123
|
{media}
|
|
75
124
|
{caption && <div className="kol-feature-split-visual-veil" aria-hidden="true" />}
|
|
76
125
|
{caption && <span className="kol-feature-split-visual-caption">{caption}</span>}
|