@kolkrabbi/kol-component 0.176.0 → 0.178.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.176.0",
3
+ "version": "0.178.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",
@@ -18,7 +18,7 @@ import { glyphSize } from '../hooks/glyphLadders.js'
18
18
  * @param {string} props.iconRight - Icon name to display on the right
19
19
  * @param {string} props.iconLeftHover - Icon to show on hover (left position)
20
20
  * @param {string} props.iconRightHover - Icon to show on hover (right position)
21
- * @param {'primary'|'secondary'|'outline'|'ghost'|'grey'|'sunken'} props.tone - the ground (tone-is-the-ground-axis, 2026-09-03) — wins over `variant` on the same element; unset = inherit the wrapper's. `sunken` = the control set's dark well + fg-96 ink (ControlToneSunken); `inverse` aliased
21
+ * @param {'primary'|'secondary'|'inverted'|'outline'|'ghost'|'grey'|'sunken'} props.tone - the ground (`secondary` = the page surface, `inverted` = the text colour as fill — what `variant="secondary"` paints) (tone-is-the-ground-axis, 2026-09-03) — wins over `variant` on the same element; unset = inherit the wrapper's. `sunken` = the control set's dark well + fg-96 ink (ControlToneSunken); `inverse` aliased
22
22
  * @param {string} props.iconOnly - Icon name for icon-only button
23
23
  * @param {string} props.iconOnlyHover - Icon to show on hover (icon-only)
24
24
  * @param {boolean} props.animateIcon - Disable default hover states to focus on icon animation
package/src/index.js CHANGED
@@ -159,6 +159,7 @@ export { nearestRatio, RATIOS } from './utilities/ratios.js'
159
159
  * useDragResize wear ONE implementation (OneGrabGestureBothRails, 2026-08-30) */
160
160
  export { default as useGrabEdge } from './hooks/useGrabEdge.js'
161
161
  export { default as markdownToHtml, inlineToHtml } from './utilities/markdownToHtml.js'
162
+ export { default as formatSize } from './utilities/formatSize.js'
162
163
  export { default as RecordManager } from './organisms/RecordManager.jsx'
163
164
  export { default as SpectrumGrid } from './organisms/SpectrumGrid.jsx'
164
165
  export { default as Table } from './organisms/Table.jsx'
@@ -30,6 +30,7 @@ import ContentText from './ContentText.jsx'
30
30
  * the A4 question is open (06-content-card-system.md §4). */
31
31
  const RATIOS = {
32
32
  file: '1 / 1',
33
+ slide: '16 / 9', /* a deck is a 1920×1080 stage (slide-variant-and-shelf-preset, 2026-09-03) */
33
34
  catalog: '1 / 1.41421',
34
35
  article: '16 / 9',
35
36
  showcase: '3 / 4',
@@ -43,6 +44,11 @@ const BOX = {
43
44
  * like I've said that before" — the ListingCardThumbBorder ruling): the selected
44
45
  * state reads from the checked ToggleCheckbox, not a fg-64 border */
45
46
  file: { layout: 'stack', border: null, bg: 'var(--kol-fg-02)', pad: 'var(--kol-pad-card-sm)' },
47
+ /* SLIDE (slide-variant-and-shelf-preset, kol-client-olina 2026-09-03; user: "they are genuinely
48
+ * different with 16:9 layout and those exposed properties"): file's stack — cover on top, the
49
+ * plate below — on the PAGE'S surface, rest and hover (olina's /slide-deck, read off the render).
50
+ * The plate is not a tone (user: "no just controls"); it is this kind's colour. */
51
+ slide: { layout: 'stack', border: null, bg: 'var(--kol-surface-primary)', pad: 'var(--kol-pad-card-sm)' },
46
52
  /* THE FRAME READS BACKWARDS (CatalogCardFrameAndZoom, kol-website 2026-08-28 — user, on a 212-tile
47
53
  * grid: no frame at rest; the old rest value is the hover): a wall of fg-04 frames is a grid of boxes,
48
54
  * louder than what they hold. Rest `transparent` (the 1px stays, so the hover step never relayouts),
@@ -88,6 +94,7 @@ const BOX = {
88
94
  * has no surface of its own to step, so it dims its title instead. */
89
95
  const HOVER = {
90
96
  file: 'var(--kol-oq-04)',
97
+ slide: 'var(--kol-surface-primary)', /* the plate holds its colour on hover; the drawer control is the affordance */
91
98
  catalog: 'var(--kol-surface-tertiary)',
92
99
  /* article and work take NO surface hover, and that is a decision not a gap:
93
100
  * article has no surface of its own (its media frame, when on, steps its
@@ -132,7 +139,10 @@ const MEDIA = {
132
139
  *
133
140
  * `layout` is the discriminator inside `showcase` because `reveal` was already
134
141
  * taken by the consumer's overlay NODE. Its values are the box vocabulary the
135
- * component already spoke: drawer (default) · canvas. */
142
+ * component already spoke: drawer (default) · canvas.
143
+ *
144
+ * A FIFTH KIND, `slide` (2026-09-03, user-ruled: a deck is genuinely a different
145
+ * thing from a file — a 16:9 stage carrying a date, a size and a slide count). */
136
146
  const ALIAS = { default: 'file', print: 'catalog', work: 'showcase', typeface: 'showcaseCanvas' }
137
147
  const LAYOUT_KEY = { canvas: 'showcaseCanvas', drawer: 'showcase' }
138
148
 
@@ -47,6 +47,12 @@ const BOX = {
47
47
  * full-width grey band under it is heavier than the line itself. Scoped to
48
48
  * `default`: catalog and print still carry the same `oq-04`, unruled. */
49
49
  file: { thumb: 48, ratio: '1 / 1', pad: `${S2} 0`, gap: S3, align: 'items-center', divider: true },
50
+ /* SLIDE (slide-variant-and-shelf-preset, 2026-09-03): file's row with the thumb FILLING the
51
+ * rung's height and taking its width from 16:9 — 48 tall, 85 wide, the whole frame — not
52
+ * the 48 square, which cropped a deck to a sliver (read off olina's /slide-deck). `minH` 64
53
+ * = the 48 thumb plus the row's 8px pads; a fill thumb reads its height off the row's
54
+ * rung, and without one it fell to the 96 fallback (80 × 142, measured on the showcase). */
55
+ slide: { thumb: 'fill', ratio: '16 / 9', pad: `${S2} 0`, gap: S3, align: 'items-center', divider: true, minH: 64 },
50
56
  /* catalog/print rows render AT 36px — the shipped GridCard list row is a
51
57
  * fixed 36 and the Y padding was what pushed it past that. X padding stays;
52
58
  * `minH` is now the whole height budget and the row centres inside it. */
@@ -57,6 +57,13 @@ const RAMP = {
57
57
  card: { title: 'kol-sans-heading-04 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta' },
58
58
  row: { title: 'kol-sans-heading-05 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta' },
59
59
  },
60
+ /* SLIDE — a deck (slide-variant-and-shelf-preset, kol-client-olina 2026-09-03;
61
+ * user: "they are genuinely different with 16:9 layout and those exposed
62
+ * properties"). file's voices plus a `meta` line — the slide count. */
63
+ slide: {
64
+ card: { title: 'kol-sans-heading-04 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', meta: 'kol-mono-12 text-meta' },
65
+ row: { title: 'kol-sans-heading-05 text-emphasis truncate', date: 'kol-mono-12 text-meta', size: 'kol-mono-12 text-meta', meta: 'kol-mono-12 text-meta' },
66
+ },
60
67
  catalog: {
61
68
  /* detail truncates (ShellHomeSystem, 2026-08-27): a wrapping blurb set three
62
69
  * cards' plates at three heights and misaligned the media */
@@ -138,6 +145,8 @@ const RAMP = {
138
145
  * read off the shipped components and the live pages they render on. */
139
146
  const ORDER = {
140
147
  file: { card: ['title', ['group', 'date', 'size']], row: ['title', ['group', 'date', 'size']] },
148
+ /* slide: title, then date · size · slide count on one baseline (read off olina's /slide-deck) */
149
+ slide: { card: ['title', ['group', 'date', 'size', 'meta']], row: ['title', ['group', 'date', 'size', 'meta']] },
141
150
  catalog: { card: ['title', 'detail'], row: [['between', 'title', 'detail']] },
142
151
  /* title + body are ONE block in BOTH forms — a `stack`, so they sit on the
143
152
  * tight 4px internal gap while tags, kicker and the meta group keep the
@@ -190,6 +199,7 @@ const STACK = {
190
199
 
191
200
  const GAPS = {
192
201
  file: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-2)' },
202
+ slide: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-2)' },
193
203
  catalog: { card: 'var(--kol-spacing-1)', row: 'var(--kol-spacing-2)' },
194
204
  article: { card: 'var(--kol-spacing-3)', row: 'var(--kol-spacing-3)', hero: 'var(--kol-spacing-3)' },
195
205
  showcase: { card: 'var(--kol-spacing-2)', row: 'var(--kol-spacing-4)' },
@@ -265,7 +265,8 @@ const ContentFilters = ({
265
265
  /* THE LAW (user ruling 2026-08-27, said "for the 10th time" — ContentFiltersFirstGroupHugs,
266
266
  * its WIDTH overruled the same day — ContentFiltersFirstGroupFixedWidth, kol-monitor: "nope
267
267
  * not hug, fix a size … if columns, maybe just use one?"): THE FIRST FILTER GROUP IS ONE
268
- * CATALOG COLUMN WIDE — `.kol-filters-first` (kol-theme): `(100cqw 120px) / 6`, the `1fr`
268
+ * CATALOG COLUMN WIDE — `.kol-filters-first` (kol-theme): one track of the catalog grid AT THE
269
+ * COUNT IT RENDERS (theme ≥0.137.0 — was `(100cqw − 120px) / 6`, true only at six tracks), the `1fr`
269
270
  * of the catalog's `repeat(6, 1fr)` gap 24, measured on the header row as a container so
270
271
  * the count/strip beside the groups never narrows it. It sits over the first card; EVERY
271
272
  * GROUP AFTER IT FLOWS across the rest of the row, starting over the second. By POSITION,
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react'
2
+ import formatSize from '../utilities/formatSize.js'
2
3
  import { Icon } from '@kolkrabbi/kol-icons'
3
4
  import Button from '../atoms/Button.jsx'
4
5
  import Divider from '../atoms/Divider.jsx'
@@ -87,14 +88,7 @@ const SORT_OPTIONS = [
87
88
  { value: 'kind', label: 'Kind' },
88
89
  ]
89
90
 
90
- /* bytes → weight, duplicated from the client on purpose (§3: the UI never imports it) */
91
- function formatSize(bytes) {
92
- if (bytes == null) return ''
93
- if (bytes < 1024) return `${bytes} B`
94
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
95
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
96
- return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
97
- }
91
+ /* bytes → weight: `utilities/formatSize` since 0.178.0 one copy, exported */
98
92
  const formatDate = (iso) => (iso ? new Date(iso).toISOString().slice(0, 10) : '')
99
93
  const isImage = (ct) => !!ct && ct.startsWith('image/')
100
94
  const isVideo = (ct) => !!ct && ct.startsWith('video/')
@@ -0,0 +1,14 @@
1
+ /**
2
+ * formatSize — bytes → the weight a file reads as in the media admin
3
+ * (`1.2 MB`). Lived inside MediaLibraryPages; exported since 0.178.0
4
+ * (slide-variant-and-shelf-preset, 2026-09-03) because kol-shell's shelf preset
5
+ * and olina's deck page were restating it byte for byte. Duplicated from the
6
+ * media client on purpose (§3: the UI never imports the client).
7
+ */
8
+ export default function formatSize(bytes) {
9
+ if (bytes == null) return ''
10
+ if (bytes < 1024) return `${bytes} B`
11
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
12
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)} MB`
13
+ return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`
14
+ }
@@ -2,7 +2,12 @@
2
2
  * toneClass — the control set's TONE, six values (tone-is-the-ground-axis,
3
3
  * kol-client-olina 2026-09-03; the six-tone list is the user's).
4
4
  *
5
- * primary · secondary · outline · ghost · grey · sunken
5
+ * primary · secondary · inverted · outline · ghost · grey · sunken
6
+ *
7
+ * `secondary` paints the PAGE SURFACE and `inverted` the text colour as fill
8
+ * (tone-secondary-is-inverse, 2026-09-03; user: "that tone should be called
9
+ * secondary. What is currently secondary should be called inverted") — 0.134.0
10
+ * had lifted `secondary` from Button's variant, which was already an inverse.
6
11
  *
7
12
  * A tone is a ground-and-ink bundle — `--kol-tone-*` custom properties in
8
13
  * kol-theme (kol-components-molecules.css, TONE) that every control paints
@@ -13,9 +18,10 @@
13
18
  *
14
19
  * `inverse` is `sunken`'s alias (0.117.0's name — ControlToneSunken,
15
20
  * kol-website 2026-08-28; user ruling: the control does not invert anything,
16
- * it sits BELOW the plane it is on).
21
+ * it sits BELOW the plane it is on) — NOT `inverted`; one kol-website call
22
+ * still passes it, and it drops when that moves.
17
23
  */
18
- export const TONES = ['primary', 'secondary', 'outline', 'ghost', 'grey', 'sunken']
24
+ export const TONES = ['primary', 'secondary', 'inverted', 'outline', 'ghost', 'grey', 'sunken']
19
25
 
20
26
  export const toneClass = (tone) =>
21
27
  tone === 'inverse' ? 'kol-tone-sunken' : TONES.includes(tone) ? `kol-tone-${tone}` : ''