@mario_hahn/jumpy-ui 0.11.0 → 0.13.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.11.0
214
+ npm install @mario_hahn/jumpy-ui@^0.13.0
215
215
  ```
216
216
 
217
217
  The public npm package uses the `@mario_hahn` scope. Older GitHub Packages releases
@@ -328,6 +328,16 @@ import { JumpAppShell, JumpSideMenu, JumpSideMenuItem, JumpTabBar, JumpTabBarIte
328
328
  mobile the bar stays empty and blends into the page until that heading scrolls under
329
329
  it, then shows a compact centered title. Set `--jumpy-app-shell-gutter` to match your
330
330
  content padding. Do not render a second page heading yourself.
331
+ - `topBar.actions` (0.12.0+): `[{ label, icon, onClick | href, variant }]`. Mobile shows
332
+ icon-only buttons (`label` becomes the accessible name); with three or more, the first
333
+ stays visible and the rest go into a "More actions" menu. Desktop shows labelled
334
+ buttons beside the large title, otherwise in the top bar. Prefer it over `trailing`
335
+ for ordinary actions; keep `trailing` for custom content such as an avatar.
336
+ - `topBar.back` (0.13.0+): `{ label, href, as: Link }` for detail pages ("up" to the
337
+ parent). Mobile: "‹ label" replaces the menu button; desktop: link above the large
338
+ title or in the top bar. Returns through history when the previous entry is that
339
+ parent, otherwise follows `href` (deep links stay in the app). Keep the browser's own
340
+ back (button, Safari edge swipe) working: every page change needs a history entry.
331
341
  - `JumpTopBar` and `JumpTabBar` also work standalone. Both are sticky and include
332
342
  safe-area insets. `JumpTabBarItem` accepts `as` (e.g. Next `Link`), `href`, `icon`,
333
343
  numeric or custom `badge` and `selected` (`aria-current="page"`).
@@ -11853,6 +11853,27 @@ var providerStyles = `
11853
11853
  flex: 0 0 auto;
11854
11854
  }
11855
11855
 
11856
+ /* Centered title (iOS navigation bar): equal side columns keep the title centered however many buttons sit left or right.
11857
+ Explicit columns so a hidden menu button does not shift the title. */
11858
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'][data-title-align='center'] {
11859
+ display: grid;
11860
+ grid-template-columns: minmax(0, 1fr) minmax(0, max-content) minmax(0, 1fr);
11861
+ }
11862
+
11863
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'][data-title-align='center'] > [data-jumpy-slot='top-bar-leading'] {
11864
+ grid-column: 1;
11865
+ justify-self: start;
11866
+ }
11867
+
11868
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'][data-title-align='center'] > [data-jumpy-slot='top-bar-title'] {
11869
+ grid-column: 2;
11870
+ }
11871
+
11872
+ [data-jumpy-theme='true'] [data-jumpy-component='top-bar'][data-title-align='center'] > [data-jumpy-slot='top-bar-trailing'] {
11873
+ grid-column: 3;
11874
+ justify-self: end;
11875
+ }
11876
+
11856
11877
  [data-jumpy-theme='true'] [data-jumpy-component='tab-bar'] [data-jumpy-slot='tab-bar-surface'] {
11857
11878
  background-color: var(--jumpy-color-surface-card);
11858
11879
  }
@@ -11883,6 +11904,25 @@ var providerStyles = `
11883
11904
  backdrop-filter: none;
11884
11905
  }
11885
11906
 
11907
+ [data-jumpy-theme='true'] [data-jumpy-slot='app-shell-back'] {
11908
+ display: inline-flex;
11909
+ -webkit-tap-highlight-color: transparent;
11910
+ }
11911
+
11912
+ /* Only the label truncates; the chevron keeps its size. */
11913
+ [data-jumpy-theme='true'] [data-jumpy-slot='app-shell-back'] > svg {
11914
+ flex: none;
11915
+ }
11916
+
11917
+ [data-jumpy-theme='true'] [data-jumpy-slot='app-shell-back']:hover {
11918
+ color: var(--jumpy-interactive-active-regular-hover-text);
11919
+ }
11920
+
11921
+ [data-jumpy-theme='true'] [data-jumpy-slot='app-shell-back']:focus-visible {
11922
+ outline: var(--jumpy-spacing-outline-medium) solid var(--jumpy-interactive-inactive-regular-focus-focus-ring);
11923
+ outline-offset: var(--jumpy-spacing-outline-small);
11924
+ }
11925
+
11886
11926
  [data-jumpy-theme='true'] [data-jumpy-component='tab-bar-item'] {
11887
11927
  transition: background-color var(--jumpy-animation-duration-xsmall) var(--jumpy-animation-easing-drift), color var(--jumpy-animation-duration-xsmall) var(--jumpy-animation-easing-drift);
11888
11928
  -webkit-tap-highlight-color: transparent;
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  textStyleValue,
29
29
  themeToCSSVariables,
30
30
  useJumpyTheme
31
- } from "./chunk-V3XYXQPA.js";
31
+ } from "./chunk-2I6OOQNV.js";
32
32
  import {
33
33
  cn
34
34
  } from "./chunk-3DBKYKP2.js";
@@ -1642,7 +1642,7 @@ function JumpPopover({ open, defaultOpen = false, onOpenChange, trigger, childre
1642
1642
  visible ? /* @__PURE__ */ jsx2("div", { role: "dialog", "data-jumpy-component": "popover", style: { position: "absolute", top: `calc(100% + ${spacingValue(theme, JumpySpacing.smallXS)}px)`, left: 0, zIndex: 30, minWidth: 220, borderRadius: radiusValue(theme, JumpyRadius.large), border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`, backgroundColor: theme.color.surfaceCard, color: theme.color.textPrimary, boxShadow: theme.shadow.positiveMedium, padding: spacingValue(theme, JumpySpacing.large) }, children }) : null
1643
1643
  ] });
1644
1644
  }
1645
- function JumpMenu({ trigger, open, defaultOpen = false, onOpenChange, header, footer, children, className = "", placement = "bottom-start", closeOnSelect = true, ...props }) {
1645
+ function JumpMenu({ trigger, triggerProps = {}, open, defaultOpen = false, onOpenChange, header, footer, children, className = "", placement = "bottom-start", closeOnSelect = true, ...props }) {
1646
1646
  const theme = useJumpyTheme();
1647
1647
  const [internalOpen, setInternalOpen] = useState2(defaultOpen);
1648
1648
  const isOpen = open ?? internalOpen;
@@ -1748,7 +1748,7 @@ function JumpMenu({ trigger, open, defaultOpen = false, onOpenChange, header, fo
1748
1748
  refs.domReference.current?.focus?.();
1749
1749
  }
1750
1750
  return /* @__PURE__ */ jsxs2("span", { "data-jumpy-component": "menu-root", className: cn(className), onPointerDown: markInteractionInside, onFocus: markInteractionInside, style: { display: "inline-flex", ...props.style || {} }, children: [
1751
- /* @__PURE__ */ jsx2("span", { ref: refs.setReference, "aria-haspopup": "menu", "aria-expanded": isOpen, tabIndex: 0, onClick: () => setOpen(!isOpen), onKeyDown: handleTriggerKeyDown, children: trigger }),
1751
+ /* @__PURE__ */ jsx2("span", { ...triggerProps, ref: refs.setReference, "aria-haspopup": "menu", "aria-expanded": isOpen, tabIndex: 0, onClick: () => setOpen(!isOpen), onKeyDown: handleTriggerKeyDown, children: trigger }),
1752
1752
  isOpen ? /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsxs2(
1753
1753
  "div",
1754
1754
  {
@@ -2408,13 +2408,14 @@ function JumpSheet({ visible = false, onVisibleChange, position = "right", title
2408
2408
  footer ? /* @__PURE__ */ jsx2("div", { style: { padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` }, children: footer }) : null
2409
2409
  ] }) });
2410
2410
  }
2411
- function JumpyNavigationIcon({ type }) {
2411
+ function JumpyNavigationIcon({ type, size: size2 = 24 }) {
2412
2412
  const paths = {
2413
2413
  menu: "M4 6h16M4 12h16M4 18h16",
2414
2414
  close: "M6 6l12 12M18 6 6 18",
2415
- back: "M15 18l-6-6 6-6"
2415
+ back: "M15 18l-6-6 6-6",
2416
+ more: "M5 12h.01M12 12h.01M19 12h.01"
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
+ return /* @__PURE__ */ jsx2("svg", { width: size2, height: size2, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: type === "more" ? 3 : 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
2419
  }
2419
2420
  function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, titleAlign = "start", titleHidden = false, ariaLabels = {}, className = "", ...props }) {
2420
2421
  const theme = useJumpyTheme();
@@ -2454,20 +2455,13 @@ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle
2454
2455
  ...props.style || {}
2455
2456
  },
2456
2457
  children: [
2457
- leadingNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-leading", "data-leading": leadingType, style: { marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: leadingNode }) : null,
2458
+ leadingNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-leading", "data-leading": leadingType, style: { minWidth: 0, maxWidth: "100%", marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: leadingNode }) : null,
2458
2459
  /* @__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
2460
  display: "flex",
2465
2461
  flexDirection: "column",
2466
2462
  alignItems: "center",
2467
- justifyContent: "center",
2468
2463
  textAlign: "center",
2469
2464
  minWidth: 0,
2470
- pointerEvents: "none",
2471
2465
  opacity: titleHidden ? 0 : 1
2472
2466
  } : { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0, opacity: titleHidden ? 0 : 1 }, children: [
2473
2467
  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,
@@ -2564,6 +2558,93 @@ function JumpTabBarItem({ as, icon, badge, selected = false, children, className
2564
2558
  }
2565
2559
  );
2566
2560
  }
2561
+ var jumpyLocations = { current: null, previous: null };
2562
+ function jumpyNormalizeUrl(url) {
2563
+ const resolved = new URL(url, window.location.href);
2564
+ const path = resolved.pathname.length > 1 ? resolved.pathname.replace(/\/+$/, "") : resolved.pathname;
2565
+ return `${resolved.origin}${path}${resolved.search === "?" ? "" : resolved.search}`;
2566
+ }
2567
+ function jumpyPreviousEntryIs(href) {
2568
+ const target = jumpyNormalizeUrl(href);
2569
+ const navigation = window.navigation;
2570
+ if (navigation && typeof navigation.entries === "function" && navigation.currentEntry) {
2571
+ if (!navigation.canGoBack) return false;
2572
+ const previous = navigation.entries()[navigation.currentEntry.index - 1];
2573
+ return Boolean(previous?.url) && jumpyNormalizeUrl(previous.url) === target;
2574
+ }
2575
+ return Boolean(jumpyLocations.previous) && jumpyNormalizeUrl(jumpyLocations.previous) === target && window.history.length > 1;
2576
+ }
2577
+ function JumpyAppShellBack({ back, variant, ariaLabels }) {
2578
+ const theme = useJumpyTheme();
2579
+ const Component = back.as || (back.href ? "a" : "button");
2580
+ const textStyle = textStyleValue(theme, variant === "inline" ? JumpyTextStyle.bodySmallEmphasized : JumpyTextStyle.bodyMedium);
2581
+ const accessibleName = ariaLabels.backTo ? ariaLabels.backTo(back.label) : `Back to ${back.label}`;
2582
+ function handleClick(event) {
2583
+ back.onClick?.(event);
2584
+ if (event.defaultPrevented || !back.href || back.history === false) return;
2585
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
2586
+ if (!jumpyPreviousEntryIs(back.href)) return;
2587
+ event.preventDefault();
2588
+ window.history.back();
2589
+ }
2590
+ return /* @__PURE__ */ jsxs2(
2591
+ Component,
2592
+ {
2593
+ href: back.href,
2594
+ type: Component === "button" ? "button" : void 0,
2595
+ "aria-label": accessibleName,
2596
+ "data-jumpy-slot": "app-shell-back",
2597
+ "data-variant": variant,
2598
+ onClick: handleClick,
2599
+ style: {
2600
+ boxSizing: "border-box",
2601
+ alignItems: "center",
2602
+ gap: spacingValue(theme, JumpySpacing.small4XS),
2603
+ maxWidth: "100%",
2604
+ minWidth: 0,
2605
+ minHeight: variant === "inline" ? void 0 : sizeValue(theme, JumpySize.largeXL),
2606
+ margin: variant === "inline" ? `0 0 ${spacingValue(theme, JumpySpacing.small3XS)}px ${-spacingValue(theme, JumpySpacing.small3XS)}px` : 0,
2607
+ padding: variant === "inline" ? 0 : `0 ${spacingValue(theme, JumpySpacing.smallXS)}px 0 0`,
2608
+ border: 0,
2609
+ borderRadius: radiusValue(theme, JumpyRadius.small),
2610
+ background: "transparent",
2611
+ color: "var(--jumpy-interactive-active-regular-default-text)",
2612
+ textDecoration: "none",
2613
+ cursor: "pointer",
2614
+ fontFamily: theme.fontFamily.familyBase,
2615
+ fontSize: textStyle.fontSize,
2616
+ lineHeight: textStyle.lineHeight,
2617
+ fontWeight: textStyle.fontWeight
2618
+ },
2619
+ children: [
2620
+ /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "back", size: variant === "inline" ? 18 : 24 }),
2621
+ /* @__PURE__ */ jsx2("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: back.label })
2622
+ ]
2623
+ }
2624
+ );
2625
+ }
2626
+ function JumpyAppShellActions({ actions, variant, ariaLabels }) {
2627
+ const theme = useJumpyTheme();
2628
+ if (!actions.length) return null;
2629
+ const gap = spacingValue(theme, JumpySpacing.smallXS);
2630
+ if (variant === "full") {
2631
+ return /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "full", style: { alignItems: "center", gap, flexWrap: "wrap", justifyContent: "flex-end" }, children: actions.map((action, index) => /* @__PURE__ */ jsx2(JumpButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant || "secondary", leadingIcon: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index)) });
2632
+ }
2633
+ const visible = actions.length > 2 ? actions.slice(0, 1) : actions;
2634
+ const overflow = actions.length > 2 ? actions.slice(1) : [];
2635
+ return /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "compact", style: { alignItems: "center", gap }, children: [
2636
+ visible.map((action, index) => /* @__PURE__ */ jsx2(JumpIconButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant === "primary" ? "primary" : "borderless", "aria-label": action.label, disabled: action.disabled, onClick: action.onClick, children: action.icon }, action.id ?? index)),
2637
+ overflow.length ? /* @__PURE__ */ jsx2(
2638
+ JumpMenu,
2639
+ {
2640
+ placement: "bottom-end",
2641
+ triggerProps: { role: "button", "aria-label": ariaLabels.moreActions || "More actions", "data-jumpy-slot": "app-shell-actions-more" },
2642
+ trigger: /* @__PURE__ */ jsx2(JumpIconButton, { as: "span", variant: "borderless", tabIndex: -1, "aria-hidden": "true", children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "more" }) }),
2643
+ children: overflow.map((action, index) => /* @__PURE__ */ jsx2(JumpMenuItem, { as: action.href ? "a" : void 0, href: action.href, leading: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index))
2644
+ }
2645
+ ) : null
2646
+ ] });
2647
+ }
2567
2648
  function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTabBar = false, breakpoint = 1024, sidebarWidth = 256, navigationOpen, defaultNavigationOpen = false, onNavigationOpenChange, ariaLabels = {}, children, className = "", ...props }) {
2568
2649
  const theme = useJumpyTheme();
2569
2650
  const shellId = useId();
@@ -2575,11 +2656,19 @@ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTa
2575
2656
  const showDrawer = isOpen || drawerMounted;
2576
2657
  const closing = drawerMounted && !isOpen;
2577
2658
  const wasShown = useRef2(showDrawer);
2578
- const { desktop: topBarOnDesktop = false, leading, largeTitle = false, ...topBarProps } = topBar || {};
2659
+ const { desktop: topBarDesktop = false, leading, largeTitle = false, actions = [], back, ...topBarProps } = topBar || {};
2579
2660
  const showLargeTitle = largeTitle && Boolean(topBarProps.title);
2661
+ const hasBack = Boolean(back?.label);
2662
+ const topBarOnDesktop = topBarDesktop || !showLargeTitle && (actions.length > 0 || hasBack);
2580
2663
  const largeTitleRef = useRef2(null);
2581
2664
  const [largeTitleCovered, setLargeTitleCovered] = useState2(false);
2582
- const resolvedLeading = leading ?? (navigation ? "menu" : void 0);
2665
+ const resolvedLeading = hasBack ? /* @__PURE__ */ jsx2(JumpyAppShellBack, { back, variant: "bar", ariaLabels }) : leading ?? (navigation ? "menu" : void 0);
2666
+ useEffect2(() => {
2667
+ const href = window.location.href;
2668
+ if (jumpyLocations.current === href) return;
2669
+ jumpyLocations.previous = jumpyLocations.current;
2670
+ jumpyLocations.current = href;
2671
+ });
2583
2672
  const shell = `[data-jumpy-app-shell="${shellId}"]`;
2584
2673
  const width = typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
2585
2674
  function setOpen(next) {
@@ -2636,9 +2725,15 @@ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTa
2636
2725
  const shellStyles = `
2637
2726
  ${shell} { grid-template-columns: minmax(0, 1fr); }
2638
2727
  ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2728
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: inline-flex; }
2729
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: none; }
2730
+ ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: none; }
2639
2731
  @media (min-width: ${breakpoint}px) {
2640
2732
  ${shell} { grid-template-columns: ${width} minmax(0, 1fr); }
2641
2733
  ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: flex; }
2734
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: none; }
2735
+ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: inline-flex; }
2736
+ ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: flex; width: fit-content; }
2642
2737
  ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='tab-bar'] { display: none; }
2643
2738
  ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'][data-mobile-only='true'] { display: none; }
2644
2739
  ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'] > [data-leading='menu'] { display: none; }
@@ -2653,9 +2748,14 @@ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2653
2748
  onMenu: () => setOpen(true),
2654
2749
  title: topBarProps.title ?? brand,
2655
2750
  subtitle: showLargeTitle ? void 0 : topBarProps.subtitle,
2656
- titleAlign: showLargeTitle ? "center" : topBarProps.titleAlign,
2751
+ titleAlign: showLargeTitle || hasBack ? "center" : topBarProps.titleAlign,
2657
2752
  titleHidden: showLargeTitle ? !largeTitleCovered : topBarProps.titleHidden,
2658
2753
  "data-large-title": showLargeTitle ? "true" : void 0,
2754
+ trailing: actions.length || topBarProps.trailing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
2755
+ /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "compact", ariaLabels }),
2756
+ showLargeTitle ? null : /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "full", ariaLabels }),
2757
+ topBarProps.trailing
2758
+ ] }) : void 0,
2659
2759
  ariaLabels: { menu: ariaLabels.openNavigation, back: ariaLabels.back, ...topBarProps.ariaLabels || {} }
2660
2760
  }
2661
2761
  );
@@ -2703,10 +2803,14 @@ ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2703
2803
  /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "app-shell-body", style: { display: "flex", flexDirection: "column", minWidth: 0, minHeight: "100dvh" }, children: [
2704
2804
  topBar !== null ? topBarNode : null,
2705
2805
  /* @__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,
2806
+ showLargeTitle ? /* @__PURE__ */ jsx2("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: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.large), flexWrap: "wrap" }, children: [
2807
+ /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
2808
+ hasBack ? /* @__PURE__ */ jsx2(JumpyAppShellBack, { back, variant: "inline", ariaLabels }) : null,
2809
+ /* @__PURE__ */ jsx2("h1", { ref: largeTitleRef, style: { margin: 0, ...textStyleValue(theme, JumpyTextStyle.displaySmallEmphasized), overflowWrap: "anywhere" }, children: topBarProps.title }),
2810
+ 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
2811
+ ] }),
2812
+ /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "full", ariaLabels })
2813
+ ] }) }) : null,
2710
2814
  children
2711
2815
  ] }),
2712
2816
  tabBar && !hideTabBar ? tabBar : null
package/dist/theme.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  textStyleValue,
29
29
  themeToCSSVariables,
30
30
  useJumpyTheme
31
- } from "./chunk-V3XYXQPA.js";
31
+ } from "./chunk-2I6OOQNV.js";
32
32
  export {
33
33
  JumpyAnimation,
34
34
  JumpyAnimationDelay,
package/index.d.ts CHANGED
@@ -71,7 +71,7 @@ export declare function JumpSkeletonLoader(props: HTMLAttributes<HTMLElement> &
71
71
  export declare const JumpSearchField: import('react').ForwardRefExoticComponent<Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> & { label?: string } & import('react').RefAttributes<HTMLInputElement>>
72
72
  export declare function JumpTooltip(props: HTMLAttributes<HTMLSpanElement> & { content: ReactNode; children: ReactNode }): ReactNode
73
73
  export declare function JumpPopover(props: HTMLAttributes<HTMLSpanElement> & { open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; trigger: ReactNode; children: ReactNode }): ReactNode
74
- export declare function JumpMenu(props: HTMLAttributes<HTMLDivElement> & { trigger: ReactNode; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; closeOnSelect?: boolean; header?: ReactNode; footer?: ReactNode; placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'; children: ReactNode }): ReactNode
74
+ export declare function JumpMenu(props: HTMLAttributes<HTMLDivElement> & { trigger: ReactNode; triggerProps?: HTMLAttributes<HTMLSpanElement> & Record<string, unknown>; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; closeOnSelect?: boolean; header?: ReactNode; footer?: ReactNode; placement?: 'top' | 'top-start' | 'top-end' | 'right' | 'right-start' | 'right-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'left-start' | 'left-end'; children: ReactNode }): ReactNode
75
75
  export declare function JumpMenuItem(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; children: ReactNode; selected?: boolean; disabled?: boolean; readOnly?: boolean; focusedView?: boolean; leading?: ReactNode; trailing?: ReactNode; metadata?: ReactNode; description?: ReactNode; slots?: JumpListItemSlots; nestedMenu?: ReactNode }): ReactNode
76
76
  export type JumpToggleButtonValue = string | number
77
77
  export declare function JumpToggleButton(props: Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> & { as?: ElementTag; selected?: boolean; value?: JumpToggleButtonValue; onChange?: (selected: boolean) => void; leading?: ReactNode | ((state: { selected: boolean }) => ReactNode); children?: ReactNode }): ReactNode
@@ -116,8 +116,10 @@ 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; largeTitle?: boolean }
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
119
+ export type JumpAppShellAction = { id?: string; label: string; icon: ReactNode; onClick?: (event: import('react').MouseEvent<HTMLElement>) => void; href?: string; variant?: 'primary' | 'secondary' | 'tertiary' | 'borderless'; disabled?: boolean }
120
+ export type JumpAppShellBack = { label: string; href?: string; as?: ElementType; onClick?: (event: import('react').MouseEvent<HTMLElement>) => void; history?: boolean }
121
+ export type JumpAppShellTopBar = Omit<JumpTopBarProps, 'onMenu' | 'menuOpen'> & { desktop?: boolean; largeTitle?: boolean; actions?: JumpAppShellAction[]; back?: JumpAppShellBack }
122
+ 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; backTo?: (label: string) => string; moreActions?: string }; children?: ReactNode }): ReactNode
121
123
  export declare function JumpSectionHeader(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; variant?: 'regular' | 'prominent'; description?: ReactNode; children: ReactNode }): ReactNode
122
124
  export declare function JumpSectionFooter(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; children: ReactNode }): ReactNode
123
125
  export declare function JumpTag(props: HTMLAttributes<HTMLSpanElement> & { size?: 'small' | 'medium'; status?: 'regular' | 'positive' | 'warning' | 'alarm'; disabled?: boolean; children: ReactNode }): ReactNode
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mario_hahn/jumpy-ui",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {