@mario_hahn/jumpy-ui 0.10.0 → 0.11.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/README.md CHANGED
@@ -211,7 +211,7 @@ Jumpy Components als ein gemeinsames UI-Paket.
211
211
  Starting with 0.5.0, Jumpy UI uses the public npmjs registry. No package token is required to install:
212
212
 
213
213
  ```bash
214
- npm install @mario_hahn/jumpy-ui@^0.10.0
214
+ npm install @mario_hahn/jumpy-ui@^0.11.0
215
215
  ```
216
216
 
217
217
  The public npm package uses the `@mario_hahn` scope. Older GitHub Packages releases
@@ -324,6 +324,10 @@ import { JumpAppShell, JumpSideMenu, JumpSideMenuItem, JumpTabBar, JumpTabBarIte
324
324
  - `topBar.leading` defaults to `'menu'` when `navigation` exists; `'back'` calls
325
325
  `onBack`. `topBar.title` falls back to `brand`. `topBar.desktop` keeps the bar on
326
326
  desktop (without the menu button). `topBar={null}` removes it.
327
+ - `topBar.largeTitle` (0.11.0+) renders `title`/`subtitle` as a large page `h1`; on
328
+ mobile the bar stays empty and blends into the page until that heading scrolls under
329
+ it, then shows a compact centered title. Set `--jumpy-app-shell-gutter` to match your
330
+ content padding. Do not render a second page heading yourself.
327
331
  - `JumpTopBar` and `JumpTabBar` also work standalone. Both are sticky and include
328
332
  safe-area insets. `JumpTabBarItem` accepts `as` (e.g. Next `Link`), `href`, `icon`,
329
333
  numeric or custom `badge` and `selected` (`aria-current="page"`).
@@ -9945,7 +9945,8 @@ var providerStyles = `
9945
9945
  }
9946
9946
 
9947
9947
  /* An icon SVG fills the leading slot: otherwise a 20px icon is squeezed to the slot width but keeps its height and drops below the label. */
9948
- [data-jumpy-theme='true'] :is([data-jumpy-component='list-item'], [data-jumpy-component='menu-item'], [data-jumpy-component='side-menu-item']) > [data-jumpy-slot='leading'] > svg {
9948
+ [data-jumpy-theme='true'] :is([data-jumpy-component='list-item'], [data-jumpy-component='menu-item'], [data-jumpy-component='side-menu-item']) > [data-jumpy-slot='leading'] > svg,
9949
+ [data-jumpy-theme='true'] [data-jumpy-component='tab-bar-item'] > [data-jumpy-slot='tab-bar-item-icon'] > svg {
9949
9950
  width: 100%;
9950
9951
  height: 100%;
9951
9952
  flex: none;
@@ -11869,6 +11870,19 @@ var providerStyles = `
11869
11870
  box-shadow: var(--jumpy-shadow-positive-small);
11870
11871
  }
11871
11872
 
11873
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'] > [data-jumpy-slot='top-bar-title'] {
11874
+ transition: opacity var(--jumpy-animation-duration-xsmall) var(--jumpy-animation-easing-drift);
11875
+ }
11876
+
11877
+ /* Large-title mode: until the page title scrolls under it, the bar blends into the page (iOS). */
11878
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'][data-large-title='true'][data-title-hidden='true'] {
11879
+ background-color: var(--jumpy-color-surface-canvas);
11880
+ border-bottom-color: transparent;
11881
+ box-shadow: none;
11882
+ -webkit-backdrop-filter: none;
11883
+ backdrop-filter: none;
11884
+ }
11885
+
11872
11886
  [data-jumpy-theme='true'] [data-jumpy-component='tab-bar-item'] {
11873
11887
  transition: background-color var(--jumpy-animation-duration-xsmall) var(--jumpy-animation-easing-drift), color var(--jumpy-animation-duration-xsmall) var(--jumpy-animation-easing-drift);
11874
11888
  -webkit-tap-highlight-color: transparent;
@@ -11887,13 +11901,43 @@ var providerStyles = `
11887
11901
  animation: jumpy-app-shell-drawer-in var(--jumpy-animation-duration-small) var(--jumpy-animation-easing-drift);
11888
11902
  }
11889
11903
 
11904
+ [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer'][data-state='closing'] {
11905
+ animation: jumpy-app-shell-drawer-out var(--jumpy-animation-duration-small) var(--jumpy-animation-easing-release) forwards;
11906
+ pointer-events: none;
11907
+ }
11908
+
11909
+ [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer-overlay'] {
11910
+ animation: jumpy-app-shell-overlay-in var(--jumpy-animation-duration-small) var(--jumpy-animation-easing-drift);
11911
+ }
11912
+
11913
+ [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer-overlay'][data-state='closing'] {
11914
+ animation: jumpy-app-shell-overlay-out var(--jumpy-animation-duration-small) var(--jumpy-animation-easing-release) forwards;
11915
+ pointer-events: none;
11916
+ }
11917
+
11890
11918
  @keyframes jumpy-app-shell-drawer-in {
11891
11919
  from { transform: translateX(-100%); }
11892
11920
  to { transform: translateX(0); }
11893
11921
  }
11894
11922
 
11923
+ @keyframes jumpy-app-shell-drawer-out {
11924
+ from { transform: translateX(0); }
11925
+ to { transform: translateX(-100%); }
11926
+ }
11927
+
11928
+ @keyframes jumpy-app-shell-overlay-in {
11929
+ from { opacity: 0; }
11930
+ to { opacity: 1; }
11931
+ }
11932
+
11933
+ @keyframes jumpy-app-shell-overlay-out {
11934
+ from { opacity: 1; }
11935
+ to { opacity: 0; }
11936
+ }
11937
+
11895
11938
  @media (prefers-reduced-motion: reduce) {
11896
- [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer'] {
11939
+ [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer'],
11940
+ [data-jumpy-theme='true'] [data-jumpy-component='app-shell-drawer-overlay'] {
11897
11941
  animation: none;
11898
11942
  }
11899
11943
  }
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  textStyleValue,
29
29
  themeToCSSVariables,
30
30
  useJumpyTheme
31
- } from "./chunk-LORNGSIB.js";
31
+ } from "./chunk-V3XYXQPA.js";
32
32
  import {
33
33
  cn
34
34
  } from "./chunk-3DBKYKP2.js";
@@ -2416,7 +2416,7 @@ function JumpyNavigationIcon({ type }) {
2416
2416
  };
2417
2417
  return /* @__PURE__ */ jsx2("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", "data-jumpy-stroke-icon": "true", style: { display: "block" }, children: /* @__PURE__ */ jsx2("path", { d: paths[type] }) });
2418
2418
  }
2419
- function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, ariaLabels = {}, className = "", ...props }) {
2419
+ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, titleAlign = "start", titleHidden = false, ariaLabels = {}, className = "", ...props }) {
2420
2420
  const theme = useJumpyTheme();
2421
2421
  const [scrolled, setScrolled] = useState2(false);
2422
2422
  const titleStyle = textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized);
@@ -2437,6 +2437,8 @@ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle
2437
2437
  "data-jumpy-component": "top-bar",
2438
2438
  "data-sticky": sticky ? "true" : "false",
2439
2439
  "data-scrolled": scrolled ? "true" : "false",
2440
+ "data-title-align": titleAlign,
2441
+ "data-title-hidden": titleHidden ? "true" : "false",
2440
2442
  className: cn(className),
2441
2443
  style: {
2442
2444
  boxSizing: "border-box",
@@ -2453,11 +2455,25 @@ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle
2453
2455
  },
2454
2456
  children: [
2455
2457
  leadingNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-leading", "data-leading": leadingType, style: { marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: leadingNode }) : null,
2456
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "top-bar-title", style: { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0 }, children: [
2457
- title ? /* @__PURE__ */ jsx2("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: titleStyle.fontSize, lineHeight: titleStyle.lineHeight, fontWeight: titleStyle.fontWeight }, children: title }) : null,
2458
- subtitle ? /* @__PURE__ */ jsx2("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--jumpy-color-text-secondary)", fontSize: subtitleStyle.fontSize, lineHeight: subtitleStyle.lineHeight }, children: subtitle }) : null
2458
+ /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "top-bar-title", "aria-hidden": titleHidden || void 0, style: titleAlign === "center" ? {
2459
+ position: "absolute",
2460
+ left: `calc(${sizeValue(theme, JumpySize.largeXL) + spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`,
2461
+ right: `calc(${sizeValue(theme, JumpySize.largeXL) + spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px))`,
2462
+ top: "env(safe-area-inset-top, 0px)",
2463
+ bottom: 0,
2464
+ display: "flex",
2465
+ flexDirection: "column",
2466
+ alignItems: "center",
2467
+ justifyContent: "center",
2468
+ textAlign: "center",
2469
+ minWidth: 0,
2470
+ pointerEvents: "none",
2471
+ opacity: titleHidden ? 0 : 1
2472
+ } : { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0, opacity: titleHidden ? 0 : 1 }, children: [
2473
+ title ? /* @__PURE__ */ jsx2("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: titleStyle.fontSize, lineHeight: titleStyle.lineHeight, fontWeight: titleStyle.fontWeight }, children: title }) : null,
2474
+ subtitle ? /* @__PURE__ */ jsx2("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--jumpy-color-text-secondary)", fontSize: subtitleStyle.fontSize, lineHeight: subtitleStyle.lineHeight }, children: subtitle }) : null
2459
2475
  ] }),
2460
- trailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-trailing", style: { display: "inline-flex", flex: "0 0 auto", alignItems: "center", gap: spacingValue(theme, JumpySpacing.smallXS) }, children: trailing }) : null
2476
+ trailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-trailing", style: { display: "inline-flex", flex: "0 0 auto", marginLeft: "auto", alignItems: "center", gap: spacingValue(theme, JumpySpacing.smallXS) }, children: trailing }) : null
2461
2477
  ]
2462
2478
  }
2463
2479
  );
@@ -2504,7 +2520,7 @@ function JumpTabBar({ children, className = "", "aria-label": ariaLabel = "Main
2504
2520
  function JumpTabBarItem({ as, icon, badge, selected = false, children, className = "", ...props }) {
2505
2521
  const theme = useJumpyTheme();
2506
2522
  const Component = as || (props.href ? "a" : "button");
2507
- const labelStyle = textStyleValue(theme, JumpyTextStyle.labelSmallEmphasized);
2523
+ const labelStyle = textStyleValue(theme, JumpyTextStyle.labelSmall);
2508
2524
  const badgeNode = typeof badge === "number" ? badge > 0 ? /* @__PURE__ */ jsx2(JumpBadge, { size: "small", status: "active", "aria-label": void 0, role: "presentation", "aria-live": "off", children: badge > 99 ? "99+" : badge }) : null : badge;
2509
2525
  return /* @__PURE__ */ jsxs2(
2510
2526
  Component,
@@ -2539,7 +2555,7 @@ function JumpTabBarItem({ as, icon, badge, selected = false, children, className
2539
2555
  ...props.style || {}
2540
2556
  },
2541
2557
  children: [
2542
- icon ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "tab-bar-item-icon", "aria-hidden": "true", style: { position: "relative", display: "inline-flex", width: sizeValue(theme, JumpySize.small2XS), height: sizeValue(theme, JumpySize.small2XS) }, children: [
2558
+ icon ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "tab-bar-item-icon", "aria-hidden": "true", style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: sizeValue(theme, JumpySize.smallXS), height: sizeValue(theme, JumpySize.smallXS) }, children: [
2543
2559
  icon,
2544
2560
  badgeNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "tab-bar-item-badge", style: { position: "absolute", top: -spacingValue(theme, JumpySpacing.smallXS), left: "50%", marginLeft: spacingValue(theme, JumpySpacing.small3XS), display: "inline-flex" }, children: badgeNode }) : null
2545
2561
  ] }) : null,
@@ -2555,8 +2571,14 @@ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTa
2555
2571
  const isOpen = Boolean(navigation) && (navigationOpen ?? internalOpen);
2556
2572
  const swipeStart = useRef2(null);
2557
2573
  const rootRef = useRef2(null);
2558
- const wasOpen = useRef2(isOpen);
2559
- const { desktop: topBarOnDesktop = false, leading, ...topBarProps } = topBar || {};
2574
+ const [drawerMounted, setDrawerMounted] = useState2(isOpen);
2575
+ const showDrawer = isOpen || drawerMounted;
2576
+ const closing = drawerMounted && !isOpen;
2577
+ const wasShown = useRef2(showDrawer);
2578
+ const { desktop: topBarOnDesktop = false, leading, largeTitle = false, ...topBarProps } = topBar || {};
2579
+ const showLargeTitle = largeTitle && Boolean(topBarProps.title);
2580
+ const largeTitleRef = useRef2(null);
2581
+ const [largeTitleCovered, setLargeTitleCovered] = useState2(false);
2560
2582
  const resolvedLeading = leading ?? (navigation ? "menu" : void 0);
2561
2583
  const shell = `[data-jumpy-app-shell="${shellId}"]`;
2562
2584
  const width = typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
@@ -2575,15 +2597,37 @@ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTa
2575
2597
  return () => desktop.removeEventListener("change", closeOnDesktop);
2576
2598
  });
2577
2599
  useEffect2(() => {
2578
- const closed = wasOpen.current && !isOpen;
2579
- wasOpen.current = isOpen;
2600
+ if (isOpen) {
2601
+ setDrawerMounted(true);
2602
+ return void 0;
2603
+ }
2604
+ if (!drawerMounted) return void 0;
2605
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2606
+ setDrawerMounted(false);
2607
+ return void 0;
2608
+ }
2609
+ const timer = window.setTimeout(() => setDrawerMounted(false), animationDurationValue(theme, JumpyAnimationDuration.small) * 1e3 + 100);
2610
+ return () => window.clearTimeout(timer);
2611
+ }, [isOpen, drawerMounted]);
2612
+ useEffect2(() => {
2613
+ const closed = wasShown.current && !showDrawer;
2614
+ wasShown.current = showDrawer;
2580
2615
  if (!closed) return void 0;
2581
2616
  const frame = window.requestAnimationFrame(() => {
2582
2617
  if (document.activeElement && document.activeElement !== document.body) return;
2583
2618
  rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"] > [data-leading="menu"] button')?.focus();
2584
2619
  });
2585
2620
  return () => window.cancelAnimationFrame(frame);
2586
- }, [isOpen]);
2621
+ }, [showDrawer]);
2622
+ useEffect2(() => {
2623
+ const element = largeTitleRef.current;
2624
+ if (!showLargeTitle || !element || typeof IntersectionObserver === "undefined") return void 0;
2625
+ const bar = rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"]');
2626
+ const barHeight = bar && bar.getBoundingClientRect().height > 0 ? bar.getBoundingClientRect().height : 0;
2627
+ const observer = new IntersectionObserver(([entry]) => setLargeTitleCovered(!entry.isIntersecting), { rootMargin: `-${Math.round(barHeight)}px 0px 0px 0px` });
2628
+ observer.observe(element);
2629
+ return () => observer.disconnect();
2630
+ }, [showLargeTitle, topBarProps.title]);
2587
2631
  function closeOnNavigate(event) {
2588
2632
  const item = event.target.closest?.('a[href], [data-jumpy-component="side-menu-item"]');
2589
2633
  if (!item || item.hasAttribute("aria-expanded") || item.getAttribute("aria-disabled") === "true" || item.disabled) return;
@@ -2608,6 +2652,10 @@ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2608
2652
  menuOpen: isOpen,
2609
2653
  onMenu: () => setOpen(true),
2610
2654
  title: topBarProps.title ?? brand,
2655
+ subtitle: showLargeTitle ? void 0 : topBarProps.subtitle,
2656
+ titleAlign: showLargeTitle ? "center" : topBarProps.titleAlign,
2657
+ titleHidden: showLargeTitle ? !largeTitleCovered : topBarProps.titleHidden,
2658
+ "data-large-title": showLargeTitle ? "true" : void 0,
2611
2659
  ariaLabels: { menu: ariaLabels.openNavigation, back: ariaLabels.back, ...topBarProps.ariaLabels || {} }
2612
2660
  }
2613
2661
  );
@@ -2641,29 +2689,39 @@ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2641
2689
  top: 0,
2642
2690
  height: "100dvh",
2643
2691
  flexDirection: "column",
2644
- overflowY: "auto",
2692
+ overflow: "hidden",
2645
2693
  borderRight: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`,
2646
2694
  backgroundColor: "var(--jumpy-color-surface-card)"
2647
2695
  },
2648
2696
  children: [
2649
- brand ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-brand", style: { padding: spacingValue(theme, JumpySpacing.large2XL), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: brand }) : null,
2650
- navigation ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-navigation", style: { flex: 1, padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }) : null,
2651
- sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-sidebar-footer", style: { padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: sidebarFooter }) : null
2697
+ brand ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-brand", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large2XL), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: brand }) : null,
2698
+ navigation ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }) : null,
2699
+ sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-sidebar-footer", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: sidebarFooter }) : null
2652
2700
  ]
2653
2701
  }
2654
2702
  ),
2655
2703
  /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "app-shell-body", style: { display: "flex", flexDirection: "column", minWidth: 0, minHeight: "100dvh" }, children: [
2656
2704
  topBar !== null ? topBarNode : null,
2657
- /* @__PURE__ */ jsx2("main", { "data-jumpy-slot": "app-shell-content", style: { flex: 1, minWidth: 0 }, children }),
2705
+ /* @__PURE__ */ jsxs2("main", { "data-jumpy-slot": "app-shell-content", style: { flex: 1, minWidth: 0 }, children: [
2706
+ showLargeTitle ? /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "app-shell-large-title", style: { padding: `${spacingValue(theme, JumpySpacing.smallXS)}px calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-right, 0px)) 0 calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-left, 0px))` }, children: [
2707
+ /* @__PURE__ */ jsx2("h1", { ref: largeTitleRef, style: { margin: 0, ...textStyleValue(theme, JumpyTextStyle.displaySmallEmphasized), overflowWrap: "anywhere" }, children: topBarProps.title }),
2708
+ topBarProps.subtitle ? /* @__PURE__ */ jsx2("p", { style: { margin: `${spacingValue(theme, JumpySpacing.small4XS)}px 0 0`, color: "var(--jumpy-color-text-secondary)", ...textStyleValue(theme, JumpyTextStyle.bodyMedium) }, children: topBarProps.subtitle }) : null
2709
+ ] }) : null,
2710
+ children
2711
+ ] }),
2658
2712
  tabBar && !hideTabBar ? tabBar : null
2659
2713
  ] }),
2660
- isOpen ? /* @__PURE__ */ jsx2(JumpyModalLayer, { onVisibleChange: setOpen, "data-jumpy-component": "app-shell-drawer-overlay", style: { position: "fixed", inset: 0, zIndex: 60, backgroundColor: "var(--jumpy-color-surface-overlay)" }, children: /* @__PURE__ */ jsxs2(
2714
+ showDrawer ? /* @__PURE__ */ jsx2(JumpyModalLayer, { onVisibleChange: setOpen, "data-jumpy-component": "app-shell-drawer-overlay", "data-state": closing ? "closing" : "open", style: { position: "fixed", inset: 0, zIndex: 60, backgroundColor: "var(--jumpy-color-surface-overlay)" }, children: /* @__PURE__ */ jsxs2(
2661
2715
  "div",
2662
2716
  {
2663
2717
  role: "dialog",
2664
2718
  "aria-modal": "true",
2665
2719
  "aria-label": ariaLabels.navigation || "Navigation",
2666
2720
  "data-jumpy-component": "app-shell-drawer",
2721
+ "data-state": closing ? "closing" : "open",
2722
+ onAnimationEnd: (event) => {
2723
+ if (closing && event.target === event.currentTarget) setDrawerMounted(false);
2724
+ },
2667
2725
  onClick: closeOnNavigate,
2668
2726
  onTouchStart: (event) => {
2669
2727
  const touch = event.touches[0];
@@ -2686,20 +2744,25 @@ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2686
2744
  flexDirection: "column",
2687
2745
  width: "100vw",
2688
2746
  height: "100dvh",
2689
- overflowY: "auto",
2747
+ overflow: "hidden",
2690
2748
  outline: "none",
2691
2749
  backgroundColor: "var(--jumpy-color-surface-card)",
2692
2750
  color: "var(--jumpy-color-text-primary)",
2693
- fontFamily: theme.fontFamily.familyBase,
2694
- paddingBottom: "env(safe-area-inset-bottom, 0px)"
2751
+ fontFamily: theme.fontFamily.familyBase
2695
2752
  },
2696
2753
  children: [
2697
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "app-shell-drawer-header", style: { boxSizing: "border-box", display: "grid", gridTemplateColumns: "auto minmax(0, 1fr)", alignItems: "center", gap: spacingValue(theme, JumpySpacing.medium), minHeight: `calc(${sizeValue(theme, JumpySize.large2XL)}px + env(safe-area-inset-top, 0px))`, padding: `calc(${spacingValue(theme, JumpySpacing.smallXS)}px + env(safe-area-inset-top, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) ${spacingValue(theme, JumpySpacing.smallXS)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`, borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: [
2698
- /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.closeNavigation || "Close navigation", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "close" }) }) }),
2699
- /* @__PURE__ */ jsx2("span", { style: { minWidth: 0 }, children: brand })
2700
- ] }),
2701
- /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-navigation", style: { flex: 1, padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }),
2702
- sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-footer", style: { padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: sidebarFooter }) : null
2754
+ /* @__PURE__ */ jsx2(
2755
+ JumpTopBar,
2756
+ {
2757
+ "data-jumpy-slot": "app-shell-drawer-header",
2758
+ sticky: false,
2759
+ style: { flex: "0 0 auto" },
2760
+ leading: /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.closeNavigation || "Close navigation", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "close" }) }),
2761
+ title: brand
2762
+ }
2763
+ ),
2764
+ /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }),
2765
+ sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-footer", style: { flex: "0 0 auto", padding: `${spacingValue(theme, JumpySpacing.large)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-bottom, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`, borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`, backgroundColor: "var(--jumpy-color-surface-card)" }, children: sidebarFooter }) : null
2703
2766
  ]
2704
2767
  }
2705
2768
  ) }) : null
package/dist/theme.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  textStyleValue,
29
29
  themeToCSSVariables,
30
30
  useJumpyTheme
31
- } from "./chunk-LORNGSIB.js";
31
+ } from "./chunk-V3XYXQPA.js";
32
32
  export {
33
33
  JumpyAnimation,
34
34
  JumpyAnimationDelay,
package/index.d.ts CHANGED
@@ -111,12 +111,12 @@ export declare function JumpTableCell(props: Omit<TdHTMLAttributes<HTMLTableCell
111
111
  export declare function JumpTableHeaderCell(props: Omit<ThHTMLAttributes<HTMLTableCellElement>, 'align'> & { as?: ElementTag; ref?: Ref<HTMLTableCellElement>; selected?: boolean; disabled?: boolean; align?: JumpTableAlign; variant?: 'default' | 'control'; padding?: 'default' | 'none' | 'checkbox'; showHorizontalBorder?: boolean; pinned?: JumpTablePinned; pinnedEdge?: boolean; sortable?: boolean; sortOrder?: JumpTableSortOrder; onSortChange?: (sortOrder: JumpTableSortOrder) => void; ariaLabels?: { sort?: string | ((sortOrder: JumpTableSortOrder) => string) }; leading?: ReactNode; trailing?: ReactNode; slots?: { leading?: ReactNode; trailing?: ReactNode }; innerClasses?: JumpTableCellInnerClasses; children?: ReactNode }): ReactNode
112
112
  export declare function JumpSheet(props: HTMLAttributes<HTMLElement> & { visible?: boolean; onVisibleChange?: (visible: boolean) => void; position?: 'top' | 'right' | 'bottom' | 'left'; title?: string; subtitle?: string; footer?: ReactNode; children?: ReactNode }): ReactNode
113
113
  export type JumpTopBarLeading = 'menu' | 'back' | ReactNode
114
- export type JumpTopBarProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & { leading?: JumpTopBarLeading; onMenu?: () => void; onBack?: () => void; menuOpen?: boolean; title?: ReactNode; subtitle?: ReactNode; trailing?: ReactNode; sticky?: boolean; ariaLabels?: { menu?: string; back?: string } }
114
+ export type JumpTopBarProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & { leading?: JumpTopBarLeading; onMenu?: () => void; onBack?: () => void; menuOpen?: boolean; title?: ReactNode; subtitle?: ReactNode; trailing?: ReactNode; sticky?: boolean; titleAlign?: 'start' | 'center'; titleHidden?: boolean; ariaLabels?: { menu?: string; back?: string } }
115
115
  export declare function JumpTopBar(props: JumpTopBarProps): ReactNode
116
116
  export declare function JumpTabBar(props: HTMLAttributes<HTMLElement> & { children: ReactNode }): ReactNode
117
117
  export type JumpTabBarItemProps = { as?: ElementType; href?: string; icon?: ReactNode; badge?: ReactNode | number; selected?: boolean; children: ReactNode; className?: string; style?: CSSProperties } & Record<string, unknown>
118
118
  export declare function JumpTabBarItem(props: JumpTabBarItemProps): ReactNode
119
- export type JumpAppShellTopBar = Omit<JumpTopBarProps, 'onMenu' | 'menuOpen'> & { desktop?: boolean }
119
+ export type JumpAppShellTopBar = Omit<JumpTopBarProps, 'onMenu' | 'menuOpen'> & { desktop?: boolean; largeTitle?: boolean }
120
120
  export declare function JumpAppShell(props: HTMLAttributes<HTMLDivElement> & { brand?: ReactNode; navigation?: ReactNode; sidebarFooter?: ReactNode; topBar?: JumpAppShellTopBar | null; tabBar?: ReactNode; hideTabBar?: boolean; breakpoint?: number; sidebarWidth?: number | string; navigationOpen?: boolean; defaultNavigationOpen?: boolean; onNavigationOpenChange?: (open: boolean) => void; ariaLabels?: { navigation?: string; openNavigation?: string; closeNavigation?: string; back?: string }; children?: ReactNode }): ReactNode
121
121
  export declare function JumpSectionHeader(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; variant?: 'regular' | 'prominent'; description?: ReactNode; children: ReactNode }): ReactNode
122
122
  export declare function JumpSectionFooter(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; children: ReactNode }): ReactNode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mario_hahn/jumpy-ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {