@bygghammar/cms-platform 0.64.0 → 0.66.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": "@bygghammar/cms-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Shared CMS platform: blocks, block types, i18n core, UI primitives and design-token contract used by every Bygghammar/Voyara site.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -300,4 +300,4 @@
|
|
|
300
300
|
"cms-platform-migrations": "scripts/sync-migrations.mjs",
|
|
301
301
|
"create-cms-site": "./scripts/create-cms-site.mjs"
|
|
302
302
|
}
|
|
303
|
-
}
|
|
303
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { Check, ChevronLeft, ChevronRight } from "lucide-react";
|
|
3
3
|
import { useT } from "../../i18n";
|
|
4
4
|
import { Reveal } from "../../ui/Reveal";
|
|
@@ -20,6 +20,28 @@ export function CabinTabsBlock({ props }: { props: CabinTabsProps }) {
|
|
|
20
20
|
const items = props.items ?? [];
|
|
21
21
|
const [active, setActive] = useState(0);
|
|
22
22
|
const [slide, setSlide] = useState(0);
|
|
23
|
+
const [scroll, setScroll] = useState({ value: 0, max: 0 });
|
|
24
|
+
|
|
25
|
+
const railRef = useRef<HTMLDivElement | null>(null);
|
|
26
|
+
|
|
27
|
+
// Keep the mobile slider in sync with how far the tab row is scrolled.
|
|
28
|
+
const syncScroll = useCallback(() => {
|
|
29
|
+
const el = railRef.current;
|
|
30
|
+
if (!el) return;
|
|
31
|
+
setScroll({ value: el.scrollLeft, max: Math.max(0, el.scrollWidth - el.clientWidth) });
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
syncScroll();
|
|
36
|
+
const el = railRef.current;
|
|
37
|
+
if (!el) return;
|
|
38
|
+
el.addEventListener("scroll", syncScroll, { passive: true });
|
|
39
|
+
window.addEventListener("resize", syncScroll);
|
|
40
|
+
return () => {
|
|
41
|
+
el.removeEventListener("scroll", syncScroll);
|
|
42
|
+
window.removeEventListener("resize", syncScroll);
|
|
43
|
+
};
|
|
44
|
+
}, [syncScroll, items.length]);
|
|
23
45
|
|
|
24
46
|
if (items.length === 0) return null;
|
|
25
47
|
|
|
@@ -61,10 +83,13 @@ export function CabinTabsBlock({ props }: { props: CabinTabsProps }) {
|
|
|
61
83
|
<Reveal>
|
|
62
84
|
<div className="mt-10 grid gap-8 lg:grid-cols-[minmax(0,15rem)_minmax(0,1fr)_minmax(0,1.05fr)] lg:gap-10">
|
|
63
85
|
{/* Tabs */}
|
|
86
|
+
<div className="min-w-0">
|
|
87
|
+
<div className="relative lg:static">
|
|
64
88
|
<div
|
|
89
|
+
ref={railRef}
|
|
65
90
|
role="tablist"
|
|
66
91
|
aria-label={t(props.title) || "Categories"}
|
|
67
|
-
className="flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible lg:pb-0"
|
|
92
|
+
className="flex gap-2 overflow-x-auto pb-1 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden lg:flex-col lg:overflow-visible lg:pb-0"
|
|
68
93
|
>
|
|
69
94
|
{items.map((it, i) => {
|
|
70
95
|
const on = i === Math.min(active, items.length - 1);
|
|
@@ -86,6 +111,34 @@ export function CabinTabsBlock({ props }: { props: CabinTabsProps }) {
|
|
|
86
111
|
})}
|
|
87
112
|
</div>
|
|
88
113
|
|
|
114
|
+
{/* Arrow hint: there is more to the right. */}
|
|
115
|
+
{scroll.max > 4 && scroll.value < scroll.max - 4 ? (
|
|
116
|
+
<div
|
|
117
|
+
aria-hidden
|
|
118
|
+
className="pointer-events-none absolute right-0 top-1/2 -translate-y-1/2 bg-white/85 p-1 text-sea shadow-sm lg:hidden"
|
|
119
|
+
>
|
|
120
|
+
<ChevronRight className="size-5" />
|
|
121
|
+
</div>
|
|
122
|
+
) : null}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Slider: drag to move through the categories. */}
|
|
126
|
+
{scroll.max > 4 ? (
|
|
127
|
+
<input
|
|
128
|
+
type="range"
|
|
129
|
+
min={0}
|
|
130
|
+
max={scroll.max}
|
|
131
|
+
value={Math.min(scroll.value, scroll.max)}
|
|
132
|
+
aria-label="Bläddra bland hyttkategorier"
|
|
133
|
+
onChange={(e) => {
|
|
134
|
+
const el = railRef.current;
|
|
135
|
+
if (el) el.scrollLeft = Number(e.target.value);
|
|
136
|
+
}}
|
|
137
|
+
className="mt-3 h-1.5 w-full cursor-pointer appearance-none rounded-full bg-ink/12 accent-sea lg:hidden"
|
|
138
|
+
/>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
89
142
|
{/* Copy */}
|
|
90
143
|
<div className="min-w-0">
|
|
91
144
|
<h3 className="font-serif text-2xl md:text-3xl text-sea">
|