@kolkrabbi/kol-component 0.154.1 → 0.156.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.154.1",
3
+ "version": "0.156.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",
@@ -283,7 +283,16 @@ export default function ContentCard({
283
283
  </>
284
284
  ) : box.layout === 'fill-card' ? (
285
285
  <>
286
- <div className="flex-1 min-w-0 min-h-0 relative overflow-hidden" style={{ ...(expanded ? { flex: '0 0 50%' } : null), ...(doFlip ? { perspective: '1000px' } : null) }}>
286
+ {/* THE SPLIT HAS A NARROW RUNG (ContentCardExpandedSplitStacks, kol-mirror
287
+ * 2026-09-01): below `md` the expanded halves STACK — media on top at
288
+ * its own ratio, content full width under it. Side by side, a 350px
289
+ * card gave each half 174 and the prose 126, and one module's specs
290
+ * ran 1177px tall. The 50% half is a `md:` class now, not an inline
291
+ * style — an inline flex-basis has no breakpoint. */}
292
+ <div
293
+ className={`flex-1 min-w-0 min-h-0 relative overflow-hidden ${expanded ? 'max-md:flex-none max-md:aspect-[var(--kol-card-ratio)] md:flex-[0_0_50%]' : ''}`}
294
+ style={{ ...(expanded ? { '--kol-card-ratio': r ?? '3 / 2' } : null), ...(doFlip ? { perspective: '1000px' } : null) }}
295
+ >
287
296
  {/* the FLIP (print): PrintGridCard's turn, verbatim — preserve-3d,
288
297
  * 0.4s ease-out, rotateY(180deg) while `selected`; the consumer's
289
298
  * `onClick` reads the rect off `event.currentTarget` for its
@@ -356,7 +365,7 @@ export default function ContentCard({
356
365
 
357
366
  const common = {
358
367
  'data-tags': isHero && Array.isArray(text.tags) && text.tags.length ? text.tags.join(' ') : undefined,
359
- 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(),
368
+ className: `kol-card group flex ${box.layout === 'canvas' && reveal != null ? 'has-reveal' : ''} ${expanded ? 'flex-col md: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(),
360
369
  style: {
361
370
  /* same reason as ContentRow: rest colours are PROPERTIES, because an
362
371
  * inline background/borderColor outranks the hover class and the step
@@ -2,6 +2,8 @@ import { useId, useState } from 'react'
2
2
  import { Icon } from '@kolkrabbi/kol-icons'
3
3
  import IconFrame from '../atoms/IconFrame.jsx'
4
4
  import { Tooltip } from '../utilities/Popover.jsx'
5
+ import useSectionTheme from '../hooks/useSectionTheme.js'
6
+ import { surfaceClass } from '../utilities/sectionSurface.js'
5
7
 
6
8
  /* taxonomy-ok: nests kol-icons's Icon (a package import the relative-import
7
9
  * check can't see). */
@@ -12,7 +14,20 @@ import { Tooltip } from '../utilities/Popover.jsx'
12
14
  * a square photo with a disclosure control inset on it, and a SHELF that opens
13
15
  * under it (vertical) or beside it (horizontal) — logo, name, mailto, a rack of
14
16
  * socials. The shelf is `bg-surface-inverse` / `text-auto-inverse` at every
15
- * theme: the inverse of the page is the design, not a bug.
17
+ * theme by default: the inverse of the page is the design, not a bug.
18
+ *
19
+ * THE SHELF HAS SEAMS (user, 2026-09-01: "is it dark in light mode? are there
20
+ * light/dark or color variants on the shelf? variant on toggle? variant on
21
+ * logo? padding?"), on the mechanisms the section family already uses:
22
+ * `shelfTheme` is the section `theme` stamp (`data-theme` on the shelf, so
23
+ * every token inside — text, logo ink via currentColor, the mailto's underline
24
+ * — resolves to that theme and `inverse` follows the toggle); `shelfBackground`
25
+ * is the section `background` prop (a named surface or a raw token); with a
26
+ * theme stamped the default paint is that theme's `primary`, without one it is
27
+ * `inverse` with inverse ink, exactly as shipped. `controlVariant` goes straight
28
+ * to the disclosure's `IconFrame`; `pad` is `ContentCard`'s — one step on
29
+ * `--kol-pad-card-*`, overriding the size ramp's padding. The logo is a slot
30
+ * and stays one: its ink follows the shelf, a coloured mark is the asset's.
16
31
  *
17
32
  * THE SHELF SIZES TO ITS CONTENT. The source held it in a fixed box
18
33
  * (`h-60`/`h-44`/`h-32`/`h-24`, `overflow-hidden`) and every vertical size
@@ -54,6 +69,10 @@ import { Tooltip } from '../utilities/Popover.jsx'
54
69
  * @param {'xl'|'lg'|'md'|'sm'} size the ramp + width cap (680 · 480 · 320 · 200); default xl
55
70
  * @param {'vertical'|'horizontal'} orientation shelf under the photo (card grows) or beside it (card holds its square, photo crops)
56
71
  * @param {string} variant alias for the source's names — `'lg-h'` = `size="lg" orientation="horizontal"`, any other value = `size`
72
+ * @param {'inverse'|'light'|'dark'} shelfTheme the shelf's theme scope — stamps `data-theme`; `inverse` = the paired theme of the page, following the toggle; omit for the shipped inverse-surface look
73
+ * @param {'primary'|'secondary'|'tertiary'|'inverse'|'auto'|'none'|string} shelfBackground the shelf's surface — a named surface or a raw utility / token string; default `inverse`, or `primary` once a `shelfTheme` is stamped
74
+ * @param {'primary'|'secondary'|'accent'|'outline'|'ghost'|'nav'|'grey'|'danger'} controlVariant the disclosure's `IconFrame` variant (default `secondary`)
75
+ * @param {'sm'|'md'|'lg'} pad shelf padding on `--kol-pad-card-*` (12 · 16 · 24), overriding the size ramp's
57
76
  * @param {boolean} open controlled open state
58
77
  * @param {boolean} defaultOpen uncontrolled initial state (default false)
59
78
  * @param {Function} onOpenChange `(open) => void`
@@ -81,6 +100,10 @@ export default function ProfileCard({
81
100
  size = 'xl',
82
101
  orientation = 'vertical',
83
102
  variant,
103
+ shelfTheme,
104
+ shelfBackground,
105
+ controlVariant = 'secondary',
106
+ pad,
84
107
  open,
85
108
  defaultOpen = false,
86
109
  onOpenChange,
@@ -99,11 +122,19 @@ export default function ProfileCard({
99
122
  }
100
123
  const shelfId = useId()
101
124
 
125
+ /* the shelf's paint and ink: stamped → that theme's own tokens (`text-auto`
126
+ * resolves inside the stamp); unstamped → the shipped inverse pair */
127
+ const [themeRef, themeStamp] = useSectionTheme(shelfTheme)
128
+ const shelfBg = surfaceClass(shelfBackground, shelfTheme ? 'primary' : 'inverse')
129
+ const ink = !shelfTheme && shelfBg === 'bg-surface-inverse' ? 'text-auto-inverse' : 'text-auto'
130
+ const padStyle = pad ? { padding: `var(--kol-pad-card-${pad})` } : undefined
131
+ const padCls = pad ? '' : ramp.pad
132
+
102
133
  const control = (
103
134
  <div className={`absolute ${ramp.control}`}>
104
135
  <IconFrame
105
136
  name={isOpen ? 'minus' : 'plus'}
106
- variant="secondary"
137
+ variant={controlVariant}
107
138
  size="md"
108
139
  radius="full"
109
140
  aria-expanded={isOpen}
@@ -120,15 +151,15 @@ export default function ProfileCard({
120
151
  /* `[&_svg]` not `[&>svg]`: a brand `Asset` wraps its svg, so the site had to
121
152
  * re-add the sizing on the node it passed — the slot sizes whatever it is
122
153
  * handed, wrapped or bare */
123
- <div className={`inline-flex ${ramp.logo} text-auto-inverse [&>*]:h-full [&_svg]:h-full [&_svg]:w-auto`}>{logo}</div>
154
+ <div className={`inline-flex ${ramp.logo} ${ink} [&>*]:h-full [&_svg]:h-full [&_svg]:w-auto`}>{logo}</div>
124
155
  )
125
156
  const contact = (
126
157
  <div className={`flex flex-col ${horizontal ? 'gap-1.5' : 'gap-1'} antialiased`} style={{ lineHeight: 1 }}>
127
- {name && <span className={`${ramp.text} text-auto-inverse`}>{name}</span>}
158
+ {name && <span className={`${ramp.text} ${ink}`}>{name}</span>}
128
159
  {email && (
129
160
  <a
130
161
  href={`mailto:${email}`}
131
- className={`${ramp.text} text-auto-inverse underline decoration-2 decoration-transparent hover:decoration-current transition-all`}
162
+ className={`${ramp.text} ${ink} underline decoration-2 decoration-transparent hover:decoration-current transition-all`}
132
163
  >
133
164
  {email}
134
165
  </a>
@@ -144,7 +175,7 @@ export default function ProfileCard({
144
175
  target="_blank"
145
176
  rel="noopener noreferrer"
146
177
  aria-label={label}
147
- className={`${ramp.iconBox} flex items-center justify-center text-auto-inverse transition-transform hover:scale-125`}
178
+ className={`${ramp.iconBox} flex items-center justify-center ${ink} transition-transform hover:scale-125`}
148
179
  >
149
180
  <Icon name={icon} size={ramp.iconSize} />
150
181
  </a>
@@ -161,8 +192,8 @@ export default function ProfileCard({
161
192
  >
162
193
  {/* Shelf — LEFT. The inner column holds the open width so the text does
163
194
  * not reflow while the track animates. */}
164
- <div id={shelfId} className="min-w-0 overflow-hidden bg-surface-inverse">
165
- <div className={`h-full ${ramp.pad} flex flex-col justify-between items-start`} style={{ width: HORIZONTAL.shelf }}>
195
+ <div id={shelfId} ref={themeRef} data-theme={themeStamp} className={`min-w-0 overflow-hidden ${shelfBg}`}>
196
+ <div className={`h-full ${padCls} flex flex-col justify-between items-start`} style={{ width: HORIZONTAL.shelf, ...padStyle }}>
166
197
  {lockup}
167
198
  <div className="flex flex-col gap-8">
168
199
  {contact}
@@ -192,8 +223,8 @@ export default function ProfileCard({
192
223
  className="grid transition-[grid-template-rows] duration-300 ease-in-out"
193
224
  style={{ gridTemplateRows: isOpen ? '1fr' : '0fr' }}
194
225
  >
195
- <div id={shelfId} className="min-h-0 overflow-hidden bg-surface-inverse">
196
- <div className={`${ramp.pad} flex justify-between items-end`}>
226
+ <div id={shelfId} ref={themeRef} data-theme={themeStamp} className={`min-h-0 overflow-hidden ${shelfBg}`}>
227
+ <div className={`${padCls} flex justify-between items-end`} style={padStyle}>
197
228
  <div className="self-stretch flex flex-col justify-between items-start">
198
229
  {lockup}
199
230
  {contact}
@@ -1,6 +1,6 @@
1
1
  import SectionCardItem from '../molecules/SectionCardItem.jsx'
2
2
  import { FULL_BLEED } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import SectionText, { HEADLINE_ROLE } from '../molecules/SectionText.jsx'
5
5
  import useSectionTheme from '../hooks/useSectionTheme.js'
6
6
  import { minHeightClass } from './sectionHeights.js'
@@ -1,6 +1,6 @@
1
1
  import Button from '../atoms/Button.jsx'
2
2
  import { FULL_BLEED } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import SectionText from '../molecules/SectionText.jsx'
5
5
  import { minHeightClass } from './sectionHeights.js'
6
6
 
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react'
2
2
  import { FULL_BLEED } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import { Accordion, AccordionPanel } from '../molecules/Accordion.jsx'
5
5
  import SectionText from '../molecules/SectionText.jsx'
6
6
  import { minHeightClass } from './sectionHeights.js'
@@ -1,6 +1,6 @@
1
1
  import { isValidElement } from 'react'
2
2
  import { FULL_BLEED, bleedClass } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import HlsVideo from '../atoms/HlsVideo.jsx'
5
5
  import Image from '../atoms/Image.jsx'
6
6
  import OverlayGlassPanel from '../utilities/OverlayGlassPanel.jsx'
@@ -1,6 +1,6 @@
1
1
  import { useId, useState } from 'react'
2
2
  import { bleedClass } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import Input from '../atoms/Input.jsx'
5
5
  import Button from '../atoms/Button.jsx'
6
6
  import SectionText from '../molecules/SectionText.jsx'
@@ -1,6 +1,6 @@
1
1
  import SectionText from '../molecules/SectionText.jsx'
2
2
  import { FULL_BLEED } from './sectionBleed.js'
3
- import { surfaceClass } from './sectionSurface.js'
3
+ import { surfaceClass } from '../utilities/sectionSurface.js'
4
4
  import useSectionTheme from '../hooks/useSectionTheme.js'
5
5
  import { minHeightClass } from './sectionHeights.js'
6
6
 
File without changes