Skip to content
This repository was archived by the owner on Sep 20, 2024. It is now read-only.

Commit b3e8de8

Browse files
committed
feat: create collapse and breadcrumb component
1 parent 812bc24 commit b3e8de8

File tree

6 files changed

+251
-3
lines changed

6 files changed

+251
-3
lines changed

components.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
* This is a generated file. Do not edit it's contents.
88
*
9-
* This file was generated on 2021-07-27T09:42:08.546Z
9+
* This file was generated on 2021-07-29T05:01:14.847Z
1010
*/
1111

1212
import { ChakraProps } from '@chakra-ui/vue-system'
@@ -43,6 +43,9 @@ declare module 'vue' {
4343
CAlertDescription: typeof import('@chakra-ui/vue-next')['CAlertDescription']
4444
CAlertIcon: typeof import('@chakra-ui/vue-next')['CAlertIcon']
4545
CBreadcrumb: typeof import('@chakra-ui/vue-next')['CBreadcrumb']
46+
CBreadcrumbSeparator: typeof import('@chakra-ui/vue-next')['CBreadcrumbSeparator']
47+
CBreadcrumbItem: typeof import('@chakra-ui/vue-next')['CBreadcrumbItem']
48+
CBreadcrumbLink: typeof import('@chakra-ui/vue-next')['CBreadcrumbLink']
4649
CButton: typeof import('@chakra-ui/vue-next')['CButton']
4750
CButtonGroup: typeof import('@chakra-ui/vue-next')['CButtonGroup']
4851
CIconButton: typeof import('@chakra-ui/vue-next')['CIconButton']

packages/c-motion/examples/animate-presence.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
<c-text> Active variant: {{ activeVariant }} </c-text>
2020
</c-stack>
2121
<!-- <c-portal> -->
22-
<c-animate-presence :type="activeVariant">
22+
<c-animate-presence
23+
:type="activeVariant"
24+
@before-enter="handleOnBeforeEnter"
25+
@after-enter="handleOnAfterEnter"
26+
>
2327
<chakra.div
2428
:bg="useColorModeValue('teal.100', 'teal.800').value"
2529
:color="useColorModeValue('teal.800', 'teal.100').value"
@@ -51,6 +55,13 @@ const variants = computed(
5155
() => Object.keys(TransitionVariants) as CMotionVariant[]
5256
)
5357
58+
const handleOnBeforeEnter = (...args: any[]) => {
59+
console.log('handleOnBeforeEnter', ...args)
60+
}
61+
const handleOnAfterEnter = (...args: any[]) => {
62+
console.log('handleOnAfterEnter', ...args)
63+
}
64+
5465
const setVariant = (variant: CMotionVariant) => {
5566
activeVariant.value = variant
5667
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<c-button @click="toggle">Toggle Collapse</c-button>
3+
<c-collapse :is-open="open">
4+
<chakra.div
5+
:__css="{
6+
background: 'red',
7+
padding: 30,
8+
color: 'white',
9+
fontWeight: 'bold',
10+
}"
11+
>
12+
<p>
13+
Lorem Ipsum is simply dummy text of the printing and typesetting
14+
industry. Lorem Ipsum has been the industry's standard dummy text ever
15+
since the 1500s, when an unknown printer took a galley of type and
16+
scrambled it to make a type specimen book. It has survived not only five
17+
centuries, but also the leap into electronic typesetting, remaining
18+
essentially unchanged. It was popularised in the 1960s with the release
19+
of Letraset sheets containing Lorem Ipsum passages, and more recently
20+
with desktop publishing software like Aldus PageMaker including versions
21+
of Lorem Ipsum.
22+
</p>
23+
</chakra.div>
24+
</c-collapse>
25+
<c-text>
26+
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
27+
Lorem Ipsum has been the industry's standard dummy text ever since the
28+
1500s, when an unknown printer took a galley of type and scrambled it to
29+
make a type specimen book.
30+
</c-text>
31+
</template>
32+
33+
<script lang="ts" setup>
34+
import { useToggle } from '@vueuse/core'
35+
import { CCollapse } from '../src'
36+
37+
const [open, toggle] = useToggle(false)
38+
</script>

packages/c-motion/src/animate-presence.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export const CAnimatePresence = defineComponent({
7676

7777
const onLeave = (el: Element, done?: VoidFunction) => {
7878
motionInstance.value.leave(done)
79+
emit('leave', el, done)
80+
}
81+
82+
const onBeforeLeave = (el: Element, done?: VoidFunction) => {
83+
emit('beforeLeave', el, done)
7984
}
8085

8186
return () => {
@@ -94,7 +99,7 @@ export const CAnimatePresence = defineComponent({
9499
css={false}
95100
mode="out-in"
96101
onLeave={onLeave}
97-
onBeforeLeave={onLeave}
102+
onBeforeLeave={onBeforeLeave}
98103
{...attrs}
99104
>
100105
{() => [children]}

packages/c-motion/src/c-collapse.tsx

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { chakra } from '@chakra-ui/vue-system'
2+
import { useRef } from '@chakra-ui/vue-utils'
3+
import { useId } from '@chakra-ui/vue-composables'
4+
import { MotionDirective, MotionVariants, useMotions } from '@vueuse/motion'
5+
import { h, defineComponent, PropType, computed, ref, watch, withDirectives, onMounted } from 'vue'
6+
import { TransitionEasings, TransitionVariants } from './motion-utils'
7+
import { warn } from '@chakra-ui/utils'
8+
9+
export interface CollapseOptions {
10+
/**
11+
* If `true`, the opacity of the content will be animated
12+
* @default true
13+
*/
14+
animateOpacity: boolean
15+
/**
16+
* The height you want the content in its collapsed state.
17+
* @default 0
18+
*/
19+
startingHeight: number | string
20+
/**
21+
* The height you want the content in its expanded state.
22+
* @default "auto"
23+
*/
24+
endingHeight: number | string
25+
/**
26+
* The current disclosure state of the CColapse component
27+
* @default true
28+
*/
29+
isOpen: boolean,
30+
/**
31+
* Unmounts it's children after the leave transition
32+
* @default true
33+
*/
34+
unmountOnExit: boolean
35+
}
36+
37+
38+
/**
39+
* CCollapse
40+
*
41+
* It renders a `span` when it matches the current link. Otherwise,
42+
* it renders an anchor tag.
43+
*/
44+
45+
export const CCollapse = defineComponent({
46+
name: 'CCollapse',
47+
props: {
48+
isOpen: {
49+
type: Boolean as PropType<CollapseOptions['isOpen']>,
50+
default: true
51+
},
52+
animateOpacity: {
53+
type: Boolean as PropType<CollapseOptions['animateOpacity']>,
54+
default: true
55+
},
56+
startingHeight: {
57+
type: Number as PropType<CollapseOptions['startingHeight']>,
58+
default: 0
59+
},
60+
endingHeight: {
61+
type: [String, Number] as PropType<CollapseOptions['endingHeight']>,
62+
default: 'auto'
63+
},
64+
unmountOnExit: {
65+
type: Boolean as PropType<CollapseOptions['unmountOnExit']>,
66+
default: true
67+
},
68+
},
69+
setup(props, { slots, attrs, emit }) {
70+
const [targetRef, targetNode] = useRef()
71+
const transitionId = useId('collapse-transition')
72+
const preTransitionHeight = ref(0)
73+
const collapsedHeight = computed(() => {
74+
return preTransitionHeight.value || props.endingHeight
75+
})
76+
77+
const variant = computed<MotionVariants>(() => ({
78+
leave: {
79+
overflow: 'hidden',
80+
height: props.startingHeight,
81+
...props.animateOpacity && ({ opacity: 0 }),
82+
transition: {
83+
duration: 200,
84+
ease: TransitionEasings.easeInOut
85+
}
86+
},
87+
enter: {
88+
overflow: 'hidden',
89+
height: collapsedHeight.value,
90+
...props.animateOpacity && ({ opacity: 1 }),
91+
transition: {
92+
duration: 300,
93+
ease: TransitionEasings.easeInOut
94+
}
95+
},
96+
initial: {
97+
overflow: 'hidden',
98+
height: props.startingHeight,
99+
...props.animateOpacity && ({ opacity: 0 }),
100+
transition: {
101+
duration: 200,
102+
ease: TransitionEasings.easeInOut
103+
}
104+
},
105+
}))
106+
107+
warn({
108+
condition: Boolean(props.startingHeight > 0 && props.unmountOnExit),
109+
message: `"startingHeight" and "unmountOnExit" props are mutually exclusive. You can't use them together`,
110+
})
111+
112+
/** Handles exit transition */
113+
const leave = (done: VoidFunction) => {
114+
const el = targetNode.value!
115+
const { height } = getComputedStyle(el)
116+
117+
requestAnimationFrame(() => {
118+
const motions = useMotions()
119+
const instance = motions[transitionId.value]
120+
instance?.leave(done)
121+
})
122+
}
123+
124+
/** Handles enter transition */
125+
const enter = (done: VoidFunction) => {
126+
const el = targetNode.value!
127+
if (el) {
128+
el.style.visibility = 'hidden'
129+
// @ts-ignore
130+
el.style.height = props.endingHeight
131+
const { height } = getComputedStyle(el)
132+
// @ts-ignore
133+
el.style.height = props.startingHeight
134+
135+
el.style.visibility = 'visible'
136+
const motions = useMotions()
137+
const instance = motions[transitionId.value]
138+
instance?.apply({
139+
...variant.value.enter,
140+
height: parseFloat(height),
141+
})?.then(done)
142+
}
143+
}
144+
145+
watch(() => props.isOpen!, (newVal) => {
146+
if (!newVal && targetNode.value!) {
147+
leave(() => null)
148+
} else {
149+
enter(() => null)
150+
}
151+
})
152+
153+
/**
154+
* We first invoke
155+
* the transition to make sure it's registered
156+
* inside the `useMotion` plugin.
157+
*
158+
* Visually this does nothing, but it applies
159+
* the transition and stores it so we can access
160+
* it using the `useMotions` hook.
161+
*/
162+
onMounted(() => {
163+
if (props.isOpen) {
164+
enter(() => null)
165+
} else {
166+
leave(() => null)
167+
}
168+
})
169+
170+
return () => {
171+
const children = slots
172+
?.default?.()
173+
.filter((vnode) => String(vnode.type) !== 'Symbol(Comment)')
174+
175+
return (
176+
withDirectives(
177+
<chakra.div ref={targetRef}>
178+
{() => children}
179+
</chakra.div>,
180+
[
181+
[
182+
MotionDirective(variant.value),
183+
transitionId.value
184+
]
185+
]
186+
)
187+
)
188+
}
189+
},
190+
})

packages/c-motion/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './c-motion'
22
export * from './animate-presence'
3+
export * from './c-collapse'
34
export * from './motion-utils'

0 commit comments

Comments
 (0)