@mario_hahn/jumpy-ui 0.12.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 +6 -1
- package/dist/{chunk-3PHOJLCH.js → chunk-2I6OOQNV.js} +19 -0
- package/dist/index.js +83 -8
- package/dist/theme.js +1 -1
- package/index.d.ts +3 -2
- package/package.json +1 -1
- package/tokens/android/tokens/src/main/kotlin/com/mariohahn/jumpy/tokens/defaultDark/Tokens.kt +1197 -1
- package/tokens/android/tokens/src/main/kotlin/com/mariohahn/jumpy/tokens/defaultLight/Tokens.kt +1197 -1
- package/tokens/android/tokens/src/main/kotlin/com/mariohahn/jumpy/tokens/jumpyDark/Tokens.kt +1197 -1
- package/tokens/android/tokens/src/main/kotlin/com/mariohahn/jumpy/tokens/jumpyLight/Tokens.kt +1197 -1
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.
|
|
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
|
|
@@ -333,6 +333,11 @@ import { JumpAppShell, JumpSideMenu, JumpSideMenuItem, JumpTabBar, JumpTabBarIte
|
|
|
333
333
|
stays visible and the rest go into a "More actions" menu. Desktop shows labelled
|
|
334
334
|
buttons beside the large title, otherwise in the top bar. Prefer it over `trailing`
|
|
335
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.
|
|
336
341
|
- `JumpTopBar` and `JumpTabBar` also work standalone. Both are sticky and include
|
|
337
342
|
safe-area insets. `JumpTabBarItem` accepts `as` (e.g. Next `Link`), `href`, `icon`,
|
|
338
343
|
numeric or custom `badge` and `selected` (`aria-current="page"`).
|
|
@@ -11904,6 +11904,25 @@ var providerStyles = `
|
|
|
11904
11904
|
backdrop-filter: none;
|
|
11905
11905
|
}
|
|
11906
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
|
+
|
|
11907
11926
|
[data-jumpy-theme='true'] [data-jumpy-component='tab-bar-item'] {
|
|
11908
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);
|
|
11909
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-
|
|
31
|
+
} from "./chunk-2I6OOQNV.js";
|
|
32
32
|
import {
|
|
33
33
|
cn
|
|
34
34
|
} from "./chunk-3DBKYKP2.js";
|
|
@@ -2408,14 +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
2415
|
back: "M15 18l-6-6 6-6",
|
|
2416
2416
|
more: "M5 12h.01M12 12h.01M19 12h.01"
|
|
2417
2417
|
};
|
|
2418
|
-
return /* @__PURE__ */ jsx2("svg", { width:
|
|
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] }) });
|
|
2419
2419
|
}
|
|
2420
2420
|
function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, titleAlign = "start", titleHidden = false, ariaLabels = {}, className = "", ...props }) {
|
|
2421
2421
|
const theme = useJumpyTheme();
|
|
@@ -2455,7 +2455,7 @@ function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle
|
|
|
2455
2455
|
...props.style || {}
|
|
2456
2456
|
},
|
|
2457
2457
|
children: [
|
|
2458
|
-
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,
|
|
2459
2459
|
/* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "top-bar-title", "aria-hidden": titleHidden || void 0, style: titleAlign === "center" ? {
|
|
2460
2460
|
display: "flex",
|
|
2461
2461
|
flexDirection: "column",
|
|
@@ -2558,6 +2558,71 @@ function JumpTabBarItem({ as, icon, badge, selected = false, children, className
|
|
|
2558
2558
|
}
|
|
2559
2559
|
);
|
|
2560
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
|
+
}
|
|
2561
2626
|
function JumpyAppShellActions({ actions, variant, ariaLabels }) {
|
|
2562
2627
|
const theme = useJumpyTheme();
|
|
2563
2628
|
if (!actions.length) return null;
|
|
@@ -2591,12 +2656,19 @@ function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTa
|
|
|
2591
2656
|
const showDrawer = isOpen || drawerMounted;
|
|
2592
2657
|
const closing = drawerMounted && !isOpen;
|
|
2593
2658
|
const wasShown = useRef2(showDrawer);
|
|
2594
|
-
const { desktop: topBarDesktop = false, leading, largeTitle = false, actions = [], ...topBarProps } = topBar || {};
|
|
2659
|
+
const { desktop: topBarDesktop = false, leading, largeTitle = false, actions = [], back, ...topBarProps } = topBar || {};
|
|
2595
2660
|
const showLargeTitle = largeTitle && Boolean(topBarProps.title);
|
|
2596
|
-
const
|
|
2661
|
+
const hasBack = Boolean(back?.label);
|
|
2662
|
+
const topBarOnDesktop = topBarDesktop || !showLargeTitle && (actions.length > 0 || hasBack);
|
|
2597
2663
|
const largeTitleRef = useRef2(null);
|
|
2598
2664
|
const [largeTitleCovered, setLargeTitleCovered] = useState2(false);
|
|
2599
|
-
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
|
+
});
|
|
2600
2672
|
const shell = `[data-jumpy-app-shell="${shellId}"]`;
|
|
2601
2673
|
const width = typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
|
|
2602
2674
|
function setOpen(next) {
|
|
@@ -2655,11 +2727,13 @@ ${shell} { grid-template-columns: minmax(0, 1fr); }
|
|
|
2655
2727
|
${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
|
|
2656
2728
|
${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: inline-flex; }
|
|
2657
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; }
|
|
2658
2731
|
@media (min-width: ${breakpoint}px) {
|
|
2659
2732
|
${shell} { grid-template-columns: ${width} minmax(0, 1fr); }
|
|
2660
2733
|
${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: flex; }
|
|
2661
2734
|
${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: none; }
|
|
2662
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; }
|
|
2663
2737
|
${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='tab-bar'] { display: none; }
|
|
2664
2738
|
${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'][data-mobile-only='true'] { display: none; }
|
|
2665
2739
|
${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'] > [data-leading='menu'] { display: none; }
|
|
@@ -2674,7 +2748,7 @@ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: n
|
|
|
2674
2748
|
onMenu: () => setOpen(true),
|
|
2675
2749
|
title: topBarProps.title ?? brand,
|
|
2676
2750
|
subtitle: showLargeTitle ? void 0 : topBarProps.subtitle,
|
|
2677
|
-
titleAlign: showLargeTitle ? "center" : topBarProps.titleAlign,
|
|
2751
|
+
titleAlign: showLargeTitle || hasBack ? "center" : topBarProps.titleAlign,
|
|
2678
2752
|
titleHidden: showLargeTitle ? !largeTitleCovered : topBarProps.titleHidden,
|
|
2679
2753
|
"data-large-title": showLargeTitle ? "true" : void 0,
|
|
2680
2754
|
trailing: actions.length || topBarProps.trailing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
@@ -2731,6 +2805,7 @@ ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: n
|
|
|
2731
2805
|
/* @__PURE__ */ jsxs2("main", { "data-jumpy-slot": "app-shell-content", style: { flex: 1, minWidth: 0 }, children: [
|
|
2732
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: [
|
|
2733
2807
|
/* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
|
|
2808
|
+
hasBack ? /* @__PURE__ */ jsx2(JumpyAppShellBack, { back, variant: "inline", ariaLabels }) : null,
|
|
2734
2809
|
/* @__PURE__ */ jsx2("h1", { ref: largeTitleRef, style: { margin: 0, ...textStyleValue(theme, JumpyTextStyle.displaySmallEmphasized), overflowWrap: "anywhere" }, children: topBarProps.title }),
|
|
2735
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
|
|
2736
2811
|
] }),
|
package/dist/theme.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -117,8 +117,9 @@ export declare function JumpTabBar(props: HTMLAttributes<HTMLElement> & { childr
|
|
|
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
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
|
|
121
|
-
export
|
|
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
|
|
122
123
|
export declare function JumpSectionHeader(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; variant?: 'regular' | 'prominent'; description?: ReactNode; children: ReactNode }): ReactNode
|
|
123
124
|
export declare function JumpSectionFooter(props: HTMLAttributes<HTMLElement> & { as?: ElementTag; children: ReactNode }): ReactNode
|
|
124
125
|
export declare function JumpTag(props: HTMLAttributes<HTMLSpanElement> & { size?: 'small' | 'medium'; status?: 'regular' | 'positive' | 'warning' | 'alarm'; disabled?: boolean; children: ReactNode }): ReactNode
|