From 7c46be622756be338ee9d3041e984e0a018e944e Mon Sep 17 00:00:00 2001
From: webfussel <fiona@webfussel.de>
Date: Mon, 24 Feb 2025 21:38:45 +0100
Subject: [PATCH] add: collapsing and deletion animation

Added collapsable cards and animation for deletion and card footer
---
 app/assets/styles/button.css    | 17 ++++++--
 app/assets/styles/general.css   |  7 +++
 app/assets/styles/priceCard.css | 38 ++++++++++++++---
 app/assets/styles/toolbar.css   |  1 +
 app/components/Pp/PriceCard.vue | 75 +++++++++++++++++++++++++--------
 app/pages/index.vue             | 12 +++++-
 6 files changed, 121 insertions(+), 29 deletions(-)

diff --git a/app/assets/styles/button.css b/app/assets/styles/button.css
index ee545bd..c247d2c 100644
--- a/app/assets/styles/button.css
+++ b/app/assets/styles/button.css
@@ -1,5 +1,9 @@
 .Button {
     --padding: .2rem;
+    --background: var(--color-main);
+    --color: var(--color-white);
+    --background-hover: var(--color-main-dark);
+
     display: flex;
     justify-content: center;
     align-items: center;
@@ -9,19 +13,26 @@
     outline: none;
     border: none;
     background: transparent;
+    color: var(--color);
 
     &.cta {
-        background: var(--color-main);
-        color: var(--color-white);
+        background: var(--background);
+        color: var(--color);
         padding: .5rem 1.5rem;
         border-radius: var(--radius-default);
         box-shadow: var(--box-shadow-z2);
 
         &:hover {
-            background: var(--color-main-dark);
+            background: var(--background-hover);
         }
     }
 
+    &.cta.white {
+        --background: var(--color-white);
+        --color: var(--color-main);
+        --background-hover: var(--color-grey);
+    }
+
     &.icon-button {
         display: flex;
         padding: var(--padding);
diff --git a/app/assets/styles/general.css b/app/assets/styles/general.css
index 2d68fc6..ad3b46c 100644
--- a/app/assets/styles/general.css
+++ b/app/assets/styles/general.css
@@ -14,7 +14,9 @@
     --color-main-light: var(--color-blue-light);
     --color-main-dark: var(--color-blue-dark);
 
+    --box-shadow-upper: 0 -3px 6px rgba(0,0,0,0.16), 0 -3px 6px rgba(0,0,0,0.23);
     --box-shadow-z2: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
+    --box-shadow-inset: inset 0 3px 6px rgba(0,0,0,0.16), inset 0 3px 6px rgba(0,0,0,0.23);
 }
 
 * {
@@ -52,6 +54,11 @@ body {
     flex-direction: column;
 }
 
+.flex-row {
+    display: flex;
+    flex-direction: row;
+}
+
 .text-white {
     color: var(--color-white);
 }
diff --git a/app/assets/styles/priceCard.css b/app/assets/styles/priceCard.css
index ff44c5b..92ad006 100644
--- a/app/assets/styles/priceCard.css
+++ b/app/assets/styles/priceCard.css
@@ -1,34 +1,60 @@
 .PriceCard {
+    --height: auto;
     width: 100%;
+    display: grid;
+    transition: var(--transition-default);
+    grid-template-rows: auto 1fr auto;
+    height: var(--height);
+
+    &.deleting {
+        height: 0;
+    }
+
+    &.folded {
+        grid-template-rows: auto 0fr auto;
+    }
 
     & > header {
-        color: white;
+        color: var(--color-white);
         display: flex;
         justify-content: space-between;
         align-items: center;
         font-size: 1.3em;
 
         & > .Button {
-            scale: 0;
             color: var(--color-white);
             border: 2px solid var(--color-white);
         }
+    }
+
+    & aside {
+        overflow: hidden;
+    }
+
+    & footer {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        & > .Button.delete {
+            scale: 0;
+        }
 
         & > .Button.deletable {
             scale: 1;
         }
     }
 
-    & > .padding {
+    & .padding {
         gap: 1rem;
         padding: var(--padding-default);
     }
 
-    & > .bg-blue {
+    & .bg-blue {
         background: var(--color-blue);
     }
 
-    & > .bg-white {
+    & .bg-white {
         background: var(--color-white);
     }
 
@@ -40,7 +66,7 @@
         justify-content: space-between;
 
         & > * {
-            flex-basis: 30%;
+            flex-basis: 25%;
             flex-grow: 1;
         }
 
diff --git a/app/assets/styles/toolbar.css b/app/assets/styles/toolbar.css
index fdb3a25..44f305f 100644
--- a/app/assets/styles/toolbar.css
+++ b/app/assets/styles/toolbar.css
@@ -4,6 +4,7 @@
     background: var(--color-main);
     position: sticky;
     bottom: 0;
+    box-shadow: var(--box-shadow-upper);
 
     & > .Button {
         --padding: 1rem;
diff --git a/app/components/Pp/PriceCard.vue b/app/components/Pp/PriceCard.vue
index 21fc886..0aa0e99 100644
--- a/app/components/Pp/PriceCard.vue
+++ b/app/components/Pp/PriceCard.vue
@@ -1,27 +1,34 @@
 <template>
-  <form class="PriceCard card">
+  <form
+      ref="root"
+      class="PriceCard card"
+      :class="{ folded }"
+      :style="{ '--height': height }"
+      @submit.prevent="() => {}"
+  >
     <header class="padding bg-blue">
-      TODO: IRGENDWAS
+      {{ name || 'Kein Name' }}
       <PpButton
           class="icon-button bg-main"
-          :class="[deletable && 'deletable']"
-          @click="emit('remove', uid)"
+          @click="folded = !folded"
       >
-        <Icon name="uil:times" mode="svg" />
+        <Icon :name="folded ? 'uil:sort' : 'uil:sorting'" mode="svg" />
       </PpButton>
     </header>
-    <div class="padding bg-blue flex-col">
-      <div class="wrapper">
-<!--        <PpFormInput v-model="name" label="Name" id="n" :uid="uid" type="text" />-->
-        <PpFormInput v-model="price" label="Preis" id="p" :uid="uid" type="number" :min="0.01" @blur="calculate" />
+    <aside>
+      <div class="input-wrapper padding bg-blue flex-col">
+        <div class="wrapper">
+          <PpFormInput v-model="name" label="Name" id="n" :uid="uid" type="text" />
+          <PpFormInput v-model="price" label="Preis" id="p" :uid="uid" type="number" :min="0.01" @blur="calculate" />
+        </div>
+        <div class="wrapper">
+          <PpFormInput v-model="roles" label="Rollen" id="r" :uid="uid" type="number" :max="150" @blur="calculate" />
+          <PpFormInput v-model="sheets" label="Blätter" id="b" :uid="uid" type="number" :max="500" @blur="calculate" />
+          <PpFormInput v-model="layers" label="Lagen" id="l" :uid="uid" type="number" :max="10" @blur="calculate" />
+        </div>
       </div>
-      <div class="wrapper">
-        <PpFormInput v-model="roles" label="Rollen" id="r" :uid="uid" type="number" :max="150" @blur="calculate" />
-        <PpFormInput v-model="sheets" label="Blätter" id="b" :uid="uid" type="number" :max="500" @blur="calculate" />
-        <PpFormInput v-model="layers" label="Lagen" id="l" :uid="uid" type="number" :max="10" @blur="calculate" />
-      </div>
-    </div>
-    <div class="wrapper padding bg-white">
+    </aside>
+    <main class="wrapper padding bg-white">
       <div class="info flex-col">
         <Icon class="icon" name="uil:toilet-paper" mode="svg" />
         <span class="price">{{ intl.format(ppr) }}</span>
@@ -37,20 +44,42 @@
         <span class="price">{{ intl.format(ppl) }}</span>
         <span class="pro">Pro 100</span>
       </div>
-    </div>
+    </main>
+    <footer class="padding bg-blue flex-row">
+      <PpButton
+          class="delete"
+          :class="{ deletable }"
+          @click="deleteCard"
+      >
+        <Icon name="uil:trash" mode="svg" />
+        Entfernen
+      </PpButton>
+      <PpButton class="cta white">
+        <Icon name="uil:qrcode-scan" mode="svg" />
+        Scan
+      </PpButton>
+    </footer>
   </form>
 </template>
 
 <script setup lang="ts">
+import { nextTick } from '@redocly/openapi-core/src/utils'
+
 type Props = {
   uid: string
   deletable: boolean
 }
 
 const { uid } = defineProps<Props>()
-
 const emit = defineEmits(['remove'])
 
+const root = ref<HTMLElement>()
+const height = ref<string>()
+const folded = ref(false)
+
+onMounted(() => {
+})
+
 const name = ref('')
 const price = ref(0)
 const roles = ref(0)
@@ -76,4 +105,14 @@ const calculate = () => {
   if(!layers.value) return
   ppl.value = (pps.value / layers.value) * 10
 }
+
+const deleteCard = async () => {
+  root.value?.addEventListener('transitionend', () => {
+    emit('remove', uid)
+  })
+
+  height.value = `${root.value?.offsetHeight}px`
+  await nextTick()
+  root.value?.classList.add('deleting')
+}
 </script>
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 5c51e3a..4530791 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -23,14 +23,22 @@
 </template>
 
 <script setup lang="ts">
-const initialId = crypto.randomUUID()
+const randomUUID = () => {
+  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, char => {
+    const random = Math.random() * 16 | 0
+    const value = char === 'x' ? random : (random & 0x3 | 0x8)
+    return value.toString(16)
+  })
+}
+
+const initialId = randomUUID()
 
 const cards = useState('cards', () => [
   initialId,
 ])
 
 const addCard = () => {
-  cards.value.push(crypto.randomUUID())
+  cards.value.push(randomUUID())
 }
 
 const removeCard = (uuid : string) => {