kmcom-nuxt-layers 1.6.39 → 1.6.43
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/layers/core/app/composables/useErrorLog.ts +6 -11
- package/layers/core/app/composables/useLoading.ts +15 -46
- package/layers/core/app/composables/useScrollGuard.ts +115 -129
- package/layers/forms/app/components/Form/Field.vue +2 -2
- package/layers/forms/app/composables/useFormSchema.ts +1 -3
- package/layers/forms/app/config/fields.ts +12 -6
- package/layers/forms/app/types/fields.ts +2 -20
- package/layers/forms/nuxt.config.ts +0 -10
- package/layers/motion/app/components/Motion/CountUp.vue +39 -0
- package/layers/motion/app/components/Motion/Cursor.vue +101 -0
- package/layers/motion/app/components/Motion/Magnetic.vue +22 -0
- package/layers/motion/app/components/Motion/Marquee.vue +130 -130
- package/layers/motion/app/components/Motion/MarqueeText.vue +147 -0
- package/layers/motion/app/components/Motion/Tilt.vue +22 -0
- package/layers/motion/app/components/Motion/VelocityEffect.vue +33 -71
- package/layers/motion/app/composables/useCountUp.ts +61 -0
- package/layers/motion/app/composables/useCursorFollower.ts +25 -0
- package/layers/motion/app/composables/useMagneticElement.ts +56 -0
- package/layers/motion/app/composables/useMarqueeCopies.ts +50 -0
- package/layers/motion/app/composables/useMarqueeVelocity.ts +44 -0
- package/layers/motion/app/composables/useSmoothScroll.ts +11 -0
- package/layers/motion/app/composables/useTiltEffect.ts +58 -0
- package/layers/motion/app/plugins/locomotive-scroll.client.ts +1 -1
- package/layers/motion/app/types/app-config.d.ts +11 -2
- package/layers/motion/tsconfig.json +1 -0
- package/layers/routing/app/middleware/02.governance.global.ts +7 -18
- package/layers/routing/app/types/routing.ts +10 -0
- package/layers/routing/app/utils/resolveRoute.ts +31 -0
- package/layers/shader/package.json +2 -1
- package/layers/theme/app/composables/useAccentColor.ts +1 -12
- package/layers/theme/app/composables/useThemeContrast.ts +0 -11
- package/layers/theme/app/composables/useThemeMotion.ts +0 -11
- package/layers/theme/app/composables/useThemeTransparency.ts +0 -14
- package/layers/theme/app/plugins/theme.client.ts +20 -3
- package/layers/theme/app/types/app-config.d.ts +2 -2
- package/layers/ui/app/components/Base/Modal.vue +0 -1
- package/layers/ui/app/composables/color.ts +1 -32
- package/layers/ui/app/utils/createModal.ts +11 -2
- package/package.json +28 -26
- package/layers/content/app/.DS_Store +0 -0
- package/layers/content/app/pages/blog/[slug].vue +0 -19
- package/layers/content/app/pages/blog/index.vue +0 -22
- package/layers/core/app/.DS_Store +0 -0
- package/layers/core/app/assets/.DS_Store +0 -0
- package/layers/forms/app/.DS_Store +0 -0
- package/layers/layout/app/.DS_Store +0 -0
- package/layers/motion/app/.DS_Store +0 -0
- package/layers/shader/app/.DS_Store +0 -0
- package/layers/theme/app/.DS_Store +0 -0
- package/layers/ui/app/.DS_Store +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useCursorFollower(opts: { smoothing?: MaybeRef<number> } = {}) {
|
|
4
|
+
const { gsap } = useGsap()
|
|
5
|
+
const { x: mouseX, y: mouseY } = useMouse({ type: 'client' })
|
|
6
|
+
|
|
7
|
+
const x = ref(0)
|
|
8
|
+
const y = ref(0)
|
|
9
|
+
|
|
10
|
+
function tick() {
|
|
11
|
+
const factor = unref(opts.smoothing) ?? 0.15
|
|
12
|
+
x.value += (mouseX.value - x.value) * factor
|
|
13
|
+
y.value += (mouseY.value - y.value) * factor
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
onMounted(() => {
|
|
17
|
+
x.value = mouseX.value
|
|
18
|
+
y.value = mouseY.value
|
|
19
|
+
gsap.ticker.add(tick)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
onUnmounted(() => gsap.ticker.remove(tick))
|
|
23
|
+
|
|
24
|
+
return { x, y }
|
|
25
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function useMagneticElement(
|
|
2
|
+
elementRef: Ref<HTMLElement | null>,
|
|
3
|
+
opts: {
|
|
4
|
+
strength?: number
|
|
5
|
+
radius?: number
|
|
6
|
+
damping?: number
|
|
7
|
+
stiffness?: number
|
|
8
|
+
} = {},
|
|
9
|
+
) {
|
|
10
|
+
const { gsap } = useGsap()
|
|
11
|
+
const { elementX, elementY, elementWidth, elementHeight } = useMouseInElement(elementRef)
|
|
12
|
+
|
|
13
|
+
const currentX = ref(0)
|
|
14
|
+
const currentY = ref(0)
|
|
15
|
+
const isActive = ref(false)
|
|
16
|
+
|
|
17
|
+
function tick() {
|
|
18
|
+
const strength = opts.strength ?? 0.3
|
|
19
|
+
const radius = opts.radius ?? 100
|
|
20
|
+
const damping = (opts.damping ?? 30) / 1000
|
|
21
|
+
const stiffness = (opts.stiffness ?? 300) / 1000
|
|
22
|
+
|
|
23
|
+
const dx = elementX.value - elementWidth.value / 2
|
|
24
|
+
const dy = elementY.value - elementHeight.value / 2
|
|
25
|
+
const dist = Math.sqrt(dx * dx + dy * dy)
|
|
26
|
+
|
|
27
|
+
let targetX = 0
|
|
28
|
+
let targetY = 0
|
|
29
|
+
|
|
30
|
+
if (dist < radius) {
|
|
31
|
+
isActive.value = true
|
|
32
|
+
targetX = dx * strength
|
|
33
|
+
targetY = dy * strength
|
|
34
|
+
} else {
|
|
35
|
+
isActive.value = false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
currentX.value += (targetX - currentX.value) * stiffness
|
|
39
|
+
currentX.value *= 1 - damping
|
|
40
|
+
currentY.value += (targetY - currentY.value) * stiffness
|
|
41
|
+
currentY.value *= 1 - damping
|
|
42
|
+
|
|
43
|
+
if (elementRef.value) {
|
|
44
|
+
gsap.set(elementRef.value, { x: currentX.value, y: currentY.value })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
onMounted(() => gsap.ticker.add(tick))
|
|
49
|
+
|
|
50
|
+
onUnmounted(() => {
|
|
51
|
+
gsap.ticker.remove(tick)
|
|
52
|
+
if (elementRef.value) gsap.set(elementRef.value, { x: 0, y: 0 })
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return { isActive }
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useMarqueeCopies(
|
|
4
|
+
containerRefs: Ref<HTMLElement[]>,
|
|
5
|
+
copyRefs: Ref<HTMLSpanElement[]>,
|
|
6
|
+
rowCount: MaybeRef<number>,
|
|
7
|
+
) {
|
|
8
|
+
const copyWidths = ref<number[]>([])
|
|
9
|
+
const calculatedCopies = ref<number[]>([])
|
|
10
|
+
|
|
11
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
12
|
+
|
|
13
|
+
function calculate() {
|
|
14
|
+
const n = unref(rowCount)
|
|
15
|
+
for (let i = 0; i < n; i++) {
|
|
16
|
+
const copy = copyRefs.value[i]
|
|
17
|
+
const container = containerRefs.value[i]
|
|
18
|
+
if (!copy || !container) continue
|
|
19
|
+
const singleWidth = copy.offsetWidth
|
|
20
|
+
if (singleWidth === 0) continue
|
|
21
|
+
const effectiveWidth = Math.max(container.offsetWidth, window.innerWidth)
|
|
22
|
+
const minCopies = Math.ceil((effectiveWidth * 2.5) / singleWidth)
|
|
23
|
+
copyWidths.value[i] = singleWidth
|
|
24
|
+
calculatedCopies.value[i] = Math.max(minCopies, 8)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function debouncedCalculate() {
|
|
29
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
30
|
+
debounceTimer = setTimeout(() => {
|
|
31
|
+
calculate()
|
|
32
|
+
debounceTimer = null
|
|
33
|
+
}, 150)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
nextTick(() => {
|
|
38
|
+
calculate()
|
|
39
|
+
setTimeout(calculate, 100)
|
|
40
|
+
})
|
|
41
|
+
window.addEventListener('resize', debouncedCalculate, { passive: true })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
46
|
+
window.removeEventListener('resize', debouncedCalculate)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return { copyWidths, calculatedCopies }
|
|
50
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { MaybeRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
interface VelocityMapping {
|
|
4
|
+
input: [number, number]
|
|
5
|
+
output: [number, number]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface UseMarqueeVelocityOptions {
|
|
9
|
+
damping?: MaybeRef<number>
|
|
10
|
+
stiffness?: MaybeRef<number>
|
|
11
|
+
velocityMapping?: MaybeRef<VelocityMapping>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useMarqueeVelocity(opts: UseMarqueeVelocityOptions = {}) {
|
|
15
|
+
const { velocity: rawVelocity } = useSmoothScroll()
|
|
16
|
+
const { gsap } = useGsap()
|
|
17
|
+
|
|
18
|
+
const smoothVelocity = ref(0)
|
|
19
|
+
const velocityFactor = ref(0)
|
|
20
|
+
|
|
21
|
+
function tick() {
|
|
22
|
+
const damping = (unref(opts.damping) ?? 50) / 1000
|
|
23
|
+
const stiffness = (unref(opts.stiffness) ?? 400) / 1000
|
|
24
|
+
const mapping: VelocityMapping = unref(opts.velocityMapping) ?? {
|
|
25
|
+
input: [0, 1000],
|
|
26
|
+
output: [0, 5],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
smoothVelocity.value += (rawVelocity.value - smoothVelocity.value) * stiffness
|
|
30
|
+
smoothVelocity.value *= 1 - damping
|
|
31
|
+
|
|
32
|
+
const inputRange = mapping.input[1] - mapping.input[0]
|
|
33
|
+
const outputRange = mapping.output[1] - mapping.output[0]
|
|
34
|
+
let t = (Math.abs(smoothVelocity.value) - mapping.input[0]) / inputRange
|
|
35
|
+
t = Math.max(0, Math.min(1, t))
|
|
36
|
+
velocityFactor.value = mapping.output[0] + t * outputRange
|
|
37
|
+
if (smoothVelocity.value < 0) velocityFactor.value *= -1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
onMounted(() => gsap.ticker.add(tick))
|
|
41
|
+
onUnmounted(() => gsap.ticker.remove(tick))
|
|
42
|
+
|
|
43
|
+
return { velocityFactor }
|
|
44
|
+
}
|
|
@@ -23,12 +23,19 @@ interface ScrollToOptions {
|
|
|
23
23
|
*/
|
|
24
24
|
export function useSmoothScroll() {
|
|
25
25
|
const nuxtApp = useNuxtApp()
|
|
26
|
+
const appConfig = useAppConfig()
|
|
26
27
|
|
|
27
28
|
// Get the Locomotive Scroll instance from the reactive ref provided by the plugin
|
|
28
29
|
const locomotiveScroll = computed<LocomotiveScroll | undefined>(
|
|
29
30
|
() => (nuxtApp.$locomotiveScroll as Ref<LocomotiveScroll | null>)?.value ?? undefined
|
|
30
31
|
)
|
|
31
32
|
|
|
33
|
+
// true if smooth scroll is configured to run (globally or per-route)
|
|
34
|
+
const isEnabled = computed(() => (appConfig.motion?.smoothScroll ?? true) !== false)
|
|
35
|
+
|
|
36
|
+
// true once the LocomotiveScroll instance is actually initialised on this route
|
|
37
|
+
const isReady = computed(() => locomotiveScroll.value != null)
|
|
38
|
+
|
|
32
39
|
// Reactive scroll state from the plugin
|
|
33
40
|
const scrollState = computed(
|
|
34
41
|
() =>
|
|
@@ -110,6 +117,10 @@ export function useSmoothScroll() {
|
|
|
110
117
|
// Locomotive Scroll instance
|
|
111
118
|
locomotiveScroll,
|
|
112
119
|
|
|
120
|
+
// Scroll availability
|
|
121
|
+
isEnabled,
|
|
122
|
+
isReady,
|
|
123
|
+
|
|
113
124
|
// Reactive scroll state
|
|
114
125
|
scrollY,
|
|
115
126
|
velocity,
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function useTiltEffect(
|
|
2
|
+
elementRef: Ref<HTMLElement | null>,
|
|
3
|
+
opts: {
|
|
4
|
+
maxTilt?: number
|
|
5
|
+
perspective?: number
|
|
6
|
+
damping?: number
|
|
7
|
+
stiffness?: number
|
|
8
|
+
} = {},
|
|
9
|
+
) {
|
|
10
|
+
const { gsap } = useGsap()
|
|
11
|
+
const { elementX, elementY, elementWidth, elementHeight, isOutside } =
|
|
12
|
+
useMouseInElement(elementRef)
|
|
13
|
+
|
|
14
|
+
const currentRotateX = ref(0)
|
|
15
|
+
const currentRotateY = ref(0)
|
|
16
|
+
|
|
17
|
+
function tick() {
|
|
18
|
+
const maxTilt = opts.maxTilt ?? 15
|
|
19
|
+
const perspective = opts.perspective ?? 800
|
|
20
|
+
const damping = (opts.damping ?? 40) / 1000
|
|
21
|
+
const stiffness = (opts.stiffness ?? 250) / 1000
|
|
22
|
+
|
|
23
|
+
const width = elementWidth.value || 1
|
|
24
|
+
const height = elementHeight.value || 1
|
|
25
|
+
|
|
26
|
+
let targetRotateX = 0
|
|
27
|
+
let targetRotateY = 0
|
|
28
|
+
|
|
29
|
+
if (!isOutside.value) {
|
|
30
|
+
const nx = (elementX.value / width) * 2 - 1
|
|
31
|
+
const ny = (elementY.value / height) * 2 - 1
|
|
32
|
+
targetRotateY = nx * maxTilt
|
|
33
|
+
targetRotateX = -ny * maxTilt
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
currentRotateX.value += (targetRotateX - currentRotateX.value) * stiffness
|
|
37
|
+
currentRotateX.value *= 1 - damping
|
|
38
|
+
currentRotateY.value += (targetRotateY - currentRotateY.value) * stiffness
|
|
39
|
+
currentRotateY.value *= 1 - damping
|
|
40
|
+
|
|
41
|
+
if (elementRef.value) {
|
|
42
|
+
gsap.set(elementRef.value, {
|
|
43
|
+
rotateX: currentRotateX.value,
|
|
44
|
+
rotateY: currentRotateY.value,
|
|
45
|
+
transformPerspective: perspective,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onMounted(() => gsap.ticker.add(tick))
|
|
51
|
+
|
|
52
|
+
onUnmounted(() => {
|
|
53
|
+
gsap.ticker.remove(tick)
|
|
54
|
+
if (elementRef.value) gsap.set(elementRef.value, { rotateX: 0, rotateY: 0 })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return { rotateX: currentRotateX, rotateY: currentRotateY }
|
|
58
|
+
}
|
|
@@ -45,7 +45,7 @@ export default defineNuxtPlugin(() => {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const router = useRouter()
|
|
48
|
-
const appConfig = useAppConfig()
|
|
48
|
+
const appConfig = useAppConfig()
|
|
49
49
|
const smoothScroll: boolean | string[] = appConfig.motion?.smoothScroll ?? true
|
|
50
50
|
|
|
51
51
|
if (smoothScroll === true) {
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
declare module '@nuxt/schema' {
|
|
2
2
|
interface AppConfigInput {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
motion?: {
|
|
4
|
+
smoothScroll?: boolean | string[]
|
|
5
|
+
gsapScrollTrigger?: boolean
|
|
6
|
+
lenis?: {
|
|
7
|
+
duration?: number
|
|
8
|
+
orientation?: 'vertical' | 'horizontal'
|
|
9
|
+
gestureOrientation?: 'vertical' | 'horizontal'
|
|
10
|
+
smoothWheel?: boolean
|
|
11
|
+
smoothTouch?: boolean
|
|
12
|
+
touchMultiplier?: number
|
|
13
|
+
}
|
|
5
14
|
}
|
|
6
15
|
}
|
|
7
16
|
}
|
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import { useFeatures } from '../composables/useFeatures'
|
|
2
|
+
import { resolveRoute } from '../utils/resolveRoute'
|
|
2
3
|
|
|
3
4
|
export default defineNuxtRouteMiddleware((to) => {
|
|
4
|
-
const { config
|
|
5
|
+
const { config } = useRoutingConfig()
|
|
5
6
|
const { resolve } = useFeatures()
|
|
6
|
-
const meta = to.meta
|
|
7
7
|
|
|
8
|
-
if (config.debug) console.log('[routing] governance check', to.path, meta)
|
|
8
|
+
if (config.debug) console.log('[routing] governance check', to.path, to.meta)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
if (isStrictMode() && !meta.feature) {
|
|
12
|
-
throw createError({ statusCode: 404 })
|
|
13
|
-
}
|
|
10
|
+
const resolution = resolveRoute(to.meta, config, resolve)
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
if (isLayerDefaultDeny() && meta.__fromLayer && !meta.feature) {
|
|
17
|
-
throw createError({ statusCode: 404 })
|
|
18
|
-
}
|
|
12
|
+
if (config.debug) console.log('[routing] resolution', resolution)
|
|
19
13
|
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
const variant = resolve(meta.feature)
|
|
23
|
-
if (config.debug) console.log(`[routing] feature "${meta.feature}" resolved to "${variant}"`)
|
|
24
|
-
|
|
25
|
-
if (variant === 'disabled') throw createError({ statusCode: 404 })
|
|
26
|
-
if (variant === 'beta' || variant === 'coming-soon') return navigateTo('/coming-soon')
|
|
14
|
+
if (resolution.outcome === 'deny') throw createError({ statusCode: 404 })
|
|
15
|
+
if (resolution.outcome === 'redirect') return navigateTo(resolution.to)
|
|
27
16
|
})
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
export type FeatureValue = 'enabled' | 'disabled' | 'beta' | 'coming-soon'
|
|
2
2
|
export type RoutingPreset = 'simple' | 'marketing' | 'product' | 'enterprise'
|
|
3
3
|
|
|
4
|
+
export type RouteResolution =
|
|
5
|
+
| { outcome: 'allow' }
|
|
6
|
+
| { outcome: 'deny' }
|
|
7
|
+
| { outcome: 'redirect'; to: string }
|
|
8
|
+
|
|
4
9
|
export interface RoutingLayerConfig {
|
|
5
10
|
preset: RoutingPreset
|
|
6
11
|
strictDefaultDeny: boolean
|
|
7
12
|
layerDefaultDeny: boolean
|
|
13
|
+
betaRedirect: string
|
|
8
14
|
runtimeFlags: boolean
|
|
9
15
|
debug: boolean
|
|
10
16
|
maintenance: { enabled: boolean; allowRoutes: string[] }
|
|
@@ -16,6 +22,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
|
|
|
16
22
|
simple: {
|
|
17
23
|
strictDefaultDeny: false,
|
|
18
24
|
layerDefaultDeny: false,
|
|
25
|
+
betaRedirect: '/coming-soon',
|
|
19
26
|
runtimeFlags: false,
|
|
20
27
|
debug: false,
|
|
21
28
|
maintenance: { enabled: false, allowRoutes: ['/maintenance'] },
|
|
@@ -24,6 +31,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
|
|
|
24
31
|
marketing: {
|
|
25
32
|
strictDefaultDeny: false,
|
|
26
33
|
layerDefaultDeny: true,
|
|
34
|
+
betaRedirect: '/coming-soon',
|
|
27
35
|
runtimeFlags: false,
|
|
28
36
|
debug: false,
|
|
29
37
|
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
@@ -32,6 +40,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
|
|
|
32
40
|
product: {
|
|
33
41
|
strictDefaultDeny: false,
|
|
34
42
|
layerDefaultDeny: true,
|
|
43
|
+
betaRedirect: '/coming-soon',
|
|
35
44
|
runtimeFlags: true,
|
|
36
45
|
debug: false,
|
|
37
46
|
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
@@ -40,6 +49,7 @@ export const ROUTING_PRESETS: Record<RoutingPreset, Omit<RoutingLayerConfig, 'pr
|
|
|
40
49
|
enterprise: {
|
|
41
50
|
strictDefaultDeny: true,
|
|
42
51
|
layerDefaultDeny: true,
|
|
52
|
+
betaRedirect: '/coming-soon',
|
|
43
53
|
runtimeFlags: true,
|
|
44
54
|
debug: false,
|
|
45
55
|
maintenance: { enabled: true, allowRoutes: ['/maintenance'] },
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FeatureValue, RouteResolution, RoutingLayerConfig } from '../types/routing'
|
|
2
|
+
|
|
3
|
+
interface RouteMeta {
|
|
4
|
+
feature?: string
|
|
5
|
+
__fromLayer?: boolean
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function resolveRoute(
|
|
9
|
+
meta: RouteMeta,
|
|
10
|
+
config: RoutingLayerConfig,
|
|
11
|
+
resolveFeature: (name: string) => FeatureValue,
|
|
12
|
+
): RouteResolution {
|
|
13
|
+
if (config.strictDefaultDeny && !meta.feature) {
|
|
14
|
+
return { outcome: 'deny' }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (config.layerDefaultDeny && meta.__fromLayer && !meta.feature) {
|
|
18
|
+
return { outcome: 'deny' }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!meta.feature) return { outcome: 'allow' }
|
|
22
|
+
|
|
23
|
+
const variant = resolveFeature(meta.feature)
|
|
24
|
+
|
|
25
|
+
if (variant === 'disabled') return { outcome: 'deny' }
|
|
26
|
+
if (variant === 'beta' || variant === 'coming-soon') {
|
|
27
|
+
return { outcome: 'redirect', to: config.betaRedirect }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { outcome: 'allow' }
|
|
31
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { createSharedComposable, useLocalStorage } from '@vueuse/core'
|
|
3
2
|
import type { AccentColor } from '#layers/theme/app/types/theme'
|
|
4
3
|
|
|
5
4
|
export const useAccentColor = createSharedComposable(() => {
|
|
6
5
|
const appConfig = useAppConfig()
|
|
7
6
|
|
|
8
|
-
const defaultAccent = (
|
|
7
|
+
const defaultAccent = (appConfig.themeLayer?.defaultAccent ?? 'blue') as AccentColor
|
|
9
8
|
const accent = useLocalStorage<AccentColor>('theme-colour', defaultAccent)
|
|
10
9
|
|
|
11
10
|
const pageAccent = ref<AccentColor | null>(null)
|
|
@@ -19,16 +18,6 @@ export const useAccentColor = createSharedComposable(() => {
|
|
|
19
18
|
pageAccent.value = color
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
if (import.meta.client) {
|
|
23
|
-
watch(
|
|
24
|
-
activeAccent,
|
|
25
|
-
(color) => {
|
|
26
|
-
document.documentElement.setAttribute('data-theme-colour', color)
|
|
27
|
-
},
|
|
28
|
-
{ immediate: true }
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
21
|
return {
|
|
33
22
|
accent: readonly(accent),
|
|
34
23
|
setAccent,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { createSharedComposable, useLocalStorage, usePreferredContrast } from '@vueuse/core'
|
|
3
2
|
import type { PreferenceOverride } from '#layers/theme/app/types/theme'
|
|
4
3
|
|
|
@@ -16,16 +15,6 @@ export const useThemeContrast = createSharedComposable(() => {
|
|
|
16
15
|
contrastOverride.value = value
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
if (import.meta.client) {
|
|
20
|
-
watch(
|
|
21
|
-
effectiveHighContrast,
|
|
22
|
-
(high) => {
|
|
23
|
-
document.documentElement.setAttribute('data-theme-contrast', high ? 'high' : 'standard')
|
|
24
|
-
},
|
|
25
|
-
{ immediate: true }
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
18
|
return {
|
|
30
19
|
contrastOverride: readonly(contrastOverride),
|
|
31
20
|
setContrastOverride,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { createSharedComposable, useLocalStorage, usePreferredReducedMotion } from '@vueuse/core'
|
|
3
2
|
import type { PreferenceOverride } from '#layers/theme/app/types/theme'
|
|
4
3
|
|
|
@@ -16,16 +15,6 @@ export const useThemeMotion = createSharedComposable(() => {
|
|
|
16
15
|
motionOverride.value = value
|
|
17
16
|
}
|
|
18
17
|
|
|
19
|
-
if (import.meta.client) {
|
|
20
|
-
watch(
|
|
21
|
-
effectiveReducedMotion,
|
|
22
|
-
(reduced) => {
|
|
23
|
-
document.documentElement.setAttribute('data-theme-motion', reduced ? 'reduced' : 'full')
|
|
24
|
-
},
|
|
25
|
-
{ immediate: true }
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
18
|
return {
|
|
30
19
|
motionOverride: readonly(motionOverride),
|
|
31
20
|
setMotionOverride,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import {
|
|
3
2
|
createSharedComposable,
|
|
4
3
|
useLocalStorage,
|
|
@@ -20,19 +19,6 @@ export const useThemeTransparency = createSharedComposable(() => {
|
|
|
20
19
|
transparencyOverride.value = value
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
if (import.meta.client) {
|
|
24
|
-
watch(
|
|
25
|
-
effectiveReducedTransparency,
|
|
26
|
-
(reduced) => {
|
|
27
|
-
document.documentElement.setAttribute(
|
|
28
|
-
'data-theme-transparency',
|
|
29
|
-
reduced ? 'reduced' : 'full'
|
|
30
|
-
)
|
|
31
|
-
},
|
|
32
|
-
{ immediate: true }
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
22
|
return {
|
|
37
23
|
transparencyOverride: readonly(transparencyOverride),
|
|
38
24
|
setTransparencyOverride,
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
export default defineNuxtPlugin(() => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
const { activeAccent } = useAccentColor()
|
|
3
|
+
const { effectiveHighContrast } = useThemeContrast()
|
|
4
|
+
const { effectiveReducedMotion } = useThemeMotion()
|
|
5
|
+
const { effectiveReducedTransparency } = useThemeTransparency()
|
|
6
|
+
|
|
7
|
+
watch(activeAccent, (color) => {
|
|
8
|
+
document.documentElement.setAttribute('data-theme-colour', color)
|
|
9
|
+
}, { immediate: true })
|
|
10
|
+
|
|
11
|
+
watch(effectiveHighContrast, (high) => {
|
|
12
|
+
document.documentElement.setAttribute('data-theme-contrast', high ? 'high' : 'standard')
|
|
13
|
+
}, { immediate: true })
|
|
14
|
+
|
|
15
|
+
watch(effectiveReducedMotion, (reduced) => {
|
|
16
|
+
document.documentElement.setAttribute('data-theme-motion', reduced ? 'reduced' : 'full')
|
|
17
|
+
}, { immediate: true })
|
|
18
|
+
|
|
19
|
+
watch(effectiveReducedTransparency, (reduced) => {
|
|
20
|
+
document.documentElement.setAttribute('data-theme-transparency', reduced ? 'reduced' : 'full')
|
|
21
|
+
}, { immediate: true })
|
|
5
22
|
})
|
|
@@ -1,36 +1,5 @@
|
|
|
1
1
|
import type { ColorUsage, UiColors } from '../types/colors'
|
|
2
2
|
|
|
3
|
-
const colorMap: Record<UiColors, string> = {
|
|
4
|
-
// Semantic
|
|
5
|
-
dimmed: 'dimmed',
|
|
6
|
-
muted: 'muted',
|
|
7
|
-
toned: 'toned',
|
|
8
|
-
highlighted: 'highlighted',
|
|
9
|
-
inverted: 'inverted',
|
|
10
|
-
default: 'default',
|
|
11
|
-
|
|
12
|
-
// Status
|
|
13
|
-
info: 'info',
|
|
14
|
-
success: 'success',
|
|
15
|
-
warning: 'warning',
|
|
16
|
-
error: 'error',
|
|
17
|
-
|
|
18
|
-
// Nuxt UI core
|
|
19
|
-
primary: 'primary',
|
|
20
|
-
neutral: 'neutral',
|
|
21
|
-
|
|
22
|
-
// Custom
|
|
23
|
-
secondary: 'secondary',
|
|
24
|
-
accent: 'accent',
|
|
25
|
-
|
|
26
|
-
// Base
|
|
27
|
-
black: 'black',
|
|
28
|
-
white: 'white',
|
|
29
|
-
}
|
|
30
|
-
|
|
31
3
|
export function useColor(color?: UiColors, type: ColorUsage = 'text') {
|
|
32
|
-
return computed(() => {
|
|
33
|
-
const colorName = color ? colorMap[color] : 'default'
|
|
34
|
-
return `${type}-${colorName}`
|
|
35
|
-
})
|
|
4
|
+
return computed(() => `${type}-${color ?? 'default'}`)
|
|
36
5
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Component } from 'vue'
|
|
2
2
|
|
|
3
|
+
export interface ModalController<P extends Record<string, unknown>> {
|
|
4
|
+
open: (props?: Partial<P>) => void
|
|
5
|
+
close: () => void
|
|
6
|
+
patch: (props: Partial<P>) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* Factory that turns any overlay-compatible component into a shared modal composable.
|
|
5
11
|
*
|
|
@@ -19,10 +25,13 @@ import type { Component } from 'vue'
|
|
|
19
25
|
* Extend BaseModal.vue as a starting point.
|
|
20
26
|
*/
|
|
21
27
|
export function createModal<P extends Record<string, unknown>>(component: Component) {
|
|
22
|
-
return createSharedComposable(() => {
|
|
23
|
-
if (import.meta.server)
|
|
28
|
+
return createSharedComposable((): ModalController<P> => {
|
|
29
|
+
if (import.meta.server) {
|
|
30
|
+
return { open: () => {}, close: () => {}, patch: () => {} }
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
const overlay = useOverlay()
|
|
34
|
+
// useOverlay is a Nuxt UI auto-import; not resolvable in standalone layer typecheck
|
|
26
35
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
27
36
|
const modal = overlay.create(component as any)
|
|
28
37
|
return {
|