add: only delete swipe, edit on tap

Edit price cards on tap, only delete swipe exists, fixed dialogs, add ripple
This commit is contained in:
Fiona Lena Urban 2025-05-12 14:56:05 +02:00
parent 1a5dd102e0
commit 38cd37cf74
9 changed files with 145 additions and 93 deletions

View file

@ -0,0 +1,71 @@
dialog {
top: 50%;
left: 50%;
width: 100vw;
translate: -50% -50%;
border: none;
border-radius: var(--radius-default);
background: var(--color-lightest);
font-size: 1rem;
color: var(--color-darkest);
position: relative;
opacity: 0;
scale: 0;
transition:
opacity var(--transition-default) ease-out,
scale var(--transition-default) ease-out,
overlay var(--transition-default) ease-out allow-discrete,
display var(--transition-default) ease-out allow-discrete;
&[open] {
opacity: 1;
scale: 1;
&::backdrop {
background-color: rgb(0 0 0 / 25%);
}
}
&::backdrop {
z-index: 2000;
background-color: rgb(0 0 0 / 0%);
transition:
display var(--transition-default) allow-discrete,
overlay var(--transition-default) allow-discrete,
background-color var(--transition-default);
}
& > .wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
}
& header {
justify-content: space-between;
align-items: center;
padding: var(--padding-default) var(--padding-default) 0;
}
main {
padding: 0 var(--padding-default);
}
& footer {
justify-content: space-between;
padding: 0 var(--padding-default) var(--padding-default);
}
}
@starting-style {
dialog[open] {
opacity: 0;
scale: 0;
&::backdrop {
background-color: rgb(0 0 0 / 0%);
}
}
}

View file

@ -172,40 +172,3 @@ body {
flex-grow: 1;
height: 10px;
}
dialog {
top: 50%;
left: 50%;
width: 100vw;
transform: translate(-50%, -50%);
border: none;
border-radius: var(--radius-default);
background: var(--color-lightest);
font-size: 1rem;
color: var(--color-darkest);
& > .wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
}
& header {
justify-content: space-between;
align-items: center;
padding: var(--padding-default) var(--padding-default) 0;
}
main {
padding: 0 var(--padding-default);
}
& footer {
justify-content: space-between;
padding: 0 var(--padding-default) var(--padding-default);
}
&::backdrop {
background: rgba(0, 0, 0, 0.5);
}
}

View file

@ -4,7 +4,7 @@
background: var(--color-main-darkest);
position: sticky;
bottom: 0;
z-index: 100;
z-index: 1000;
box-shadow: var(--box-shadow-upper);
& > .Button {

View file

@ -1,9 +1,9 @@
<template>
<dialog
ref="dialog"
closedby="any"
closedby="none"
>
<form method="dialog" class="wrapper">
<form method="dialog" class="wrapper" ref="wrapper">
<header class="flex-row">
Wirklich löschen?
<PpButton class="round text">
@ -35,5 +35,11 @@ type Props = {
defineProps<Props>()
defineEmits(['delete'])
const dialog = useTemplateRef<HTMLDialogElement>('dialog')
const wrapper = useTemplateRef<HTMLElement>('wrapper')
onMounted(() => {
onClickOutside(wrapper, () => dialog.value?.close())
})
</script>

View file

@ -1,5 +1,5 @@
<template>
<article class="PriceCard roboto-condensed">
<article class="PriceCard roboto-condensed" v-ripple="{ color: 'rgba(0, 0, 0, 0.1)' }">
<div class="bottom">
<div class="bg-edit">
<Icon class="icon" name="uil:pen" mode="svg" />
@ -13,6 +13,7 @@
class="top flex-col"
:class="{ 'animated' : !isSwiping }"
:style="{ left }"
@click="update"
>
<header>
<div class="name-price">
@ -20,10 +21,10 @@
<span>{{ intl.format(+replaceComma(card.price))}}</span>
</div>
<div v-if="$device.isDesktop" class="flex-row gap-default">
<PpButton class="icon-button" @click="update()">
<PpButton class="icon-button" @click="update">
<Icon class="icon" name="uil:pen" mode="svg" />
</PpButton>
<PpButton class="icon-button" @click="deleteCard()">
<PpButton class="icon-button" @click="deleteCard">
<Icon class="icon" name="uil:trash-alt" mode="svg" />
</PpButton>
</div>
@ -78,14 +79,13 @@ const intl = Intl.NumberFormat('de-DE', {
const { lengthX, direction, isSwiping } = useSwipe(top, {
passive: true,
threshold: 30,
threshold: 20,
onSwipe() {
if (['down', 'up'].includes(direction.value)) return
left.value = `${-clamp(lengthX.value, -100, 100)}px`
left.value = `${-clamp(lengthX.value, 0, 100)}px`
},
onSwipeEnd() {
if (['down', 'up'].includes(direction.value)) return
if (lengthX.value < -50) update()
if (lengthX.value > 50) deleteCard()
vibrate(100)
left.value = '0'
@ -98,7 +98,6 @@ const pps = computed(() => (ppr.value / +card.sheets) * 100)
const ppl = computed(() => (pps.value / +card.layers) * 10)
const update = () => emit('update')
const deleteCard = () => emit('remove')
defineExpose({

View file

@ -1,6 +1,6 @@
<template>
<dialog ref="dialog" closedby="any">
<div class="wrapper">
<dialog ref="dialog" closedby="none">
<div class="wrapper" ref="wrapper">
<form method="dialog">
<header class="flex-row">
{{ currentCardIndex > -1 ? 'Bearbeiten' : 'Neues hinzufügen' }}
@ -81,38 +81,32 @@
</template>
<script setup lang="ts">
import type { Card } from '../../../shared/Card';
import type { Card } from '../../../shared/Card'
type Props = {
currentCardIndex: number;
currentCard?: Card;
};
currentCardIndex: number
currentCard?: Card
}
const { currentCardIndex, currentCard } = defineProps<Props>();
const emit = defineEmits(['update']);
const { currentCardIndex, currentCard } = defineProps<Props>()
const emit = defineEmits(['update'])
const dialog = useTemplateRef<HTMLDialogElement>("dialog");
const dialog = useTemplateRef<HTMLDialogElement>('dialog')
const wrapper = useTemplateRef<HTMLElement>('wrapper')
const checkPrice = () => {
if (!currentCard) {
return false;
if (!currentCard) return false
if (currentCard.price.length === 0) return false
const price = +replaceComma(currentCard.price)
return !isNaN(price)
}
if (currentCard.price.length === 0) {
return false;
}
const price = +replaceComma(currentCard.price);
return !isNaN(price);
};
const checkIfInteger = (toBeNumber: string) => {
if (toBeNumber.length === 0) {
return false;
if (toBeNumber.length === 0) return false
if (toBeNumber.includes(',') || toBeNumber.includes(',')) return false
return !isNaN(+toBeNumber)
}
if (toBeNumber.includes(",") || toBeNumber.includes(".")) {
return false;
}
return !isNaN(+toBeNumber);
};
const validFields = reactive({
name: true,
@ -120,32 +114,32 @@ const validFields = reactive({
roles: true,
sheets: true,
layers: true,
});
})
const validate = () => {
if (!currentCard) {
return;
}
if (!currentCard) return
validFields.name = currentCard.name.length > 0;
validFields.price = checkPrice();
validFields.roles = checkIfInteger(currentCard.roles);
validFields.sheets = checkIfInteger(currentCard.sheets);
validFields.layers = checkIfInteger(currentCard.layers);
validFields.name = currentCard.name.length > 0
validFields.price = checkPrice()
validFields.roles = checkIfInteger(currentCard.roles)
validFields.sheets = checkIfInteger(currentCard.sheets)
validFields.layers = checkIfInteger(currentCard.layers)
if (Object.values(validFields).every((value) => value)) {
emit('update');
dialog.value?.close();
emit('update')
dialog.value?.close()
}
}
};
onMounted(() => {
dialog.value?.addEventListener('close', () => {
validFields.name = true;
validFields.price = true;
validFields.roles = true;
validFields.sheets = true;
validFields.layers = true;
});
});
validFields.name = true
validFields.price = true
validFields.roles = true
validFields.sheets = true
validFields.layers = true
})
onClickOutside(wrapper, () => dialog.value?.close())
})
</script>

View file

@ -36,7 +36,14 @@ export default defineNuxtConfig({
'/privacy': { prerender: true },
},
modules: ['@nuxt/icon', '@vueuse/nuxt', '@nuxtjs/device', '@nuxt/fonts', 'nuxt-seo-utils'],
modules: [
'@nuxt/icon',
'@vueuse/nuxt',
'@nuxtjs/device',
'@nuxt/fonts',
'nuxt-seo-utils',
'nuxt-ripple',
],
css : [
'./app/assets/styles/general.css',
@ -49,6 +56,7 @@ export default defineNuxtConfig({
'./app/assets/styles/form/search.css',
'./app/assets/styles/toolbar.css',
'./app/assets/styles/page.css',
'./app/assets/styles/dialog.css',
],
site: {

10
package-lock.json generated
View file

@ -14,6 +14,7 @@
"@nuxtjs/device": "^3.2.4",
"@vueuse/nuxt": "^13.1.0",
"nuxt": "^3.16.2",
"nuxt-ripple": "^0.0.8",
"nuxt-seo-utils": "^7.0.11",
"vue": "latest",
"vue-router": "latest"
@ -9161,6 +9162,15 @@
}
}
},
"node_modules/nuxt-ripple": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/nuxt-ripple/-/nuxt-ripple-0.0.8.tgz",
"integrity": "sha512-ellqn8+OXIS4SffFhIqZHSdTNQglAwGJ7B8R2ISR4+BauP360E7cfK6vr1FGqd40HyoeSLV03Z142bTey25hCg==",
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.14.1592"
}
},
"node_modules/nuxt-seo-utils": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/nuxt-seo-utils/-/nuxt-seo-utils-7.0.11.tgz",

View file

@ -19,6 +19,7 @@
"@nuxtjs/device": "^3.2.4",
"@vueuse/nuxt": "^13.1.0",
"nuxt": "^3.16.2",
"nuxt-ripple": "^0.0.8",
"nuxt-seo-utils": "^7.0.11",
"vue": "latest",
"vue-router": "latest"