@kolkrabbi/kol-component 0.97.1 → 0.97.3
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/organisms/ColumnBrowser.jsx +37 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kolkrabbi/kol-component",
|
|
3
|
-
"version": "0.97.
|
|
3
|
+
"version": "0.97.3",
|
|
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",
|
|
@@ -35,6 +35,7 @@ import { Icon } from '@kolkrabbi/kol-icons'
|
|
|
35
35
|
* @param {Function} onPrefix (prefix) => void
|
|
36
36
|
* @param {{files: object[], index: number}|null} quickLook the open Quick Look, or null
|
|
37
37
|
* @param {Function} onQuickLook (quickLook|null) => void — space opens / closes, ↑/↓ step while open
|
|
38
|
+
* @param {Function} onPick (file|null) => void — the picked file whenever it changes (a folder pick or an outside prefix change → null)
|
|
38
39
|
* @param {Function} urlOf (o) => string — the object's public URL for the preview image
|
|
39
40
|
* @param {Function} kindOf (o) => 'image' | 'video' | 'audio' | string
|
|
40
41
|
* @param {Object} kindLabel kind → label shown when there is no visual preview
|
|
@@ -141,6 +142,7 @@ export default function ColumnBrowser({
|
|
|
141
142
|
onPrefix = () => {},
|
|
142
143
|
quickLook = null,
|
|
143
144
|
onQuickLook,
|
|
145
|
+
onPick,
|
|
144
146
|
urlOf,
|
|
145
147
|
kindOf = defaultKindOf,
|
|
146
148
|
kindLabel = DEFAULT_KIND_LABEL,
|
|
@@ -149,14 +151,11 @@ export default function ColumnBrowser({
|
|
|
149
151
|
className = '',
|
|
150
152
|
}) {
|
|
151
153
|
const [picked, setPicked] = useState(null)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const [cursorActive, setCursorActive] = useState(false)
|
|
158
|
-
const rootRef = useRef(null)
|
|
159
|
-
|
|
154
|
+
/* `onPick` (ColumnBrowserOnPick, kol-r2b2 2026-08-27): the picked file, for
|
|
155
|
+
* a Finder-style breadcrumb — fired whenever it changes; a folder pick or an
|
|
156
|
+
* outside prefix change fires null */
|
|
157
|
+
const pick = (o) => { setPicked(o); onPick?.(o) }
|
|
158
|
+
useEffect(() => { if (picked) pick(null) }, [prefix]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
160
159
|
const levelsOf = (pfx) => {
|
|
161
160
|
const out = ['']
|
|
162
161
|
if (pfx) {
|
|
@@ -169,10 +168,31 @@ export default function ColumnBrowser({
|
|
|
169
168
|
const { folders, files } = partition(objects.filter((o) => o.key.startsWith(level)), level)
|
|
170
169
|
return [...folders.map((f) => ({ type: 'folder', name: f })), ...files.map((o) => ({ type: 'file', o }))]
|
|
171
170
|
}
|
|
171
|
+
// Keyboard cursor: which column, which row. Clicks keep it in sync.
|
|
172
|
+
/* SEEDED on the open folder of the deepest column that has one (Finder's
|
|
173
|
+
* start — ColumnBrowserCursorSeed, kol-r2b2 2026-08-27): with a deep `prefix`
|
|
174
|
+
* the first ↓ used to act in column 0. Re-seeded when `prefix` changes; the
|
|
175
|
+
* internal moves land on the same spot, so nothing jumps. */
|
|
176
|
+
const seedCursor = (pfx) => {
|
|
177
|
+
const lv = levelsOf(pfx)
|
|
178
|
+
if (lv.length < 2) return { col: 0, idx: 0 }
|
|
179
|
+
const col = lv.length - 2
|
|
180
|
+
const opened = lv[col + 1].slice(lv[col].length)
|
|
181
|
+
const idx = itemsAt(lv[col]).findIndex((it) => it.type === 'folder' && it.name === opened)
|
|
182
|
+
return { col, idx: Math.max(0, idx) }
|
|
183
|
+
}
|
|
184
|
+
const [cursor, setCursor] = useState(() => seedCursor(prefix))
|
|
185
|
+
useEffect(() => { setCursor(seedCursor(prefix)) }, [prefix, objects.length]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
186
|
+
/* the cursor is NOT drawn until the keyboard is used (ColumnBrowserCursorStart,
|
|
187
|
+
* kol-r2b2 2026-08-27): at rest it sat on row 0 beside the open folder and
|
|
188
|
+
* read as a second selection. Arrows arm it; a click seeds it. */
|
|
189
|
+
const [cursorActive, setCursorActive] = useState(false)
|
|
190
|
+
const rootRef = useRef(null)
|
|
191
|
+
|
|
172
192
|
const land = (level, item) => {
|
|
173
193
|
if (!item) return
|
|
174
|
-
if (item.type === 'folder') {
|
|
175
|
-
else
|
|
194
|
+
if (item.type === 'folder') { pick(null); onPrefix(level + item.name) }
|
|
195
|
+
else pick(item.o)
|
|
176
196
|
}
|
|
177
197
|
|
|
178
198
|
const onKeyDown = (e) => {
|
|
@@ -191,6 +211,9 @@ export default function ColumnBrowser({
|
|
|
191
211
|
const items = itemsAt(lv[col])
|
|
192
212
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
|
193
213
|
const idx = Math.max(0, Math.min(items.length - 1, cursor.idx + (e.key === 'ArrowDown' ? 1 : -1)))
|
|
214
|
+
/* an arrow that did not move the cursor does nothing — re-landing on an
|
|
215
|
+
* open folder re-fired onPrefix (ColumnBrowserCursorSeed) */
|
|
216
|
+
if (idx === cursor.idx && col === cursor.col) return
|
|
194
217
|
setCursor({ col, idx })
|
|
195
218
|
land(lv[col], items[idx])
|
|
196
219
|
} else if (e.key === 'ArrowRight') {
|
|
@@ -206,7 +229,7 @@ export default function ColumnBrowser({
|
|
|
206
229
|
const opened = lv[col].slice(lv[col - 1].length)
|
|
207
230
|
const idx = Math.max(0, parentItems.findIndex((it) => it.type === 'folder' && it.name === opened))
|
|
208
231
|
setCursor({ col: col - 1, idx })
|
|
209
|
-
|
|
232
|
+
pick(null)
|
|
210
233
|
onPrefix(lv[col])
|
|
211
234
|
}
|
|
212
235
|
}
|
|
@@ -228,7 +251,7 @@ export default function ColumnBrowser({
|
|
|
228
251
|
do { idx += step } while (items[idx] && items[idx].type !== 'file')
|
|
229
252
|
if (!items[idx]) return
|
|
230
253
|
setCursor({ col, idx })
|
|
231
|
-
|
|
254
|
+
pick(items[idx].o)
|
|
232
255
|
const siblings = items.filter((it) => it.type === 'file').map((it) => it.o)
|
|
233
256
|
onQuickLook?.({ files: siblings, index: siblings.findIndex((o) => o.key === items[idx].o.key) })
|
|
234
257
|
}
|
|
@@ -267,7 +290,7 @@ export default function ColumnBrowser({
|
|
|
267
290
|
active={f === activeFolder}
|
|
268
291
|
cursor={cursorActive && cursor.col === k && cursor.idx === i}
|
|
269
292
|
trailing={<Icon name="chevron-right" size={12} className="text-fg-32" />}
|
|
270
|
-
onClick={() => { setCursor({ col: k, idx: i }); setCursorActive(true); rootRef.current?.focus();
|
|
293
|
+
onClick={() => { setCursor({ col: k, idx: i }); setCursorActive(true); rootRef.current?.focus(); pick(null); onPrefix(level + f) }}
|
|
271
294
|
/>
|
|
272
295
|
))}
|
|
273
296
|
{files.map((o, i) => (
|
|
@@ -278,7 +301,7 @@ export default function ColumnBrowser({
|
|
|
278
301
|
muted={shown?.key !== o.key}
|
|
279
302
|
active={shown?.key === o.key}
|
|
280
303
|
cursor={cursorActive && cursor.col === k && cursor.idx === folders.length + i}
|
|
281
|
-
onClick={() => { setCursor({ col: k, idx: folders.length + i }); setCursorActive(true); rootRef.current?.focus();
|
|
304
|
+
onClick={() => { setCursor({ col: k, idx: folders.length + i }); setCursorActive(true); rootRef.current?.focus(); pick(o) }}
|
|
282
305
|
/>
|
|
283
306
|
))}
|
|
284
307
|
{folders.length === 0 && files.length === 0 && (
|