diff --git a/assets/stories/fixtures/documents/image-portrait.json b/assets/stories/fixtures/documents/image-portrait.json new file mode 100644 index 00000000..fc71fde4 --- /dev/null +++ b/assets/stories/fixtures/documents/image-portrait.json @@ -0,0 +1,15 @@ +{ + "@type": "Document", + "@id": "/api/documents/2", + "mimeType": "image/jpeg", + "imageWidth": "2043", + "imageHeight": "3040", + "mediaDuration": 0, + "imageAverageColor": "#533c28", + "alt": "img_test.jpg", + "relativePath": "02.jpg", + "processable": true, + "type": "image", + "copyright": "Sous licence protégée", + "folders": [] +} diff --git a/components/molecules/VCarouselControls/Default.stories.vue b/components/molecules/VCarouselControls/Default.stories.vue index 28519b6a..efe89a02 100644 --- a/components/molecules/VCarouselControls/Default.stories.vue +++ b/components/molecules/VCarouselControls/Default.stories.vue @@ -7,14 +7,14 @@ const index = ref(0) diff --git a/components/molecules/VCarouselControls/VCarouselControls.vue b/components/molecules/VCarouselControls/VCarouselControls.vue index 385b4eef..4c41bbb4 100644 --- a/components/molecules/VCarouselControls/VCarouselControls.vue +++ b/components/molecules/VCarouselControls/VCarouselControls.vue @@ -1,98 +1,52 @@ @@ -106,6 +60,20 @@ function onClick(event: Event) { align-items: center; justify-content: var(--v-carousel-controls-justify-content, center); gap: rem(8); + opacity: 0; + transition: opacity 0.3s; + + &[aria-hidden="true"] { + display: var(--v-carousel-controls-hidden-display, none); + } + + &--carousel-draggable { + opacity: var(--v-carousel-controls-opacity, 1); + } + + &[aria-hidden="true"] { + pointer-events: none; + } } .scroll { @@ -121,7 +89,7 @@ function onClick(event: Event) { &::before { position: absolute; - background-color: var(--theme-color-controls-selected, color-mix(in srgb, currentColor, transparent 70%)); + background-color: color-mix(in srgb, currentcolor, transparent 70%); content: ''; inset: 0; opacity: 0.2; @@ -132,19 +100,15 @@ function onClick(event: Event) { position: relative; width: clamp(#{rem(22)}, var(--v-carousel-controls-thumb-width), 100%); height: 100%; - background-color: var(--theme-color-controls-selected, currentColor); + background-color: currentcolor; content: ''; transform-origin: left; transition: 0.2s linear, 0.5s; transition-property: translate, width; } -.number { +.numbers { display: var(--v-carousel-controls-numbers-display, block); - - @include media('>=lg') { - display: var(--v-carousel-controls-numbers-display, none); - } } .button { diff --git a/components/molecules/VCarouselProgress/Default.stories.vue b/components/molecules/VCarouselProgress/Default.stories.vue new file mode 100644 index 00000000..70a490bd --- /dev/null +++ b/components/molecules/VCarouselProgress/Default.stories.vue @@ -0,0 +1,12 @@ + + + diff --git a/components/molecules/VCarouselProgress/VCarouselProgress.vue b/components/molecules/VCarouselProgress/VCarouselProgress.vue new file mode 100644 index 00000000..d8d5045c --- /dev/null +++ b/components/molecules/VCarouselProgress/VCarouselProgress.vue @@ -0,0 +1,94 @@ + + + + + diff --git a/components/organisms/VCarousel/AsyncSlides.stories.vue b/components/organisms/VCarousel/AsyncSlides.stories.vue index 04235bb6..f30dfb4e 100644 --- a/components/organisms/VCarousel/AsyncSlides.stories.vue +++ b/components/organisms/VCarousel/AsyncSlides.stories.vue @@ -24,7 +24,7 @@ const snapGridLength = ref(numSlides) diff --git a/components/organisms/VCarousel/Default.stories.vue b/components/organisms/VCarousel/Default.stories.vue index 8ead9b73..704fc774 100644 --- a/components/organisms/VCarousel/Default.stories.vue +++ b/components/organisms/VCarousel/Default.stories.vue @@ -23,7 +23,7 @@ const snapGridLength = ref(numSlides) diff --git a/components/organisms/VCarousel/VCarousel.vue b/components/organisms/VCarousel/VCarousel.vue index 72fc93fa..d412c464 100644 --- a/components/organisms/VCarousel/VCarousel.vue +++ b/components/organisms/VCarousel/VCarousel.vue @@ -17,7 +17,7 @@ const slideIndex = defineModel('index', { default: 0 }) const snapLength = defineModel('snapLength') const carouselEnabled = defineModel('enabled', { default: false }) -const props = withDefaults(defineProps(), { lazy: true }) +const props = withDefaults(defineProps(), { lazy: true }) const emit = defineEmits<{ progress: [number] diff --git a/components/organisms/VMediaViewer/Default.stories.vue b/components/organisms/VMediaViewer/Default.stories.vue new file mode 100644 index 00000000..9fc82ca0 --- /dev/null +++ b/components/organisms/VMediaViewer/Default.stories.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/components/organisms/VMediaViewer/VMediaViewer.vue b/components/organisms/VMediaViewer/VMediaViewer.vue new file mode 100644 index 00000000..e709b652 --- /dev/null +++ b/components/organisms/VMediaViewer/VMediaViewer.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/components/organisms/VMediaViewer/Video.stories.vue b/components/organisms/VMediaViewer/Video.stories.vue new file mode 100644 index 00000000..e8459780 --- /dev/null +++ b/components/organisms/VMediaViewer/Video.stories.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/components/organisms/VMediaViewerTransition/Default.stories.vue b/components/organisms/VMediaViewerTransition/Default.stories.vue new file mode 100644 index 00000000..ad25c0d0 --- /dev/null +++ b/components/organisms/VMediaViewerTransition/Default.stories.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/components/organisms/VMediaViewerTransition/VMediaViewerTransition.vue b/components/organisms/VMediaViewerTransition/VMediaViewerTransition.vue new file mode 100644 index 00000000..7891984c --- /dev/null +++ b/components/organisms/VMediaViewerTransition/VMediaViewerTransition.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/composables/use-carousel-controls.ts b/composables/use-carousel-controls.ts new file mode 100644 index 00000000..79d0fc44 --- /dev/null +++ b/composables/use-carousel-controls.ts @@ -0,0 +1,60 @@ +import type { Ref } from 'vue' + +export interface VCarouselControlsOptions { + displayNumbers?: boolean + snapLength: Ref + index: Ref +} + +function formatValue(n: number) { + return (n < 9 ? '0' : '') + (n + 1) +} + +export function useCarouselControls(options: VCarouselControlsOptions) { + const { t } = useI18n() + + const numbersOutput = computed(() => { + if (!options.displayNumbers) return + + return `${formatValue(toValue(options.index))} / ${formatValue(toValue(options.snapLength) - 1)}` + }) + + function onButtonClicked(event: Event) { + const el = event.currentTarget as HTMLButtonElement + + if (el.name === 'next') options.index.value = toValue(options.index) + 1 + else if (el.name === 'previous') options.index.value = toValue(options.index) - 1 + } + + const isCarouselDraggable = computed(() => { + const length = toValue(options.snapLength) + return !!length && length > 1 + }) + + const nextDisabled = computed(() => { + return (toValue(options.index) === toValue(options.snapLength) - 1) || !toValue(options.snapLength) + }) + + const nextButtonAttrs = computed(() => { + return { + disabled: nextDisabled.value, + name: 'next', + iconName: 'arrow-right', + ariaLabel: t('carousel.next_slide_aria'), + onClick: onButtonClicked, + } + }) + + const previousDisabled = computed(() => toValue(options.index) === 0) + const prevButtonAttrs = computed(() => { + return { + disabled: previousDisabled.value, + name: 'previous', + iconName: 'arrow-left', + ariaLabel: t('carousel.previous_slide_aria'), + onClick: onButtonClicked, + } + }) + + return { numbersOutput, onButtonClicked, nextButtonAttrs, prevButtonAttrs, previousDisabled, nextDisabled, isCarouselDraggable } +} diff --git a/composables/use-media-viewer.ts b/composables/use-media-viewer.ts new file mode 100644 index 00000000..ab0f9de6 --- /dev/null +++ b/composables/use-media-viewer.ts @@ -0,0 +1,19 @@ +import type { RoadizDocument } from '@roadiz/types' + +export function useMediaViewer() { + const documents = useState('mediaViewerDocuments', () => null) + const slideIndex = useState('mediaViewerIndex') + const isOpen = computed(() => !!documents.value) + + const open = (medias: RoadizDocument[], index?: number) => { + documents.value = medias + slideIndex.value = index ? index : 0 + // updateIndex(index || 0) + } + const close = () => (documents.value = null) + + const previousSlide = () => slideIndex.value = Math.max(slideIndex.value - 1, 0) + const nextSlide = () => slideIndex.value = Math.min(slideIndex.value + 1, (documents.value?.length || 1) - 1) + + return { documents, slideIndex, isOpen, open, close, nextSlide, previousSlide } +}