@kolkrabbi/kol-component 0.317.3 → 0.317.4
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.317.
|
|
3
|
+
"version": "0.317.4",
|
|
4
4
|
"description": "The core component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"react-dom": "^18.3.0 || ^19.0.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@kolkrabbi/kol-icons": "^0.53.
|
|
41
|
+
"@kolkrabbi/kol-icons": "^0.53.5"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"src",
|
|
@@ -34,7 +34,7 @@ import { RAIL_ROW } from '../hooks/railRow.js'
|
|
|
34
34
|
* Every variant but `stacked`, which has its phone bar
|
|
35
35
|
* @param {'underline'|'chip'|'text'|'stacked'} [variant='underline']
|
|
36
36
|
* @param {string} [look] @deprecated 2026-10-30 — the old spelling of `variant`; `variant` wins when both are passed
|
|
37
|
-
* @param {'mono'|'sans'} [type='mono'] `underline` only: the tab's type
|
|
37
|
+
* @param {'mono'|'sans'|'mono-light'} [type='mono'] `underline` only: the tab's type — `mono-light` is `sans`'s row on JetBrains Mono at 300 (his word, 2026-10-30)
|
|
38
38
|
* @param {boolean} [filled=false] - `chip` only: every chip shows at rest (Button `tab-filled`), so the row reads as choices
|
|
39
39
|
* underline — this row, as it always was; a tab's `icon` sits before its label. `type` picks its type: `mono`
|
|
40
40
|
* (default, the mono-12 row) or `sans` (the neutral sans at 300, a 20px glyph in a 40px row — the
|
|
@@ -274,7 +274,7 @@ function StackedTabs({ tabs, value, onChange, placement, shape = 'bar', collapse
|
|
|
274
274
|
function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap, type }) {
|
|
275
275
|
/* `type="sans"` (2026-10-29, the user's ref — Adobe's tab strip): the neutral sans at 300 (his word, 2026-10-30), a 20px glyph at the
|
|
276
276
|
* set's 1.5 stroke and a 1px underline (his word, 2026-10-30), a 40px row (his word, 2026-10-30), 24 between tabs — review 8: *"the problem … was thickness and stroke"* */
|
|
277
|
-
const md = type === 'sans'
|
|
277
|
+
const md = type === 'sans' || type === 'mono-light'
|
|
278
278
|
const listRef = useRef(null)
|
|
279
279
|
const activeIdx = tabs.findIndex((t) => t.id === value)
|
|
280
280
|
|
|
@@ -308,15 +308,15 @@ function UnderlineTabs({ tabs, value, onChange, onClose, onMinimise, wrap, type
|
|
|
308
308
|
tabIndex={isActive || (activeIdx === -1 && i === 0) ? 0 : -1}
|
|
309
309
|
onClick={() => onChange?.(t.id)}
|
|
310
310
|
className={[
|
|
311
|
-
`group ${md ? 'kol-tab-sans kol-sans-body-02 font-light gap-2.5 border-b
|
|
311
|
+
`group ${md ? `${type === 'sans' ? 'kol-tab-sans' : 'kol-tab-mono-light'} kol-sans-body-02 font-light gap-2.5 border-b` : 'kol-mono-12 gap-2 border-b-2'} flex items-center cursor-pointer`,
|
|
312
312
|
isActive
|
|
313
|
-
? 'text-
|
|
314
|
-
: 'text-
|
|
313
|
+
? 'text-oq-96 border-fg'
|
|
314
|
+
: 'text-oq-48 hover:text-oq-80 border-transparent',
|
|
315
315
|
].join(' ')}
|
|
316
316
|
>
|
|
317
317
|
{/* a tab may carry its glyph before the label (2026-10-29, Adobe's Base color · Image · Color wheel) */}
|
|
318
318
|
{/* the glyph in OPAQUE ink (icons take oq, never fg) — full when active, meta otherwise, full under the pointer */}
|
|
319
|
-
{t.icon && <Icon name={t.icon} size={md ? 20 : 16} className={isActive ? 'text-oq-96' : 'text-oq-48 group-hover:text-oq-
|
|
319
|
+
{t.icon && <Icon name={t.icon} size={md ? 20 : 16} className={isActive ? 'text-oq-96' : 'text-oq-48 group-hover:text-oq-80'} />}
|
|
320
320
|
{t.label}
|
|
321
321
|
</button>
|
|
322
322
|
)
|
|
@@ -521,6 +521,10 @@ export default function ColumnBrowser({
|
|
|
521
521
|
/* `renderDetails(file)` — extra rows under the preview column's facts (tags, an Edit action);
|
|
522
522
|
* absent, the column is exactly what it was */
|
|
523
523
|
renderDetails,
|
|
524
|
+
/* `renderFolderPreview(level, width)` — the preview column when NO file is shown: the open
|
|
525
|
+
* folder's own pane (MediaLibraryInfoKey, kol-website 2026-10-30 — the media page's folder facts,
|
|
526
|
+
* as its rows draw them). Null for a level with nothing to say; absent, the column is empty. */
|
|
527
|
+
renderFolderPreview,
|
|
524
528
|
height,
|
|
525
529
|
defaultHeight = 528,
|
|
526
530
|
onHeightChange,
|
|
@@ -798,6 +802,8 @@ export default function ColumnBrowser({
|
|
|
798
802
|
const levels = levelsOf(prefix) // '' → ['']; 'a/b/' → ['', 'a/', 'a/b/']
|
|
799
803
|
// While Quick Look is open the overlay's index is the truth for the highlight.
|
|
800
804
|
const shown = quickLook ? quickLook.files[quickLook.index] : picked
|
|
805
|
+
/* no file shown: the open folder's pane, when the caller has one for this level */
|
|
806
|
+
const folderPane = !shown && renderFolderPreview ? renderFolderPreview(prefix, widthOf('preview')) : null
|
|
801
807
|
|
|
802
808
|
/* ── THE STACK MODE (ColumnBrowserStackMode, kol-r2b2 2026-09-03) ─────────
|
|
803
809
|
*
|
|
@@ -1093,9 +1099,11 @@ export default function ColumnBrowser({
|
|
|
1093
1099
|
<SelectionPreview keys={[...selectedKeys]} objects={objects} urlOf={urlOf} kindOf={kindOf} formatSize={formatSize} formatDate={formatDate} width={widthOf('preview')} />
|
|
1094
1100
|
<ResizeHandle axis="x" onDrag={resizeCol('preview')} onEnd={endDrag} />
|
|
1095
1101
|
</>
|
|
1096
|
-
) : shown && (
|
|
1102
|
+
) : (shown || folderPane) && (
|
|
1097
1103
|
<>
|
|
1098
|
-
|
|
1104
|
+
{shown
|
|
1105
|
+
? <Preview key={shown.key} o={shown} urlOf={urlOf} kindOf={kindOf} kindLabel={kindLabel} formatSize={formatSize} formatDate={formatDate} renderPreview={renderPreview} renderDetails={renderDetails} width={widthOf('preview')} />
|
|
1106
|
+
: folderPane}
|
|
1099
1107
|
<ResizeHandle axis="x" onDrag={resizeCol('preview')} onEnd={endDrag} />
|
|
1100
1108
|
</>
|
|
1101
1109
|
)}
|
|
@@ -285,22 +285,28 @@ function ImageFrame({ src, vector, mode = 'native' }) {
|
|
|
285
285
|
)
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
/*
|
|
289
|
-
* not on AppHub, which binds its own)
|
|
290
|
-
*
|
|
291
|
-
|
|
288
|
+
/* THE PAGE'S OWN KEYS, ONE GUARD. `,` toggles the settings drawer (MediaPreviewNativeRatio,
|
|
289
|
+
* kol-website 2026-10-30: the media app is not on AppHub, which binds its own); `mod+i` opens Get
|
|
290
|
+
* Info (MediaLibraryInfoKey, kol-website 2026-10-30). AppHub's guard — no other modifiers, no
|
|
291
|
+
* repeat, not while typing. A bare key takes no modifier at all; `mod` is ⌘ or Ctrl. `on` false
|
|
292
|
+
* hands the key to the host (the gear's `onOpenSettings`), or holds it while an overlay is up. */
|
|
293
|
+
function useHotkey(combo, on, run) {
|
|
292
294
|
useEffect(() => {
|
|
293
|
-
if (!
|
|
295
|
+
if (!combo || !on) return undefined
|
|
296
|
+
const parts = combo.toLowerCase().split('+')
|
|
297
|
+
const key = parts.pop()
|
|
298
|
+
const mod = parts.includes('mod')
|
|
294
299
|
const onKey = (e) => {
|
|
295
|
-
if (!(e.key === key || (key === ',' && e.code === 'Comma')) || e.
|
|
300
|
+
if (!(e.key?.toLowerCase() === key || (key === ',' && e.code === 'Comma')) || e.altKey || e.repeat) return
|
|
301
|
+
if (mod ? !(e.metaKey || e.ctrlKey) : (e.metaKey || e.ctrlKey)) return
|
|
296
302
|
const el = e.target
|
|
297
303
|
if (el?.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(el?.tagName ?? '')) return
|
|
298
304
|
e.preventDefault()
|
|
299
|
-
|
|
305
|
+
run()
|
|
300
306
|
}
|
|
301
307
|
window.addEventListener('keydown', onKey)
|
|
302
308
|
return () => window.removeEventListener('keydown', onKey)
|
|
303
|
-
}, [
|
|
309
|
+
}, [combo, on, run])
|
|
304
310
|
}
|
|
305
311
|
|
|
306
312
|
|
|
@@ -1160,6 +1166,12 @@ export function MediaLibraryBrowse({
|
|
|
1160
1166
|
settingsKey = ',',
|
|
1161
1167
|
settingsOpen: settingsOpenProp,
|
|
1162
1168
|
onSettingsOpenChange,
|
|
1169
|
+
/* `infoKey` — Get Info on the pick, file or folder, in every view (MediaLibraryInfoKey, kol-website
|
|
1170
|
+
* 2026-10-30; Finder's ⌘I): the same overlay Space opens, Esc closes. `'mod+i'`; `false` for none.
|
|
1171
|
+
* `columnFolderInfo` — Columns: the open folder's facts pane in the preview column, as the rows
|
|
1172
|
+
* draw a picked folder's; `false` leaves the column empty, as before. */
|
|
1173
|
+
infoKey = 'mod+i',
|
|
1174
|
+
columnFolderInfo = true,
|
|
1163
1175
|
}) {
|
|
1164
1176
|
const [ownPrefix, setOwnPrefix] = useState('')
|
|
1165
1177
|
const prefix = prefixProp ?? ownPrefix
|
|
@@ -1177,7 +1189,7 @@ export function MediaLibraryBrowse({
|
|
|
1177
1189
|
const [ownSettingsOpen, setOwnSettingsOpen] = useState(false)
|
|
1178
1190
|
const settingsOpen = settingsOpenProp ?? ownSettingsOpen
|
|
1179
1191
|
const setSettingsOpen = (v) => { if (settingsOpenProp == null) setOwnSettingsOpen(v); onSettingsOpenChange?.(v) }
|
|
1180
|
-
|
|
1192
|
+
useHotkey(settingsKey, !onOpenSettings, () => setSettingsOpen(!settingsOpen))
|
|
1181
1193
|
const [trashOpen, setTrashOpen] = useState(false)
|
|
1182
1194
|
const [pickedFile, setPickedFile] = useState(null)
|
|
1183
1195
|
useEffect(() => { onPickFile?.(pickedFile) }, [pickedFile]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
@@ -1680,7 +1692,9 @@ export function MediaLibraryBrowse({
|
|
|
1680
1692
|
}
|
|
1681
1693
|
}
|
|
1682
1694
|
const spaceQuickLook = () => {
|
|
1683
|
-
|
|
1695
|
+
/* in the columns a folder is picked by being OPEN — the deepest level (MediaLibraryInfoKey: ⌘I
|
|
1696
|
+
* is the only caller there; the columns keep their own Space) */
|
|
1697
|
+
const key = pickedFile?.key ?? (view === 'columns' ? (prefix || null) : pickedFolder ?? (rowSelection.size === 1 ? [...rowSelection][0] : null))
|
|
1684
1698
|
/* a picked bucket or the title Quick-Looks like any folder (user 2026-09-25) */
|
|
1685
1699
|
if (!key && previewRoot) { setQuickLookFolder(ROOT_KEY); return }
|
|
1686
1700
|
if (!key) return
|
|
@@ -1843,6 +1857,9 @@ export function MediaLibraryBrowse({
|
|
|
1843
1857
|
|
|
1844
1858
|
const [quickLook, setQuickLook] = useState(null)
|
|
1845
1859
|
const [quickLookFolder, setQuickLookFolder] = useState(null)
|
|
1860
|
+
/* ⌘I — GET INFO (MediaLibraryInfoKey): what Space opens, from every view; inert over an open
|
|
1861
|
+
* overlay (Esc closes it) and over the search modal */
|
|
1862
|
+
useHotkey(infoKey, !quickLook && !quickLookFolder && !searchOpen, spaceQuickLook)
|
|
1846
1863
|
/* …and the open window takes the new selection as its set, on the file the cursor reached */
|
|
1847
1864
|
useEffect(() => {
|
|
1848
1865
|
if (!quickLook || rowSelection.size < 2) return
|
|
@@ -2136,6 +2153,15 @@ export function MediaLibraryBrowse({
|
|
|
2136
2153
|
owns it through settings (item 15) */
|
|
2137
2154
|
stackView={stackView ?? settings.stackView ?? 'list'}
|
|
2138
2155
|
renderDetails={(o) => detailsFor({ ...o, key: o.key.slice(VROOT.length) })}
|
|
2156
|
+
/* THE OPEN FOLDER'S FACTS IN THE PREVIEW COLUMN (MediaLibraryInfoKey, kol-website
|
|
2157
|
+
* 2026-10-30: picking a folder showed nothing beside the list) — the rows' pane, drawn
|
|
2158
|
+
* where the columns put a file's. A level above the bucket (the title, a bucket) has
|
|
2159
|
+
* no pane here, as before. */
|
|
2160
|
+
renderFolderPreview={columnFolderInfo ? (level, width) => {
|
|
2161
|
+
const real = unroot(level)
|
|
2162
|
+
if (!real || (VROOT && real === level)) return null
|
|
2163
|
+
return <FolderPreview key={real} path={real} objects={objects} width={width} formatDate={formatDate} details={detailsFor(real)} />
|
|
2164
|
+
} : undefined}
|
|
2139
2165
|
renderPreview={(o) => {
|
|
2140
2166
|
const real = { ...o, key: o.key.slice(VROOT.length) }
|
|
2141
2167
|
if (isImage(real.contentType)) return <ImageFrame src={mediaUrl(real.key)} vector={real.contentType === 'image/svg+xml'} mode={previewFrame} />
|
|
@@ -3065,7 +3091,7 @@ export function MediaLibraryLibrary({
|
|
|
3065
3091
|
const [ownSettingsOpen, setOwnSettingsOpen] = useState(false)
|
|
3066
3092
|
const settingsOpen = settingsOpenProp ?? ownSettingsOpen
|
|
3067
3093
|
const setSettingsOpen = (v) => { if (settingsOpenProp == null) setOwnSettingsOpen(v); onSettingsOpenChange?.(v) }
|
|
3068
|
-
|
|
3094
|
+
useHotkey(settingsKey, header, () => setSettingsOpen(!settingsOpen))
|
|
3069
3095
|
const [editingKey, setEditingKey] = useState(null)
|
|
3070
3096
|
const [editingValue, setEditingValue] = useState('')
|
|
3071
3097
|
const [renaming, setRenaming] = useState(false)
|