@kolkrabbi/kol-component 0.213.0 → 0.215.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.
|
|
3
|
+
"version": "0.215.0",
|
|
4
4
|
"description": "KOL design-system components \u2014 atoms through organisms, emitting canonical kol-* classes. Pairs with @kolkrabbi/kol-theme for styling.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -99,7 +99,10 @@ const SORT_OPTIONS = [
|
|
|
99
99
|
]
|
|
100
100
|
|
|
101
101
|
/* bytes → weight: `utilities/formatSize` since 0.178.0 — one copy, exported */
|
|
102
|
-
|
|
102
|
+
/* the pages' shared date default. Named `default*` because BOTH pages now take
|
|
103
|
+
* a `formatDate` prop, and a prop of the same name would shadow this into a TDZ
|
|
104
|
+
* error at the destructure. */
|
|
105
|
+
const defaultFormatDate = (iso) => (iso ? new Date(iso).toISOString().slice(0, 10) : '')
|
|
103
106
|
const isImage = (ct) => !!ct && ct.startsWith('image/')
|
|
104
107
|
const isVideo = (ct) => !!ct && ct.startsWith('video/')
|
|
105
108
|
|
|
@@ -378,7 +381,9 @@ export function MediaLibraryBrowse({
|
|
|
378
381
|
* `ColumnBrowser`, IT IS ADDED HERE IN THE SAME EDIT, unless the page holds a
|
|
379
382
|
* real opinion about it (it owns `height`, the widths and `partition`, so
|
|
380
383
|
* those are deliberately absent from this list). */
|
|
381
|
-
thumbnailFor, folderMeta,
|
|
384
|
+
thumbnailFor, folderMeta, stackView,
|
|
385
|
+
/* defaulted, not bare — see the note on the sibling page */
|
|
386
|
+
formatDate = defaultFormatDate,
|
|
382
387
|
/* THE TAB PILL'S LIST (ColumnBrowserMobileViews item 16). What a tab MEANS is
|
|
383
388
|
* the consumer's — a repo whose surfaces are routes wires its router here.
|
|
384
389
|
* No tabs, no pill, and the page is exactly what it was. */
|
|
@@ -601,17 +606,6 @@ export function MediaLibraryBrowse({
|
|
|
601
606
|
<MediaSettings bucketMeta={bucketMeta} settings={settings} profile={profile} onChange={setSettings} onReset={() => setSettings(null)} onClose={() => setSettingsOpen(false)} settingsFooter={settingsFooter} />
|
|
602
607
|
)}
|
|
603
608
|
|
|
604
|
-
{/* THE TAB PILL (item 16) — floats over the list, so the list owes it
|
|
605
|
-
room or its last row sits under the bar forever. Below `md` only;
|
|
606
|
-
above it the 2026-08-26 one-view ruling stands and nothing here
|
|
607
|
-
renders. */}
|
|
608
|
-
{tabs?.length > 0 && (
|
|
609
|
-
<>
|
|
610
|
-
<div className="md:hidden" aria-hidden="true" style={{ height: TABBAR_H }} />
|
|
611
|
-
<MobileTabBar tabs={tabs} value={activeTab} onChange={onTabChange} />
|
|
612
|
-
</>
|
|
613
|
-
)}
|
|
614
|
-
|
|
615
609
|
{folderView === 'columns' ? (
|
|
616
610
|
<div ref={columnsRef} className="relative">
|
|
617
611
|
<ColumnBrowser
|
|
@@ -680,6 +674,24 @@ export function MediaLibraryBrowse({
|
|
|
680
674
|
</p>
|
|
681
675
|
)}
|
|
682
676
|
{void onOpen}
|
|
677
|
+
|
|
678
|
+
{/* THE TAB PILL (item 16) — floats over the list, so the list owes it
|
|
679
|
+
room or its last row sits under the bar forever.
|
|
680
|
+
THE SPACER GOES LAST, AND THAT IS THE WHOLE POINT
|
|
681
|
+
(TabBarSpacerAboveTheList, kol-r2b2 2026-09-04). `MobileTabBar` is
|
|
682
|
+
`fixed`, so where it sits in the tree is irrelevant — the SPACER is
|
|
683
|
+
in NORMAL FLOW, so its position is everything. Rendered before the
|
|
684
|
+
list it failed twice at once: a 56px hole punched into the gap under
|
|
685
|
+
the pinned search, and the last row still running 99px under the
|
|
686
|
+
bar. The comment above named the failure it was meant to prevent and
|
|
687
|
+
the block sat in the wrong place anyway. Below `md` only; above it
|
|
688
|
+
the 2026-08-26 one-view ruling stands. */}
|
|
689
|
+
{tabs?.length > 0 && (
|
|
690
|
+
<>
|
|
691
|
+
<div className="md:hidden" aria-hidden="true" style={{ height: TABBAR_H }} />
|
|
692
|
+
<MobileTabBar tabs={tabs} value={activeTab} onChange={onTabChange} />
|
|
693
|
+
</>
|
|
694
|
+
)}
|
|
683
695
|
</div>
|
|
684
696
|
</div>
|
|
685
697
|
)
|
|
@@ -692,6 +704,21 @@ export function MediaLibraryLibrary({
|
|
|
692
704
|
/* the same pill the browse page takes — the wall is one of the surfaces it
|
|
693
705
|
switches between, so it has to carry it too (item 16) */
|
|
694
706
|
tabs, activeTab, onTabChange,
|
|
707
|
+
/* A SEAM ON ONE PAGE IS A SEAM ON BOTH, when the field is one both render
|
|
708
|
+
* (FormatDateSkipsTheLibraryPage, kol-r2b2 2026-09-04). This reached
|
|
709
|
+
* `MediaLibraryBrowse` and not here, so a consumer passing ONE props object
|
|
710
|
+
* to both got `19.6.2026` on the Browse tab and `2026-06-19` on the Files tab
|
|
711
|
+
* — two formats for one field, one tap apart. The desktop stack had the same
|
|
712
|
+
* mismatch; the tab pill just moved the two halves close enough to see it.
|
|
713
|
+
*
|
|
714
|
+
* THE RULE, WIDENED — the fourth defect of this exact shape (`settingsFooter`
|
|
715
|
+
* documented and hardcoded past · `thumbnailFor` and `folderMeta` not
|
|
716
|
+
* forwarded · now this). These two pages are ONE SURFACE SPLIT IN TWO and a
|
|
717
|
+
* consumer hands the same props object to both, so: a prop naming how a
|
|
718
|
+
* SHARED FIELD renders is added to BOTH pages in the same edit. Props about a
|
|
719
|
+
* concept only one page has stay put — `thumbnailFor`, `folderMeta` and
|
|
720
|
+
* `stackView` are folder and stack concepts and are correctly absent here. */
|
|
721
|
+
formatDate = defaultFormatDate,
|
|
695
722
|
}) {
|
|
696
723
|
const [ownBucket, setOwnBucket] = useState(bucket)
|
|
697
724
|
const bucketId = bucket ?? ownBucket
|
|
@@ -18,7 +18,26 @@ import CloseButton from './CloseButton.jsx'
|
|
|
18
18
|
const FOCUSABLE =
|
|
19
19
|
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
20
20
|
|
|
21
|
-
export default function FullscreenOverlay({
|
|
21
|
+
export default function FullscreenOverlay({
|
|
22
|
+
open, onClose, closeButton = true,
|
|
23
|
+
/* WHERE FOCUS LANDS ON OPEN. The sheet takes it by default, which is right
|
|
24
|
+
* for a browser and wrong for a sheet opened to be TYPED IN: a child's
|
|
25
|
+
* `autoFocus` cannot win, because child effects run BEFORE the parent's and
|
|
26
|
+
* this one moves focus afterwards — so the field focuses and is immediately
|
|
27
|
+
* robbed, with nothing in either file looking wrong (kol-fxr measured it on
|
|
28
|
+
* design-editor 0.10.0: Save As routed into the dialog correctly and focus
|
|
29
|
+
* sat on `.kol-overlay-sheet`). Pass a ref to the node that should hold it.
|
|
30
|
+
* A ref that is empty on mount falls back to the sheet, so a conditional
|
|
31
|
+
* field cannot leave the overlay unfocused.
|
|
32
|
+
*
|
|
33
|
+
* The ref may point at the control ITSELF or at a WRAPPER around it — the
|
|
34
|
+
* first focusable descendant is taken. That is deliberate: a DS input is a
|
|
35
|
+
* component, not a DOM node, and whether it forwards a ref is a detail no
|
|
36
|
+
* caller should have to know to put focus in it. A plain `<div ref>` always
|
|
37
|
+
* works. */
|
|
38
|
+
initialFocus,
|
|
39
|
+
children,
|
|
40
|
+
}) {
|
|
22
41
|
const sheetRef = useRef(null)
|
|
23
42
|
|
|
24
43
|
/* Escape closes; Tab is TRAPPED in the sheet (SettingsPanel, 2026-08-26 —
|
|
@@ -45,13 +64,19 @@ export default function FullscreenOverlay({ open, onClose, closeButton = true, c
|
|
|
45
64
|
const prev = document.body.style.overflow
|
|
46
65
|
document.body.style.overflow = 'hidden'
|
|
47
66
|
const prevFocus = document.activeElement
|
|
48
|
-
|
|
67
|
+
const wanted = initialFocus?.current
|
|
68
|
+
const target = wanted
|
|
69
|
+
? (typeof wanted.focus === 'function' && wanted.matches?.(FOCUSABLE)
|
|
70
|
+
? wanted
|
|
71
|
+
: wanted.querySelector?.(FOCUSABLE) ?? wanted)
|
|
72
|
+
: sheetRef.current
|
|
73
|
+
target?.focus?.()
|
|
49
74
|
return () => {
|
|
50
75
|
document.removeEventListener('keydown', onKey)
|
|
51
76
|
document.body.style.overflow = prev
|
|
52
77
|
if (prevFocus instanceof HTMLElement) prevFocus.focus()
|
|
53
78
|
}
|
|
54
|
-
}, [open, onClose])
|
|
79
|
+
}, [open, onClose, initialFocus])
|
|
55
80
|
|
|
56
81
|
if (!open) return null
|
|
57
82
|
|