Skip to content

Commit

Permalink
fix FAQ page
Browse files Browse the repository at this point in the history
- fix #1283 use min width for the Help link in the header
- add Ellipse collapsible panel to FaqPage
- fix #1277 jump to the right place, added custom margin top of 100
- fix #1282 (entries cut off)
  • Loading branch information
danieleguido authored Oct 7, 2024
1 parent 21b6f51 commit 50ef1f2
Show file tree
Hide file tree
Showing 13 changed files with 614 additions and 218 deletions.
124 changes: 124 additions & 0 deletions src/components/CollapsiblePanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<div ref="rootRef" class="CollapsiblePanel" :class="className" :style="rootStyles">
<div
ref="headerRef"
class="CollapsiblePanel__header d-flex justify-content-between align-items-center"
@click="togglePanelState"
>
<slot name="header">
<div class="p-3">
<h4 class="m-0">{{ title }}</h4>
<p v-if="subtitle.length" class="mb-0">subtitle</p>
</div>
</slot>
<button
class="btn btn-sm btn-icon mx-2"
:class="{
active: modelValue
}"
>
<Icon name="chevron" />
</button>
</div>
<div class="CollapsiblePanel__body position-absolute">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
/**
* Model value is used to control the visibility of the panel.
*/
import { ref, computed, watch } from 'vue'
import Icon from './base/Icon.vue'
const emit = defineEmits(['update:modelValue', 'heightChanged'])
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
required: true
},
title: {
type: String,
required: true
},
subtitle: {
type: String,
default: ''
},
className: {
type: String,
default: ''
}
})
const rootRef = ref<HTMLElement | null>(null)
const headerRef = ref<HTMLElement | null>(null)
const togglePanelState = () => {
emit('update:modelValue', !props.modelValue)
}
const collapsedHeight = computed(() => {
const header = headerRef.value
return header ? `${header.offsetHeight}px` : 'auto'
})
const expandedHeight = computed(() => {
const root = rootRef.value
const parsedOffset = parseInt(collapsedHeight.value)
const offset = isNaN(parsedOffset) ? 0 : parsedOffset
return root ? `${root.scrollHeight + offset}px` : 'auto'
})
const rootStyles = computed(() => {
return {
height: props.modelValue ? expandedHeight.value : collapsedHeight.value
}
})
watch(
() => rootStyles.value.height,
height => {
emit('heightChanged', height)
},
{ immediate: true }
)
</script>

<style>
.CollapsiblePanel {
position: relative;
overflow: hidden;
will-change: height;
transition: height 0.6s;
transition-timing-function: var(--impresso-transition-ease);
}
.CollapsiblePanel__header {
cursor: pointer;
user-select: none;
box-sizing: border-box;
}
.CollapsiblePanel h3 {
font-family: var(--bs-font-sans-serif);
}
.CollapsiblePanel__body {
min-height: 50px;
width: 100%;
}
.CollapsiblePanel button {
transition: transform 0.3s;
transition-timing-function: var(--impresso-transition-ease);
}
.CollapsiblePanel button.active {
transform: rotate(180deg);
}
</style>
79 changes: 79 additions & 0 deletions src/components/CollapsibleParagraph.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>
<CollapsiblePanel
class="CollapsibleParagraph"
v-model="state"
:title="paragraph.title"
@heightChanged="e => emit('heightChanged', e)"
>
<template v-slot:header>
<div class="d-flex align-items-center">
<h4 class="mb-0">{{ paragraph.title }}</h4>
</div>
</template>
<div v-html="paragraph.description"></div>
<ol v-if="paragraph.paragraphs.length">
<li
v-for="(subtask, idx) in paragraph.paragraphs"
:key="subtask.id"
class="pt-2 mb-2 mx-2 d-flex align-items-center"
>
<div class="p-1 flex-grow-1">
{{ subtask.title }}
</div>
</li>
</ol>
</CollapsiblePanel>
</template>

<script setup lang="ts">
import { computed, PropType } from 'vue'
import CollapsiblePanel from './CollapsiblePanel.vue'
import type { ICollapsibleParagraph } from '@/models/CollapsibleParagraph'
const emit = defineEmits(['update:modelValue', 'heightChanged'])
const props = defineProps({
modelValue: {
type: Boolean,
default: false,
required: true
},
paragraph: {
type: Object as PropType<ICollapsibleParagraph>,
required: true
},
taskNum: {
type: Number,
required: true
}
})
const state = computed({
set(value: boolean) {
emit('update:modelValue', value)
},
get() {
return props.modelValue
}
})
</script>
<style>
.CollapsibleParagraph h4 {
font-family: var(--bs-font-sans-serif);
font-size: inherit;
}
.CollapsibleParagraph__num {
font-size: var(--impresso-font-size-smallcaps);
font-family: var(--bs-font-sans-serif);
text-transform: uppercase;
letter-spacing: var(--impresso-letter-spacing-smallcaps);
font-weight: var(--impresso-wght-smallcaps);
font-variation-settings: 'wght' var(--impresso-wght-smallcaps);
}
.CollapsibleParagraph.CollapsiblePanel {
box-shadow: none;
border-radius: 0;
}
</style>
119 changes: 119 additions & 0 deletions src/components/CollapsibleSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<template>
<CollapsiblePanel
class="CollapsibleSection border border-dark"
v-model="isOpen"
:subtitle="subtitle"
:title="title"
:style="{ height: isOpen ? `${offsetHeight}px` : `${headerHeight}px` }"
>
<template v-slot:header>
<div class="p-3">
<h3 class="mb-0">{{ title }}</h3>
<span class="text-muted">{{ subtitle }}</span>
</div>
</template>
<ol>
<li v-for="(paragraph, idx) in paragraphs" :key="paragraph.id">
<CollapsibleParagraph
:modelValue="currentOpenTaskId === paragraph.id"
:paragraph="paragraph"
:taskNum="idx + 1"
@update:modelValue="(value: boolean) => (currentOpenTaskId = value ? task.id : null)"
@heightChanged="(e: string) => updateHeight(paragraph.id, e)"
>
</CollapsibleParagraph>
</li>
</ol>
</CollapsiblePanel>
</template>
<script setup lang="ts">
import { computed, PropType, ref, watch } from 'vue'
import CollapsiblePanel from './CollapsiblePanel.vue'
import CollapsibleParagraph from './CollapsibleParagraph.vue'
import type { ICollapsibleParagraph } from '../models/CollapsibleParagraph'
// TODO: Get from the element?
const headerHeight = 50
const offsetHeights = ref<Record<string, number>>({})
const offsetHeight = computed(
() => headerHeight + Object.values(offsetHeights.value).reduce((a, b) => a + b, 0)
)
const props = defineProps({
title: {
type: String,
required: true
},
isCollapsed: {
type: Boolean,
default: true
},
subtitle: {
type: String,
default: ''
},
paragraphs: {
type: Array as PropType<ICollapsibleParagraph[]>,
required: true
},
initialOpenedParagraphId: {
type: String,
default: null
}
})
const isOpen = ref(!props.isCollapsed)
const currentOpenTaskId = ref<string | null>(props.initialOpenedParagraphId)
watch(
() => props.isCollapsed,
isCollapsed => {
isOpen.value = !isCollapsed
}
)
const updateHeight = (id: string, height: string) => {
const heightAsNumber = parseInt(height.replace('px', ''))
offsetHeights.value[id] = isNaN(heightAsNumber) ? 0 : heightAsNumber
}
// const onTutorialTaskToggled = (idx: number, payload: CollapsiblePanelData) => {
// console.debug('[CollapsibleSection] idx', idx, '@onTutorialTaskToggled', payload)
// offsetHeights[idx] = payload.value ? 50 : payload.expandedHeight
// }
</script>

<style>
.CollapsibleSection.CollapsiblePanel {
box-shadow: var(--bs-box-shadow-sm);
border-radius: var(--border-radius-lg);
}
.CollapsibleSection ol {
list-style-type: none;
padding-inline-start: 0;
padding-inline-end: 0;
margin-inline-start: var(--spacing-2);
margin-inline-end: var(--spacing-2);
margin-bottom: var(--spacing-1);
}
.CollapsibleSection li {
border-top: 1px solid var(--clr-grey-200);
}
.CollapsibleSection li:first-of-type {
border-top-width: 0px;
}
.CollapsibleSection h3 {
font-size: var(--impresso-font-size-smallcaps);
font-family: var(--bs-font-sans-serif);
text-transform: uppercase;
letter-spacing: var(--impresso-letter-spacing-smallcaps);
font-weight: var(--impresso-wght-smallcaps);
font-variation-settings: 'wght' var(--impresso-wght-smallcaps);
}
</style>
Loading

0 comments on commit 50ef1f2

Please sign in to comment.