104 lines
3.0 KiB
TypeScript
104 lines
3.0 KiB
TypeScript
import { defineComponent, h, type PropType } from "vue";
|
||
import type {
|
||
RouteLocationNormalizedLoaded,
|
||
RouteLocationRaw,
|
||
} from "vue-router";
|
||
import { useNativeRouter } from "@native-vue-router/core";
|
||
import "./style.css";
|
||
|
||
export type NativePlatform = "ios" | "android" | "desktop";
|
||
|
||
export function detectNativePlatform(): NativePlatform {
|
||
if (typeof navigator === "undefined") return "desktop";
|
||
if (
|
||
/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent) &&
|
||
"ontouchend" in document
|
||
)
|
||
return "ios";
|
||
if (/Android/.test(navigator.userAgent)) return "android";
|
||
return "desktop";
|
||
}
|
||
|
||
export const NativeBackButton = defineComponent({
|
||
name: "NativeBackButton",
|
||
props: { label: { type: String, default: "Back" } },
|
||
setup(props) {
|
||
const native = useNativeRouter();
|
||
return () =>
|
||
h(
|
||
"button",
|
||
{
|
||
type: "button",
|
||
class: "nvr-native-back",
|
||
"aria-label": props.label,
|
||
onClick: () => void native.pop(),
|
||
},
|
||
[h("span", { "aria-hidden": "true" }, "‹"), h("span", props.label)],
|
||
);
|
||
},
|
||
});
|
||
|
||
export interface NativeTabItem {
|
||
label: string;
|
||
to: RouteLocationRaw;
|
||
icon?: string;
|
||
activeWhen?: (current: RouteLocationNormalizedLoaded) => boolean;
|
||
}
|
||
|
||
export const NativeTabBar = defineComponent({
|
||
name: "NativeTabBar",
|
||
props: {
|
||
items: { type: Array as PropType<NativeTabItem[]>, required: true },
|
||
},
|
||
setup(props) {
|
||
const native = useNativeRouter();
|
||
return () =>
|
||
h(
|
||
"nav",
|
||
{ class: "nvr-native-tabs", "aria-label": "Primary navigation" },
|
||
props.items.map((item) => {
|
||
const current = native.router.currentRoute.value;
|
||
const exact = current.path === native.router.resolve(item.to).path;
|
||
const active = exact || Boolean(item.activeWhen?.(current));
|
||
const href = native.router.resolve(item.to).href;
|
||
return h(
|
||
"a",
|
||
{
|
||
href,
|
||
class: ["nvr-native-tab", active && "nvr-native-tab--active"],
|
||
"aria-current": active ? "page" : undefined,
|
||
onClick: (event: MouseEvent) => {
|
||
if (
|
||
event.defaultPrevented ||
|
||
event.button !== 0 ||
|
||
event.metaKey ||
|
||
event.ctrlKey ||
|
||
event.shiftKey ||
|
||
event.altKey
|
||
)
|
||
return;
|
||
event.preventDefault();
|
||
if (exact) return;
|
||
void native.sibling(item.to, { replace: true });
|
||
},
|
||
},
|
||
[
|
||
h(
|
||
"span",
|
||
{ class: "nvr-native-tab__icon", "aria-hidden": "true" },
|
||
item.icon ?? "•",
|
||
),
|
||
h("span", item.label),
|
||
],
|
||
);
|
||
}),
|
||
);
|
||
},
|
||
});
|
||
|
||
export const nativeMotionTokens = {
|
||
ios: { edgeWidth: 28, commitThreshold: 0.36 },
|
||
android: { edgeWidth: 24, commitThreshold: 0.32 },
|
||
desktop: { edgeWidth: 18, commitThreshold: 0.4 },
|
||
} as const;
|