Skip to content

Commit

Permalink
feat: CardsDeck
Browse files Browse the repository at this point in the history
  • Loading branch information
akdasa committed Mar 3, 2023
1 parent 9dc7c9e commit 27c42a1
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@
.padding {
/* padding: 10px; */
}
.card1 {
position: absolute;
width: 100%;
height: calc(100% - 10px);
}

.deck {
width: 100%;
height: 100%;
perspective: 1300px;
}
</style>
54 changes: 54 additions & 0 deletions src/CardsDeck.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import CardsDeck from './CardsDeck.vue'
import FlipCard from './FlipCard.vue'
import { action } from '@storybook/addon-actions'

export default {
title: 'Deck/Cards Deck',
component: { CardsDeck, FlipCard },
};

const flip = action("flip")

const Template = (args) => ({
components: { CardsDeck, FlipCard },
methods: {
flip(data) { flip(data); data.card.flipped = !data.card.flipped },
computeStyle(card) {
return `transform: translateX(${card.x}px)` +
` translateY(${card.y}px)` +
` translateZ(${card.z}px)` +
` rotateX(${card.x}deg)` +
` rotateY(${card.y}deg)` +
` rotateZ(${card.z}deg);` +
`transition: .25s linear;`+
`z-index: ${10 - card.index}`
}
},
setup() {
const cardsToShow = [
{ id: "0", index: 0, px: 0, py: 0, rx: 0, ry: 0, rz: 0, flipped: false },
{ id: "1", index: 1, px: 0, py: 0, rx: 0, ry: 0, rz: 0, flipped: false },
{ id: "2", index: 2, px: 0, py: 0, rx: 0, ry: 0, rz: 0, flipped: false },
]
return { args, cardsToShow };
},
template: `
<div style="width:200px;height:400px;background-color:gray">
<CardsDeck
:cards="cardsToShow"
:compute-style="computeStyle"
class="deck"
card-class="card1"
v-slot="data"
>
<FlipCard @click="() => flip(data)" card-class="padding" side-class="side" :showOverlay="false" :flipped="data.card.flipped">
<template #front>FRONT {{data.card.flipped}}</template>
<template #back>BACK</template>
</FlipCard>
</CardsDeck>
</div>
`
});

export const Default = Template.bind({});
Default.args = {};
110 changes: 110 additions & 0 deletions src/CardsDeck.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<template>
<div>
<div
v-for="item in cards"
:key="item.id"
:class="cardClass"
:style="props.computeStyle(item)"
@touchstart="$event => onEvent($event, 'start', item)"
@touchmove="$event => onEvent($event, 'moving', item)"
@touchend="$event => onEvent($event, 'moved', item)"
@mousedown="$event => onEvent($event, 'start', item)"
@mousemove="$event => onEvent($event, 'moving', item)"
@mouseup="$event => onEvent($event, 'moved', item)"
>
<slot :card="item" />
</div>
</div>
</template>

<script setup lang="ts">
import { useArrayFind, useArrayMap } from '@vueuse/core'
import { watch, defineEmits, defineProps, toRefs } from 'vue'
/* -------------------------------------------------------------------------- */
/* Interface */
/* -------------------------------------------------------------------------- */
export interface Card {
/** Unique Id of a card */
id: string
/** Index of a card in the deck: 0 is the top card */
index: number
}
const props = defineProps<{
/** List of cards to be displayed */
cards: Card[],
/**
* Computes style for a card and applies it
* @param card Card to calculate style for
*/
computeStyle: (card: Card) => string,
/** Class to be applied to the card */
cardClass?: string,
}>()
const emit = defineEmits<{
(e: 'place', card: Card): void
(e: 'moving', card: Card, start: Position, current: Position): void
(e: 'moved', card: Card, start: Position, current: Position): void
}>()
/* -------------------------------------------------------------------------- */
/* State */
/* -------------------------------------------------------------------------- */
const { cards, cardClass } = toRefs(props)
const topCard = useArrayFind(props.cards, x => x.index === 0)
const cardIds = useArrayMap(cards, x => x.id)
let startPosition: [number, number] | undefined = undefined
/* -------------------------------------------------------------------------- */
/* Watch */
/* -------------------------------------------------------------------------- */
watch(topCard, () => { props.cards.forEach(x => emit('place', x)) })
watch(cardIds, () => { props.cards.forEach(x => emit('place', x)) }, {immediate: true})
/* -------------------------------------------------------------------------- */
/* Handlers */
/* -------------------------------------------------------------------------- */
type InteractionEvent = TouchEvent | MouseEvent
type ActionType = 'start' | 'moving' | 'moved'
type Position = [number, number]
function getPosition(event: InteractionEvent): Position {
if (event instanceof TouchEvent) {
const touch = event.touches[0] || event.changedTouches[0]
return [touch.pageX, touch.pageY]
} else if (event instanceof MouseEvent) {
return [event.clientX, event.clientY]
}
return [0, 0]
}
function onEvent(
event: InteractionEvent,
type: ActionType,
card: Card,
) {
const currentPosition = getPosition(event)
if (type === 'start') {
startPosition = getPosition(event)
} else if (startPosition) {
emit(type as any, card, startPosition, currentPosition)
if (type === 'moved') { startPosition = undefined }
}
}
</script>
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as DarkImage } from './DarkImage.vue'
export { default as SVGTextLines } from './SVGTextLines.vue'
export { default as AudioPlayer } from './AudioPlayer.vue'
export { default as FlipCard } from './FlipCard.vue'
export { default as CardsDeck } from './CardsDeck.vue'

0 comments on commit 27c42a1

Please sign in to comment.