Skip to content

Commit 7c1fa78

Browse files
feat: add layer components
1 parent eb5baa0 commit 7c1fa78

File tree

6 files changed

+218
-0
lines changed

6 files changed

+218
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script setup lang="ts"></script>
2+
3+
<template>
4+
<NuxtStory>
5+
<VTransitionExpand />
6+
</NuxtStory>
7+
</template>
8+
9+
<style lang="scss" module></style>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script setup lang="ts">
2+
// @see https://markus.oberlehner.net/blog/transition-to-height-auto-with-vue/
3+
4+
function callDoneCallback(element: Element, done: () => void) {
5+
const animation = element
6+
.getAnimations()
7+
.filter((animation) => (animation as CSSTransition).transitionProperty === 'height')[0]
8+
9+
if (animation) animation.finished.then(done)
10+
else done()
11+
}
12+
13+
function onEnter(element: Element, done: () => void) {
14+
;(element as HTMLElement).style.width = getComputedStyle(element).width
15+
;(element as HTMLElement).style.position = 'absolute'
16+
;(element as HTMLElement).style.visibility = 'hidden'
17+
;(element as HTMLElement).style.height = 'auto'
18+
19+
const height = getComputedStyle(element).height
20+
21+
;(element as HTMLElement).style.width = ''
22+
;(element as HTMLElement).style.position = ''
23+
;(element as HTMLElement).style.visibility = ''
24+
;(element as HTMLElement).style.height = '0'
25+
26+
// Force repaint to make sure the
27+
// animation is triggered correctly.
28+
// eslint-disable-next-line no-unused-expressions
29+
getComputedStyle(element).height
30+
31+
requestAnimationFrame(() => {
32+
;(element as HTMLElement).style.height = height
33+
34+
callDoneCallback(element, done)
35+
})
36+
}
37+
38+
function onAfterEnter(element: Element) {
39+
;(element as HTMLElement).style.height = 'auto'
40+
}
41+
42+
function onLeave(element: Element, done: () => void) {
43+
;(element as HTMLElement).style.height = getComputedStyle(element).height
44+
45+
// Force repaint to make sure the
46+
// animation is triggered correctly.
47+
// eslint-disable-next-line no-unused-expressions
48+
getComputedStyle(element).height
49+
50+
requestAnimationFrame(() => {
51+
;(element as HTMLElement).style.height = '0'
52+
53+
callDoneCallback(element, done)
54+
})
55+
}
56+
</script>
57+
58+
<template>
59+
<Transition name="expand" @enter="onEnter" @after-enter="onAfterEnter" @leave="onLeave">
60+
<!-- eslint-disable-next-line vue/require-toggle-inside-transition -->
61+
<slot />
62+
</Transition>
63+
</template>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
const blocks = [
3+
{
4+
'@id': 'walker-id',
5+
'@type': 'RoadizWalker',
6+
item: {
7+
'@id': 'item-id',
8+
'@type': 'ContentBlock',
9+
},
10+
children: [],
11+
},
12+
]
13+
</script>
14+
15+
<template>
16+
<NuxtStory>
17+
<VRoadizBlockFactory :blocks="blocks" />
18+
</NuxtStory>
19+
</template>
20+
21+
<style lang="scss" module></style>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { FunctionalComponent, VNodeChild } from 'vue'
2+
import { h, resolveDynamicComponent, resolveComponent } from 'vue'
3+
import type { RoadizWalker } from '@roadiz/types'
4+
5+
export interface RoadizBlockFactoryProps {
6+
prefix?: string
7+
blocks: RoadizWalker[]
8+
[key: string]: unknown
9+
}
10+
11+
const isComponent = (component: string): boolean => {
12+
return typeof resolveDynamicComponent(component) !== 'string'
13+
}
14+
15+
const RoadizBlockFactory: FunctionalComponent<RoadizBlockFactoryProps> = ({ blocks, prefix }, context): VNodeChild => {
16+
const blocksWithComponent = blocks.filter((block) => {
17+
const componentName = prefix
18+
? prefix + block.item['@type'].replace(/NS([a-zA-Z]+)/g, '$1')
19+
: block.item['@type'].replace(/NS([a-zA-Z]+)/g, '$1')
20+
21+
return isComponent(componentName)
22+
})
23+
return blocksWithComponent.map((block, index, blocks) => {
24+
const componentName = prefix
25+
? prefix + block.item['@type'].replace(/NS([a-zA-Z]+)/g, '$1')
26+
: block.item['@type'].replace(/NS([a-zA-Z]+)/g, '$1')
27+
28+
return h(resolveComponent(componentName), {
29+
walker: block,
30+
index,
31+
numBlocks: blocks.length,
32+
...context.attrs,
33+
})
34+
})
35+
}
36+
37+
export default RoadizBlockFactory
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
useRoadizPreview({
3+
isActive: true,
4+
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjIsInVzZXJuYW1lIjoiam9obi5kb2VAcmV6by16ZXJvLmNvbSJ9.cmDGzWvhOG2pczkEb9Bk6IgsUp7-fz-l_QCT6O2xwis',
5+
})
6+
</script>
7+
8+
<template>
9+
<NuxtStory>
10+
<VRoadizPreview />
11+
</NuxtStory>
12+
</template>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<script setup lang="ts">
2+
const { token, reset: resetPreview } = useRoadizPreview()
3+
4+
const jwt = computed(() => {
5+
let decodedBase64
6+
7+
if (!token.value) {
8+
return null
9+
}
10+
11+
const base64Url = token.value.split('.')[1]
12+
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
13+
14+
if (typeof window !== 'undefined' && typeof window.atob !== 'undefined') {
15+
decodedBase64 = window.atob(base64)
16+
} else {
17+
decodedBase64 = Buffer.from(base64, 'base64').toString()
18+
}
19+
20+
const jsonPayload = decodeURIComponent(
21+
decodedBase64
22+
.split('')
23+
.map((c) => {
24+
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
25+
})
26+
.join(''),
27+
)
28+
29+
return JSON.parse(jsonPayload)
30+
})
31+
32+
const remainingTime = computed(() => {
33+
return new Date((jwt.value?.exp || 0) * 1000)
34+
})
35+
36+
const router = useRouter()
37+
const route = useRoute()
38+
function stopPreview() {
39+
resetPreview()
40+
41+
router.push({ path: route.path, query: { ...route.query, _preview: undefined, token: undefined } })
42+
}
43+
</script>
44+
45+
<template>
46+
<div v-if="jwt" :class="$style.root">
47+
<div :class="$style.user">Previewing as: {{ jwt.username }}</div>
48+
<div>Expire at: {{ remainingTime }}</div>
49+
<button :class="$style.button" @click.prevent="stopPreview">Stop previewing</button>
50+
</div>
51+
</template>
52+
53+
<style lang="scss" module>
54+
.root {
55+
position: fixed;
56+
z-index: 9999;
57+
right: 0;
58+
bottom: 0;
59+
display: block;
60+
max-width: 48ch;
61+
padding: 1em 1.5em 0.75em;
62+
border-radius: 3px 3px 0 0;
63+
background: #ffa600;
64+
font-size: 10px;
65+
line-height: 16px;
66+
opacity: 0.4;
67+
}
68+
69+
.user {
70+
font-weight: bold;
71+
}
72+
73+
.button {
74+
font-weight: bold;
75+
}
76+
</style>

0 commit comments

Comments
 (0)