first commit
This commit is contained in:
18
packages/preset-native/package.json
Normal file
18
packages/preset-native/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@native-vue-router/preset-native",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"files": ["dist"],
|
||||
"sideEffects": ["./dist/style.css"],
|
||||
"scripts": { "build": "vite build --config vite.config.ts && vue-tsc -p tsconfig.json --emitDeclarationOnly" },
|
||||
"exports": {
|
||||
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
|
||||
"./style.css": "./dist/style.css"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@native-vue-router/core": "^0.1.0",
|
||||
"vue": "^3.5.0",
|
||||
"vue-router": "^5.0.0"
|
||||
}
|
||||
}
|
||||
68
packages/preset-native/src/index.ts
Normal file
68
packages/preset-native/src/index.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
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
|
||||
56
packages/preset-native/src/style.css
Normal file
56
packages/preset-native/src/style.css
Normal file
@@ -0,0 +1,56 @@
|
||||
:root {
|
||||
--nvr-accent: #7c5cff;
|
||||
--nvr-safe-top: env(safe-area-inset-top, 0px);
|
||||
--nvr-safe-right: env(safe-area-inset-right, 0px);
|
||||
--nvr-safe-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--nvr-safe-left: env(safe-area-inset-left, 0px);
|
||||
}
|
||||
|
||||
.nvr-native-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0 10px 0 4px;
|
||||
border: 0;
|
||||
color: var(--nvr-accent);
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nvr-native-back > span:first-child {
|
||||
font-size: 34px;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.nvr-native-tabs {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr;
|
||||
padding: 8px max(12px, var(--nvr-safe-right)) calc(8px + var(--nvr-safe-bottom)) max(12px, var(--nvr-safe-left));
|
||||
border-top: 1px solid color-mix(in srgb, currentColor 12%, transparent);
|
||||
background: color-mix(in srgb, var(--nvr-view-background, #fff) 88%, transparent);
|
||||
backdrop-filter: blur(24px) saturate(1.6);
|
||||
}
|
||||
|
||||
.nvr-native-tab {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
min-height: 44px;
|
||||
color: color-mix(in srgb, currentColor 54%, transparent);
|
||||
text-decoration: none;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nvr-native-tab--active { color: var(--nvr-accent); }
|
||||
.nvr-native-tab__icon { font-size: 19px; line-height: 1.2; }
|
||||
|
||||
@media (pointer: fine) and (min-width: 900px) {
|
||||
.nvr-native-tabs { border-radius: 18px 18px 0 0; }
|
||||
}
|
||||
15
packages/preset-native/tsconfig.json
Normal file
15
packages/preset-native/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"tsBuildInfoFile": "../../node_modules/.tmp/preset-native.tsbuildinfo",
|
||||
"baseUrl": ".",
|
||||
"paths": { "@native-vue-router/core": ["../core/dist/index.d.ts"] }
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
10
packages/preset-native/vite.config.ts
Normal file
10
packages/preset-native/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: 'dist', emptyOutDir: true,
|
||||
lib: { entry: resolve(__dirname, 'src/index.ts'), formats: ['es'], fileName: 'index', cssFileName: 'style' },
|
||||
rollupOptions: { external: ['vue', 'vue-router', '@native-vue-router/core'] },
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user