69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
import { defineComponent, h, type PropType } from 'vue'
|
||
import type { 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
|
||
}
|
||
|
||
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 active = native.router.currentRoute.value.path === native.router.resolve(item.to).path
|
||
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 (active) 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
|