Created
June 19, 2022 20:58
-
-
Save AvocadoVenom/63e27a566e1a7f6842ac66d29f33b3f2 to your computer and use it in GitHub Desktop.
Expandable block content with smooth read less/read more feature
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <template> | |
| <div class="block-content"> | |
| <transition | |
| @before-enter="onBeforeEnter" | |
| @enter="onEnter" | |
| @after-enter="onAfterEnter" | |
| @before-leave="onBeforeLeave" | |
| @leave="onLeave" | |
| mode="out-in" | |
| > | |
| <p v-if="!isExpanded" class="block-content__preview" v-html="content" /> | |
| <p | |
| v-else | |
| :class="{ | |
| 'block-content__paragraph': true, | |
| 'block-content__paragraph--is-expanded': isExpanded, | |
| }" | |
| v-html="content" | |
| /> | |
| </transition> | |
| <button | |
| type="button" | |
| class="block-content__button" | |
| aria-label="Toggle button" | |
| @click="isExpanded = !isExpanded" | |
| > | |
| {{ toggleCtaLabel }} | |
| </button> | |
| </div> | |
| </template> | |
| <script lang="ts"> | |
| import { computed, defineComponent, ref, toRefs } from "vue"; | |
| export default defineComponent({ | |
| name: "ExpandableBlockContent", | |
| props: { | |
| content: { type: String }, | |
| visibleLines: { type: Number, default: 4 }, | |
| }, | |
| setup(props) { | |
| const { visibleLines } = toRefs(props); | |
| // Collapsed state | |
| // Assuming that default line-height is 24px | |
| const LINE_HEIGHT = 24; | |
| const maxHeightCollapsed = computed(() => LINE_HEIGHT * visibleLines.value); | |
| // Animation hooks | |
| const onBeforeEnter = (el: Element) => { | |
| const htmlEl = el as HTMLElement; | |
| htmlEl.style.height = maxHeightCollapsed.value + "px"; | |
| }; | |
| const onEnter = (el: Element) => { | |
| const htmlEl = el as HTMLElement; | |
| htmlEl.style.height = el.scrollHeight + "px"; | |
| }; | |
| const onAfterEnter = (el: Element) => { | |
| const htmlEl = el as HTMLElement; | |
| htmlEl.style.overflow = "hidden"; | |
| }; | |
| const onBeforeLeave = (el: Element) => { | |
| const htmlEl = el as HTMLElement; | |
| htmlEl.style.height = `${el.scrollHeight}px`; | |
| htmlEl.style.overflow = "hidden"; | |
| }; | |
| const onLeave = (el: Element) => { | |
| const htmlEl = el as HTMLElement; | |
| htmlEl.style.height = maxHeightCollapsed.value + "px"; | |
| htmlEl.style.overflow = "hidden"; | |
| }; | |
| // Expanded state | |
| const isExpanded = ref(false); | |
| const toggleCtaLabel = computed(() => | |
| isExpanded.value ? "Read less" : "Read more" | |
| ); | |
| return { | |
| isExpanded, | |
| toggleCtaLabel, | |
| onBeforeEnter, | |
| onEnter, | |
| onAfterEnter, | |
| onBeforeLeave, | |
| onLeave, | |
| }; | |
| }, | |
| }); | |
| </script> | |
| <style lang="scss" scoped> | |
| .block-content { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 8px; | |
| padding: 24px 20px; | |
| background-color: skyblue; | |
| border-radius: 8px; | |
| } | |
| .block-content__preview { | |
| /* default line-height is 24px */ | |
| max-height: calc(24px * v-bind(visibleLines)); | |
| overflow: hidden; | |
| color: white; | |
| } | |
| .block-content__paragraph { | |
| color: white; | |
| overflow: hidden; | |
| transition: all 250ms ease-out; | |
| } | |
| .block-content__paragraph.block-content__paragraph--is-expanded { | |
| overflow: initial; | |
| } | |
| .block-content__button { | |
| color: white; | |
| text-decoration: underline; | |
| align-self: flex-end; | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment