aboutsummaryrefslogtreecommitdiffstats
path: root/apps/settings/src
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-03-13 00:37:02 +0100
committerBenjamin Gaussorgues <benjamin.gaussorgues@nextcloud.com>2024-03-14 20:45:24 +0100
commitac4003879d65f5cfaf27a8a4c90090fc62f3ce2d (patch)
tree8221b880f8536a9ce591a3a218f616998cf3101d /apps/settings/src
parentaa29204fe0994190317eda1d6504fbb10ad61e29 (diff)
downloadnextcloud-server-ac4003879d65f5cfaf27a8a4c90090fc62f3ce2d.tar.gz
nextcloud-server-ac4003879d65f5cfaf27a8a4c90090fc62f3ce2d.zip
feat(settings): Implement `carousel` type for app discover section
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/settings/src')
-rw-r--r--apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue3
-rw-r--r--apps/settings/src/components/AppStoreDiscover/CarouselType.vue202
-rw-r--r--apps/settings/src/components/AppStoreDiscover/PostType.vue19
-rw-r--r--apps/settings/src/constants/AppDiscoverTypes.ts2
4 files changed, 222 insertions, 4 deletions
diff --git a/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue b/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue
index 1118f23419f..68610347420 100644
--- a/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue
+++ b/apps/settings/src/components/AppStoreDiscover/AppStoreDiscoverSection.vue
@@ -41,6 +41,7 @@ import logger from '../../logger'
import { apiTypeParser } from '../../utils/appDiscoverTypeParser.ts'
const PostType = defineAsyncComponent(() => import('./PostType.vue'))
+const CarouselType = defineAsyncComponent(() => import('./CarouselType.vue'))
const hasError = ref(false)
const elements = ref<IAppDiscoverElements[]>([])
@@ -75,6 +76,8 @@ onBeforeMount(async () => {
const getComponent = (type) => {
if (type === 'post') {
return PostType
+ } else if (type === 'carousel') {
+ return CarouselType
}
return defineComponent({
mounted: () => logger.error('Unknown component requested ', type),
diff --git a/apps/settings/src/components/AppStoreDiscover/CarouselType.vue b/apps/settings/src/components/AppStoreDiscover/CarouselType.vue
new file mode 100644
index 00000000000..d0f410b433d
--- /dev/null
+++ b/apps/settings/src/components/AppStoreDiscover/CarouselType.vue
@@ -0,0 +1,202 @@
+<template>
+ <section :aria-roledescription="t('settings', 'Carousel')" :aria-labelledby="headingId ? `${headingId}` : undefined">
+ <h3 v-if="headline" :id="headingId">
+ {{ translatedHeadline }}
+ </h3>
+ <div class="app-discover-carousel__wrapper">
+ <div class="app-discover-carousel__button-wrapper">
+ <NcButton class="app-discover-carousel__button app-discover-carousel__button--previous"
+ type="tertiary-no-background"
+ :aria-label="t('settings', 'Previous slide')"
+ :disabled="!hasPrevious"
+ @click="currentIndex -= 1">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiChevronLeft" />
+ </template>
+ </NcButton>
+ </div>
+
+ <Transition :name="transitionName" mode="out-in">
+ <PostType v-bind="shownElement"
+ :key="shownElement.id ?? currentIndex"
+ :aria-labelledby="`${internalId}-tab-${currentIndex}`"
+ :dom-id="`${internalId}-tabpanel-${currentIndex}`"
+ inline
+ role="tabpanel" />
+ </Transition>
+
+ <div class="app-discover-carousel__button-wrapper">
+ <NcButton class="app-discover-carousel__button app-discover-carousel__button--next"
+ type="tertiary-no-background"
+ :aria-label="t('settings', 'Next slide')"
+ :disabled="!hasNext"
+ @click="currentIndex += 1">
+ <template #icon>
+ <NcIconSvgWrapper :path="mdiChevronRight" />
+ </template>
+ </NcButton>
+ </div>
+ </div>
+ <div class="app-discover-carousel__tabs" role="tablist" :aria-label="t('settings', 'Choose slide to display')">
+ <NcButton v-for="index of content.length"
+ :id="`${internalId}-tab-${index}`"
+ :key="index"
+ :aria-label="t('settings', '{index} of {total}', { index, total: content.length })"
+ :aria-controls="`${internalId}-tabpanel-${index}`"
+ :aria-selected="`${currentIndex === (index - 1)}`"
+ role="tab"
+ type="tertiary-no-background"
+ @click="currentIndex = index - 1">
+ <template #icon>
+ <NcIconSvgWrapper :path="currentIndex === (index - 1) ? mdiCircleSlice8 : mdiCircleOutline" />
+ </template>
+ </NcButton>
+ </div>
+ </section>
+</template>
+
+<script lang="ts">
+import type { PropType } from 'vue'
+import type { IAppDiscoverCarousel } from '../../constants/AppDiscoverTypes.ts'
+
+import { mdiChevronLeft, mdiChevronRight, mdiCircleOutline, mdiCircleSlice8 } from '@mdi/js'
+import { translate as t } from '@nextcloud/l10n'
+import { computed, defineComponent, nextTick, ref, watch } from 'vue'
+import { commonAppDiscoverProps } from './common.ts'
+import { useLocalizedValue } from '../../composables/useGetLocalizedValue.ts'
+
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import PostType from './PostType.vue'
+
+export default defineComponent({
+ name: 'CarouselType',
+
+ components: {
+ NcButton,
+ NcIconSvgWrapper,
+ PostType,
+ },
+
+ props: {
+ ...commonAppDiscoverProps,
+
+ /**
+ * The content of the carousel
+ */
+ content: {
+ type: Array as PropType<IAppDiscoverCarousel['content']>,
+ required: true,
+ },
+ },
+
+ setup(props) {
+ const translatedHeadline = useLocalizedValue(computed(() => props.headline))
+
+ const currentIndex = ref(Math.min(1, props.content.length - 1))
+ const shownElement = ref(props.content[currentIndex.value])
+ const hasNext = computed(() => currentIndex.value < (props.content.length - 1))
+ const hasPrevious = computed(() => currentIndex.value > 0)
+
+ const internalId = computed(() => props.id ?? (Math.random() + 1).toString(36).substring(7))
+ const headingId = computed(() => `${internalId.value}-h`)
+
+ const transitionName = ref('slide-out')
+ watch(() => currentIndex.value, (o, n) => {
+ if (o < n) {
+ transitionName.value = 'slide-out'
+ } else {
+ transitionName.value = 'slide-in'
+ }
+
+ // Wait next tick
+ nextTick(() => {
+ shownElement.value = props.content[currentIndex.value]
+ })
+ })
+
+ return {
+ t,
+ internalId,
+ headingId,
+
+ hasNext,
+ hasPrevious,
+ currentIndex,
+ shownElement,
+
+ transitionName,
+
+ translatedHeadline,
+
+ mdiChevronLeft,
+ mdiChevronRight,
+ mdiCircleOutline,
+ mdiCircleSlice8,
+ }
+ },
+})
+</script>
+
+<style scoped lang="scss">
+h3 {
+ font-size: 24px;
+ font-weight: 600;
+ margin-block: 0 1em;
+}
+
+.app-discover-carousel {
+ &__wrapper {
+ display: flex;
+ }
+
+ &__button {
+ color: var(--color-text-maxcontrast);
+ position: absolute;
+ top: calc(50% - 22px); // 50% minus half of button height
+
+ &-wrapper {
+ position: relative;
+ }
+
+ // See padding of discover section
+ &--next {
+ right: -54px;
+ }
+ &--previous {
+ left: -54px;
+ }
+ }
+
+ &__tabs {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+
+ > * {
+ color: var(--color-text-maxcontrast);
+ }
+ }
+}
+</style>
+
+<style>
+.slide-in-enter-active,
+.slide-in-leave-active,
+.slide-out-enter-active,
+.slide-out-leave-active {
+ transition: all .4s ease-out;
+}
+
+.slide-in-leave-to,
+.slide-out-enter {
+ opacity: 0;
+ transform: translateX(50%);
+}
+
+.slide-in-enter,
+.slide-out-leave-to {
+ opacity: 0;
+ transform: translateX(-50%);
+}
+</style>
diff --git a/apps/settings/src/components/AppStoreDiscover/PostType.vue b/apps/settings/src/components/AppStoreDiscover/PostType.vue
index 8b182b56566..9d19a1b4da4 100644
--- a/apps/settings/src/components/AppStoreDiscover/PostType.vue
+++ b/apps/settings/src/components/AppStoreDiscover/PostType.vue
@@ -20,14 +20,15 @@
-
-->
<template>
- <article class="app-discover-post"
+ <article :id="domId"
+ class="app-discover-post"
:class="{ 'app-discover-post--reverse': media && media.alignment === 'start' }">
<component :is="link ? 'a' : 'div'"
v-if="headline || text"
:href="link"
:target="link ? '_blank' : undefined"
class="app-discover-post__text">
- <h3>{{ translatedHeadline }}</h3>
+ <component :is="inline ? 'h4' : 'h3'">{{ translatedHeadline }}</component>
<p>{{ translatedText }}</p>
</component>
<component :is="mediaLink ? 'a' : 'div'"
@@ -97,6 +98,18 @@ export default defineComponent({
required: false,
default: () => null,
},
+
+ inline: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+
+ domId: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
setup(props) {
@@ -178,7 +191,7 @@ export default defineComponent({
flex-direction: row-reverse;
}
- h3 {
+ h3, h4 {
font-size: 24px;
font-weight: 600;
margin-block: 0 1em;
diff --git a/apps/settings/src/constants/AppDiscoverTypes.ts b/apps/settings/src/constants/AppDiscoverTypes.ts
index 606288f5967..d28516fe79c 100644
--- a/apps/settings/src/constants/AppDiscoverTypes.ts
+++ b/apps/settings/src/constants/AppDiscoverTypes.ts
@@ -123,7 +123,7 @@ export interface IAppDiscoverShowcase extends IAppDiscoverElement {
export interface IAppDiscoverCarousel extends IAppDiscoverElement {
type: 'carousel'
text?: ILocalizedValue<string>
- content: (IAppDiscoverPost | IAppDiscoverApp)[]
+ content: IAppDiscoverPost[]
}
export type IAppDiscoverElements = IAppDiscoverPost | IAppDiscoverCarousel | IAppDiscoverShowcase