barua-ui 0.4.1 → 0.5.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/css/components/nav.css +5 -0
- package/css/utilities.css +57 -0
- package/dist/index.cjs +200 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +194 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/css/components/nav.css
CHANGED
|
@@ -522,6 +522,11 @@
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
/* Dropdown wrapper (CSS-only open via <details>) */
|
|
525
|
+
/* A menu used as a listbox can hold a long list — twenty blocks of a serial,
|
|
526
|
+
every region — so it scrolls inside itself rather than running off the
|
|
527
|
+
card or the screen. */
|
|
528
|
+
.b-menu[role="listbox"] { max-block-size: min(18rem, 50dvh); overflow-y: auto; overscroll-behavior: contain; }
|
|
529
|
+
|
|
525
530
|
.b-dropdown { position: relative; display: inline-block; }
|
|
526
531
|
.b-dropdown > summary { list-style: none; cursor: pointer; }
|
|
527
532
|
.b-dropdown > summary::-webkit-details-marker { display: none; }
|
package/css/utilities.css
CHANGED
|
@@ -599,4 +599,61 @@
|
|
|
599
599
|
@media (min-width: 768px) {
|
|
600
600
|
.b-hide-desktop { display: none !important; }
|
|
601
601
|
}
|
|
602
|
+
|
|
603
|
+
/* ---- Scroll: what a container's own position can drive ------------------
|
|
604
|
+
A container carrying data-b-scroll-geometry publishes --b-scroll-progress
|
|
605
|
+
(0–1) and --b-scroll-y, so a progress line or a parallax can be pure CSS.
|
|
606
|
+
.b-scroll-progress is that line: a hairline at the top of a scroll area
|
|
607
|
+
whose width is the distance scrolled. */
|
|
608
|
+
.b-scroll-progress {
|
|
609
|
+
position: sticky;
|
|
610
|
+
inset-block-start: 0;
|
|
611
|
+
z-index: var(--b-z-raised);
|
|
612
|
+
height: 2px;
|
|
613
|
+
width: calc(var(--b-scroll-progress, 0) * 100%);
|
|
614
|
+
background: var(--b-color-accent);
|
|
615
|
+
transition: width 80ms linear;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* A large title that condenses as its scroll area moves — SwiftUI's
|
|
619
|
+
navigation title, on the scroll timeline so the browser drives it. Put it
|
|
620
|
+
on .b-large-title or .b-mobile-header__large-title inside the scroller. */
|
|
621
|
+
@supports (animation-timeline: scroll()) {
|
|
622
|
+
.b-scroll-condense {
|
|
623
|
+
animation: b-condense linear both;
|
|
624
|
+
animation-timeline: scroll(nearest);
|
|
625
|
+
animation-range: 0 5rem;
|
|
626
|
+
transform-origin: 0 100%;
|
|
627
|
+
}
|
|
628
|
+
@keyframes b-condense {
|
|
629
|
+
to { scale: 0.62; opacity: 0.9; }
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/* A bar over content that has moved gains its edge. */
|
|
634
|
+
.is-scrolled > .b-mobile-header,
|
|
635
|
+
.is-scrolled > .b-topnav,
|
|
636
|
+
.is-scrolled > .b-marketing-nav,
|
|
637
|
+
.b-mobile-header.is-scrolled,
|
|
638
|
+
.b-topnav.is-scrolled,
|
|
639
|
+
.b-marketing-nav.is-scrolled {
|
|
640
|
+
box-shadow: var(--b-shadow-sm);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/* data-b-visible: the entrance for browsers without a view timeline, and a
|
|
644
|
+
hook any element can use — .is-visible arrives when the threshold does. */
|
|
645
|
+
@supports not (animation-timeline: view()) {
|
|
646
|
+
.b-reveal[data-b-visible]:not(.is-visible) { opacity: 0; translate: 0 12px; }
|
|
647
|
+
.b-reveal[data-b-visible] { transition: opacity var(--b-duration-slow) var(--b-ease-out), translate var(--b-duration-slow) var(--b-ease-out); }
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/* A backplate: glass put on any one element — SwiftUI's glassBackgroundEffect.
|
|
651
|
+
Blur behind, a lit top edge, a soft shadow, the system radius. */
|
|
652
|
+
.b-backplate {
|
|
653
|
+
background: var(--b-material-regular-bg);
|
|
654
|
+
-webkit-backdrop-filter: var(--b-glass);
|
|
655
|
+
backdrop-filter: var(--b-glass);
|
|
656
|
+
border-radius: var(--b-radius-xl);
|
|
657
|
+
box-shadow: var(--b-glass-edge), var(--b-shadow-md);
|
|
658
|
+
}
|
|
602
659
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -314,9 +314,14 @@ __export(index_exports, {
|
|
|
314
314
|
block: () => block,
|
|
315
315
|
buttonClasses: () => buttonClasses,
|
|
316
316
|
cn: () => cn,
|
|
317
|
+
haptic: () => haptic,
|
|
317
318
|
iconNames: () => iconNames,
|
|
318
319
|
useBaruaTheme: () => useBaruaTheme,
|
|
319
|
-
|
|
320
|
+
usePersistedState: () => usePersistedState,
|
|
321
|
+
useScrollGeometry: () => useScrollGeometry,
|
|
322
|
+
useSnapPosition: () => useSnapPosition,
|
|
323
|
+
useToast: () => useToast,
|
|
324
|
+
useVisible: () => useVisible
|
|
320
325
|
});
|
|
321
326
|
module.exports = __toCommonJS(index_exports);
|
|
322
327
|
|
|
@@ -2617,6 +2622,194 @@ var ContextMenuTarget = (0, import_react21.forwardRef)(
|
|
|
2617
2622
|
return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Component, { ref, "data-b-contextmenu": menu, ...rest, children });
|
|
2618
2623
|
}
|
|
2619
2624
|
);
|
|
2625
|
+
|
|
2626
|
+
// src/scroll.tsx
|
|
2627
|
+
var import_react22 = require("react");
|
|
2628
|
+
function readGeometry(el) {
|
|
2629
|
+
const page = !el || el === document.documentElement || el === document.body;
|
|
2630
|
+
const box = page ? document.documentElement : el;
|
|
2631
|
+
const x = page ? window.scrollX : el.scrollLeft;
|
|
2632
|
+
const y = page ? window.scrollY : el.scrollTop;
|
|
2633
|
+
const width = box.clientWidth, height = box.clientHeight;
|
|
2634
|
+
const contentWidth = box.scrollWidth, contentHeight = box.scrollHeight;
|
|
2635
|
+
return {
|
|
2636
|
+
x,
|
|
2637
|
+
y,
|
|
2638
|
+
width,
|
|
2639
|
+
height,
|
|
2640
|
+
contentWidth,
|
|
2641
|
+
contentHeight,
|
|
2642
|
+
progressX: contentWidth > width ? Math.min(1, x / (contentWidth - width)) : 0,
|
|
2643
|
+
progressY: contentHeight > height ? Math.min(1, y / (contentHeight - height)) : 0
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
function useScrollGeometry(ref, onChange) {
|
|
2647
|
+
const [geometry, setGeometry] = (0, import_react22.useState)(null);
|
|
2648
|
+
(0, import_react22.useEffect)(() => {
|
|
2649
|
+
const el = ref?.current ?? null;
|
|
2650
|
+
const page = !el || el === document.documentElement || el === document.body;
|
|
2651
|
+
const target = page ? window : el;
|
|
2652
|
+
let queued = false;
|
|
2653
|
+
const flush = () => {
|
|
2654
|
+
queued = false;
|
|
2655
|
+
const g = readGeometry(el);
|
|
2656
|
+
setGeometry(g);
|
|
2657
|
+
onChange?.(g);
|
|
2658
|
+
};
|
|
2659
|
+
const on = () => {
|
|
2660
|
+
if (!queued) {
|
|
2661
|
+
queued = true;
|
|
2662
|
+
requestAnimationFrame(flush);
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
target.addEventListener("scroll", on, { passive: true });
|
|
2666
|
+
const ro = !page && "ResizeObserver" in window ? new ResizeObserver(on) : null;
|
|
2667
|
+
if (ro && el) ro.observe(el);
|
|
2668
|
+
else window.addEventListener("resize", on);
|
|
2669
|
+
flush();
|
|
2670
|
+
return () => {
|
|
2671
|
+
target.removeEventListener("scroll", on);
|
|
2672
|
+
if (ro) ro.disconnect();
|
|
2673
|
+
else window.removeEventListener("resize", on);
|
|
2674
|
+
};
|
|
2675
|
+
}, [ref?.current]);
|
|
2676
|
+
return geometry;
|
|
2677
|
+
}
|
|
2678
|
+
function useVisible(ref, { threshold = 0.5 } = {}) {
|
|
2679
|
+
const [visible, setVisible] = (0, import_react22.useState)(false);
|
|
2680
|
+
(0, import_react22.useEffect)(() => {
|
|
2681
|
+
const el = ref.current;
|
|
2682
|
+
if (!el) return;
|
|
2683
|
+
if (!("IntersectionObserver" in window)) {
|
|
2684
|
+
setVisible(true);
|
|
2685
|
+
return;
|
|
2686
|
+
}
|
|
2687
|
+
const io = new IntersectionObserver(
|
|
2688
|
+
(entries) => {
|
|
2689
|
+
for (const entry of entries) {
|
|
2690
|
+
setVisible(threshold === 0 ? entry.isIntersecting : entry.intersectionRatio >= threshold);
|
|
2691
|
+
}
|
|
2692
|
+
},
|
|
2693
|
+
{ threshold: threshold === 0 ? [0] : [0, threshold] }
|
|
2694
|
+
);
|
|
2695
|
+
io.observe(el);
|
|
2696
|
+
return () => io.disconnect();
|
|
2697
|
+
}, [ref.current, threshold]);
|
|
2698
|
+
return visible;
|
|
2699
|
+
}
|
|
2700
|
+
function useSnapPosition(ref) {
|
|
2701
|
+
const [current, setCurrent] = (0, import_react22.useState)({ id: null, index: -1 });
|
|
2702
|
+
const items = (0, import_react22.useCallback)(() => ref.current ? Array.from(ref.current.children).filter((c) => !c.hidden) : [], [ref]);
|
|
2703
|
+
(0, import_react22.useEffect)(() => {
|
|
2704
|
+
const box = ref.current;
|
|
2705
|
+
if (!box) return;
|
|
2706
|
+
const announce = (el) => {
|
|
2707
|
+
if (!el) return;
|
|
2708
|
+
const list = items();
|
|
2709
|
+
const index = list.indexOf(el);
|
|
2710
|
+
if (index >= 0) setCurrent({ id: el.id || null, index });
|
|
2711
|
+
};
|
|
2712
|
+
const nearest = () => {
|
|
2713
|
+
const list = items();
|
|
2714
|
+
const horizontal = box.scrollWidth > box.clientWidth;
|
|
2715
|
+
const origin = box.getBoundingClientRect();
|
|
2716
|
+
let best = null, distance = Infinity;
|
|
2717
|
+
for (const c of list) {
|
|
2718
|
+
const r = c.getBoundingClientRect();
|
|
2719
|
+
const d = Math.abs(horizontal ? r.left - origin.left : r.top - origin.top);
|
|
2720
|
+
if (d < distance) {
|
|
2721
|
+
distance = d;
|
|
2722
|
+
best = c;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
return best;
|
|
2726
|
+
};
|
|
2727
|
+
let timer;
|
|
2728
|
+
const native = "onscrollsnapchange" in box;
|
|
2729
|
+
const onSnap = (e) => {
|
|
2730
|
+
const ev = e;
|
|
2731
|
+
announce(ev.snapTargetInline || ev.snapTargetBlock);
|
|
2732
|
+
};
|
|
2733
|
+
const settle = () => {
|
|
2734
|
+
clearTimeout(timer);
|
|
2735
|
+
timer = setTimeout(() => announce(nearest()), 90);
|
|
2736
|
+
};
|
|
2737
|
+
if (native) box.addEventListener("scrollsnapchange", onSnap);
|
|
2738
|
+
else box.addEventListener("onscrollend" in box ? "scrollend" : "scroll", settle, { passive: true });
|
|
2739
|
+
announce(nearest());
|
|
2740
|
+
return () => {
|
|
2741
|
+
box.removeEventListener("scrollsnapchange", onSnap);
|
|
2742
|
+
box.removeEventListener("scrollend", settle);
|
|
2743
|
+
box.removeEventListener("scroll", settle);
|
|
2744
|
+
clearTimeout(timer);
|
|
2745
|
+
};
|
|
2746
|
+
}, [ref.current]);
|
|
2747
|
+
const to = (0, import_react22.useCallback)(
|
|
2748
|
+
(target) => {
|
|
2749
|
+
const list = items();
|
|
2750
|
+
const el = typeof target === "number" ? list[target] : list.find((c) => c.id === target);
|
|
2751
|
+
el?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "start" });
|
|
2752
|
+
},
|
|
2753
|
+
[items]
|
|
2754
|
+
);
|
|
2755
|
+
return { ...current, to };
|
|
2756
|
+
}
|
|
2757
|
+
var HAPTICS = {
|
|
2758
|
+
selection: [8],
|
|
2759
|
+
impact: [16],
|
|
2760
|
+
success: [10, 30, 20],
|
|
2761
|
+
warning: [25, 40, 25],
|
|
2762
|
+
error: [40, 30, 40, 30, 60]
|
|
2763
|
+
};
|
|
2764
|
+
function haptic(kind = "selection") {
|
|
2765
|
+
if (typeof navigator === "undefined") return false;
|
|
2766
|
+
if (typeof navigator.vibrate === "function") {
|
|
2767
|
+
navigator.vibrate(HAPTICS[kind]);
|
|
2768
|
+
return true;
|
|
2769
|
+
}
|
|
2770
|
+
if (/iphone|ipad|ipod/i.test(navigator.userAgent)) {
|
|
2771
|
+
let sw = document.getElementById("b-haptic-switch");
|
|
2772
|
+
if (!sw) {
|
|
2773
|
+
sw = document.createElement("input");
|
|
2774
|
+
sw.type = "checkbox";
|
|
2775
|
+
sw.id = "b-haptic-switch";
|
|
2776
|
+
sw.tabIndex = -1;
|
|
2777
|
+
sw.setAttribute("switch", "");
|
|
2778
|
+
sw.setAttribute("aria-hidden", "true");
|
|
2779
|
+
sw.style.cssText = "position:fixed;inset:auto auto 0 0;width:1px;height:1px;opacity:0;pointer-events:none";
|
|
2780
|
+
document.body.appendChild(sw);
|
|
2781
|
+
}
|
|
2782
|
+
sw.click();
|
|
2783
|
+
return true;
|
|
2784
|
+
}
|
|
2785
|
+
return false;
|
|
2786
|
+
}
|
|
2787
|
+
var PERSIST = "barua-persist:";
|
|
2788
|
+
function usePersistedState(key, initial) {
|
|
2789
|
+
const [value, setValue] = (0, import_react22.useState)(() => {
|
|
2790
|
+
if (typeof window === "undefined") return initial;
|
|
2791
|
+
try {
|
|
2792
|
+
const raw = localStorage.getItem(PERSIST + key);
|
|
2793
|
+
return raw === null ? initial : JSON.parse(raw);
|
|
2794
|
+
} catch {
|
|
2795
|
+
return initial;
|
|
2796
|
+
}
|
|
2797
|
+
});
|
|
2798
|
+
const set = (0, import_react22.useCallback)(
|
|
2799
|
+
(next) => {
|
|
2800
|
+
setValue((prev) => {
|
|
2801
|
+
const resolved = typeof next === "function" ? next(prev) : next;
|
|
2802
|
+
try {
|
|
2803
|
+
localStorage.setItem(PERSIST + key, JSON.stringify(resolved));
|
|
2804
|
+
} catch {
|
|
2805
|
+
}
|
|
2806
|
+
return resolved;
|
|
2807
|
+
});
|
|
2808
|
+
},
|
|
2809
|
+
[key]
|
|
2810
|
+
);
|
|
2811
|
+
return [value, set];
|
|
2812
|
+
}
|
|
2620
2813
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2621
2814
|
0 && (module.exports = {
|
|
2622
2815
|
Accordion,
|
|
@@ -2912,8 +3105,13 @@ var ContextMenuTarget = (0, import_react21.forwardRef)(
|
|
|
2912
3105
|
block,
|
|
2913
3106
|
buttonClasses,
|
|
2914
3107
|
cn,
|
|
3108
|
+
haptic,
|
|
2915
3109
|
iconNames,
|
|
2916
3110
|
useBaruaTheme,
|
|
2917
|
-
|
|
3111
|
+
usePersistedState,
|
|
3112
|
+
useScrollGeometry,
|
|
3113
|
+
useSnapPosition,
|
|
3114
|
+
useToast,
|
|
3115
|
+
useVisible
|
|
2918
3116
|
});
|
|
2919
3117
|
//# sourceMappingURL=index.cjs.map
|