Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add VSlider #23

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions assets/scss/_functions.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
@import "functions/fluid";
@import "functions/list";
@import "functions/decimal";
@import "functions/flex-grid";
15 changes: 15 additions & 0 deletions assets/scss/functions/_flex-grid.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@function flex-grid($columns: 1, $total-columns: 1, $length: '%', $fullscreen: false) {
@return calc(#{flex-grid-value($columns, $total-columns, $length, $fullscreen)});
}

@function flex-grid-value($columns: 1, $total-columns: 1, $length: '%', $fullscreen: false) {
$num-gutters: if($fullscreen, $total-columns + 1, $total-columns - 1);
$total-width: '(100#{$length} - #{$num-gutters} * var(--gutter))';
$result: '(#{$total-width} / #{$total-columns})';

@if $columns != 1 {
$result: '#{$result} * #{$columns} + #{$columns - 1} * var(--gutter)';
}

@return $result;
}
44 changes: 44 additions & 0 deletions components/molecules/VSlider/Default.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts"></script>

<template>
<NuxtStory>
<NuxtStoryVariant title="Default">
<VSlider v-slot="{ itemClass }" :class="$style.root">
<div v-for="item in 20" :key="item" :class="[$style.item, itemClass]">
{{ item }}
</div>
</VSlider>
</NuxtStoryVariant>
<NuxtStoryVariant title="With scroll bar">
<VSlider v-slot="{ itemClass }" :class="[$style.root, $style['root--with-scroll-bar']]">
<div v-for="item2 in 20" :key="item2 + '-2'" :class="[$style.item, itemClass]">
{{ item2 }}
</div>
</VSlider>
</NuxtStoryVariant>
</NuxtStory>
</template>

<style lang="scss" module>
.root {
--gutter: #{rem(24)};

background-color: #d8d8d8;
gap: rem(24);
padding-block: rem(48);

&--with-scroll-bar {
scrollbar-width: thin;
}
}

.item {
display: flex;
width: flex-grid(1, 4);
height: rem(60);
align-items: center;
justify-content: center;
border: 1px solid black;
background-color: #ffd6d6;
}
</style>
84 changes: 84 additions & 0 deletions components/molecules/VSlider/IrregularWidthSlide.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue'
import type { VSlider } from '#components'
import type { Slide } from '~/components/molecules/VSlider/VSlider.vue'

const vSliderInstance = ref<ComponentPublicInstance<typeof VSlider> | null>(null)
const slides = computed(() => vSliderInstance.value?.slides as Slide[])

function setSnapMarkers(slides: Slide[]) {
slides.forEach((slide) => {
const leftMarker = slide.el?.querySelector('.marker-left') as HTMLElement
const rightMarker = slide.el?.querySelector('.marker-right') as HTMLElement

if (leftMarker) leftMarker.style.left = slide.snapLeft + 'px'
if (rightMarker) rightMarker.style.left = slide.snapRight + 'px'
})
}

watch(slides, (value) => {
if (value?.length) setSnapMarkers(value)
})
</script>

<template>
<NuxtStory>
<VSlider v-slot="{ itemClass }" ref="vSliderInstance" :class="$style.root">
<div v-for="item in 10" :key="item" :class="[$style.item, itemClass]">
<div class="marker marker-left"></div>
{{ item }}
<div class="marker marker-right"></div>
</div>
</VSlider>
</NuxtStory>
</template>

<style>
.marker-right,
.marker-left {
position: absolute;
z-index: 2;
left: 0;
width: 2px;
height: 100%;
background-color: red;
}
</style>

<style lang="scss" module>
.root {
--gutter: #{rem(24)};

position: relative;
background-color: #d8d8d8;
gap: rem(24);
padding-block: rem(48);

&--with-scroll-bar {
scrollbar-width: thin;
}
}

.item {
display: flex;
width: flex-grid(2, 10);
height: rem(300);
align-items: center;
justify-content: center;
border: 1px solid black;
background-color: #ffd6d6;

&:nth-child(odd) :global(.marker) {
width: 8px;
background-color: blue;
}

&:nth-child(2n) {
width: flex-grid(6, 10);
}

&:nth-child(3n) {
width: flex-grid(5, 10);
}
}
</style>
84 changes: 84 additions & 0 deletions components/molecules/VSlider/OneSlide.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue'
import type { VSlider } from '#components'
import type { Slide } from '~/components/molecules/VSlider/VSlider.vue'

const itemLength = ref(6)
const slideIndex = ref(0)

const vSliderInstance = ref<ComponentPublicInstance<typeof VSlider> | null>(null)
const slides = computed(() => vSliderInstance.value?.slides as Slide[])

function setSnapMarkers(slides: Slide[]) {
slides.forEach((slide) => {
const leftMarker = slide.el?.querySelector('.marker-left') as HTMLElement
const rightMarker = slide.el?.querySelector('.marker-right') as HTMLElement

if (leftMarker) {
leftMarker.style.left = slide.snapLeft + 'px'
}
if (rightMarker) {
rightMarker.style.left = slide.snapRight + 'px'
}
})
}

watch(slides, (value) => {
if (value?.length) setSnapMarkers(value)
})
</script>

<template>
<NuxtStory>
<VSlider v-slot="{ itemClass }" ref="vSliderInstance" v-model="slideIndex" :class="$style.root">
<div v-for="item in itemLength" :key="item" :class="[$style.item, itemClass]">
<div class="marker marker-left"></div>
{{ item }}
<div class="marker marker-right"></div>
</div>
</VSlider>
</NuxtStory>
</template>

<style>
.marker-right,
.marker-left {
position: absolute;
z-index: 2;
left: 0;
width: 2px;
height: 100%;
background-color: red;
}
</style>

<style lang="scss" module>
.root {
--gutter: #{rem(24)};
--v-slider-scroll-snap-align: center;

position: relative;
background-color: #d8d8d8;
padding-block: rem(48);

:global(.nuxt-story__main) {
touch-action: pan-x;
}
}

.item {
display: flex;
width: 60%;
height: rem(260);
align-items: center;
justify-content: center;
border: 1px solid black;
background-color: #ffd6d6;
margin-inline: 20%;

&:nth-child(odd) :global(.marker) {
width: 4px;
background-color: blue;
}
}
</style>
96 changes: 96 additions & 0 deletions components/molecules/VSlider/SnapVisualizer.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue'
import type { VSlider } from '#components'
import type { Slide } from '~/components/molecules/VSlider/VSlider.vue'

const itemLength = ref(18)
const value = ref(25)
const slideIndex = ref(0)

const vSliderInstance = ref<ComponentPublicInstance<typeof VSlider> | null>(null)
const slides = computed(() => vSliderInstance.value?.slides as Slide[])

function setSnapMarkers(slides: Slide[]) {
slides.forEach((slide) => {
const leftMarker = slide.el?.querySelector('.marker-left') as HTMLElement
const rightMarker = slide.el?.querySelector('.marker-right') as HTMLElement

if (leftMarker) leftMarker.style.left = slide.snapLeft + 'px'
if (rightMarker) rightMarker.style.left = slide.snapRight + 'px'
})
}

watch(slides, (value) => {
if (value?.length) setSnapMarkers(value)
})
</script>

<template>
<NuxtStory>
<template #aside>
<h4>Slide Index: {{ slideIndex }}</h4>
<h4>Slide Length: {{ itemLength }}</h4>
<button @click="() => (itemLength = itemLength - 1)">Remove slide</button>
<button @click="() => (itemLength = itemLength + 1)">Add slide</button>
<hr />
<h4>Slide width: {{ value }} %</h4>
<label for="item-width">
<input id="item-width" v-model="value" type="range" name="item-width" min="10" step="5" max="100" />
</label>
</template>
<VSlider v-slot="{ itemClass }" ref="vSliderInstance" v-model="slideIndex" :class="$style.root">
<div v-for="item in itemLength" :key="item" :class="[$style.item, itemClass]">
<div class="marker marker-left"></div>
{{ item }}
<div class="marker marker-right"></div>
</div>
</VSlider>

<div>
<button @click="() => (slideIndex = slideIndex - 1)">Prev</button>
<button @click="() => (slideIndex = slideIndex + 1)">Next</button>
</div>
</NuxtStory>
</template>

<style>
.marker-right,
.marker-left {
position: absolute;
z-index: 2;
left: 0;
width: 2px;
height: 100%;
background-color: red;
}
</style>

<style lang="scss" module>
.root {
--gutter: #{rem(24)};

position: relative;
background-color: #d8d8d8;
gap: rem(24);
padding-block: rem(48);

:global(.nuxt-story__main) {
touch-action: pan-x;
}
}

.item {
display: flex;
width: calc(((100% - 99 * var(--gutter)) / 100) * v-bind(value) + ((v-bind(value) - 1) * var(--gutter)));
height: rem(260);
align-items: center;
justify-content: center;
border: 1px solid black;
background-color: #ffd6d6;

&:nth-child(odd) :global(.marker) {
width: 4px;
background-color: blue;
}
}
</style>
41 changes: 41 additions & 0 deletions components/molecules/VSlider/VImgSlider.stories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup lang="ts">
const imageProps = {
src: '01.jpg',
width: 900,
height: 600,
alt: 'Image alt text',
provider: 'interventionRequest',
}
</script>

<template>
<NuxtStory>
<NuxtStoryVariant title="Default" :class="$style.root">
<VSlider v-slot="{ itemClass }" :class="$style.slider">
<VImg v-for="item in 20" :key="item" v-bind="imageProps" :class="[$style.item, itemClass]" />
</VSlider>
</NuxtStoryVariant>
</NuxtStory>
</template>

<style lang="scss" module>
.root {
:global(.nuxt-story-variant__main) {
display: grid;
}
}

.slider {
--gutter: #{rem(24)};

background-color: #d8d8d8;
gap: rem(24);
padding-block: rem(48);
}

.item {
width: flex-grid(1, 4);
border: 1px solid black;
background-color: #ffd6d6;
}
</style>
Loading
Loading