From 1bd69c9c97924a990f672af5339e2236ca4b7e24 Mon Sep 17 00:00:00 2001 From: webfussel <fiona@webfussel.de> Date: Sat, 10 May 2025 18:36:42 +0200 Subject: [PATCH] add: subheader Subheader for dynamic content --- app/assets/styles/button.css | 1 - app/assets/styles/header.css | 100 +++++++++++---------- app/assets/styles/page.css | 7 ++ app/components/Pp/Header.vue | 37 ++++---- app/pages/index.vue | 50 ++++------- app/pages/other.vue | 165 +++++++++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 101 deletions(-) create mode 100644 app/pages/other.vue diff --git a/app/assets/styles/button.css b/app/assets/styles/button.css index be84428..11a6a95 100755 --- a/app/assets/styles/button.css +++ b/app/assets/styles/button.css @@ -102,7 +102,6 @@ &:hover { scale: 1.2; - --background: var(--color-light); } } diff --git a/app/assets/styles/header.css b/app/assets/styles/header.css index 8db1384..74df67a 100755 --- a/app/assets/styles/header.css +++ b/app/assets/styles/header.css @@ -1,57 +1,61 @@ .Header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--padding-default); - background: var(--color-main-darkest); position: sticky; top: 0; z-index: 100; - font-family: 'Roboto', sans-serif; - font-weight: bold; + background: var(--color-main-darkest); - & a { - text-decoration: none; - color: var(--color-lightest); - } - - & .header-text { - font-size: 1.5em; - } - - & input[type="checkbox"] { - display: none; - } - - & input[type="checkbox"]:checked + nav { - translate: 0; - } - - & nav, - & ul { - gap: 1em; - } - - & nav { - position: fixed; - padding: var(--padding-default); - translate: 100% 0; - width: 100vw; - right: 0; - top: 0; - height: 100dvh; - transition: 150ms ease-in-out; - background: var(--color-lightest); - font-size: 2em; - align-items: end; - z-index: 100; - } - - & ul { - width: 100%; + & header { + display: flex; align-items: center; - & li { - list-style: none; + justify-content: space-between; + padding: var(--padding-default); + padding-bottom: 0; + font-family: 'Roboto', sans-serif; + font-weight: bold; + + & a { + text-decoration: none; + color: var(--color-lightest); + } + + & .header-text { + font-size: 1.5em; + } + + & input[type="checkbox"] { + display: none; + } + + & input[type="checkbox"]:checked + nav { + translate: 0; + } + + & nav, + & ul { + gap: 1em; + } + + & nav { + position: fixed; + padding: var(--padding-default); + translate: 100% 0; + width: 100vw; + right: 0; + top: 0; + height: 100dvh; + transition: 150ms ease-in-out; + background: var(--color-lightest); + font-size: 2em; + align-items: end; + z-index: 100; + } + + & ul { + width: 100%; + align-items: center; + & li { + list-style: none; + } } } } \ No newline at end of file diff --git a/app/assets/styles/page.css b/app/assets/styles/page.css index 8538a05..469d149 100644 --- a/app/assets/styles/page.css +++ b/app/assets/styles/page.css @@ -22,7 +22,14 @@ } .search-bar { + z-index: 100; padding: 0 1rem 1rem 1rem; + + & p { + font-family: 'Roboto Condensed', sans-serif; + font-weight: lighter; + color: var(--color-lightest); + } } .content { diff --git a/app/components/Pp/Header.vue b/app/components/Pp/Header.vue index d52d4a1..81c1ffd 100755 --- a/app/components/Pp/Header.vue +++ b/app/components/Pp/Header.vue @@ -1,22 +1,25 @@ <template> - <header class="Header"> - <NuxtLink class="header-text" to="/"> - ProPapier - </NuxtLink> - <label for="burger_nav_toggle" v-if="available"> - <Icon name="solar:hamburger-menu-broken" size="2em" /> - </label> - <input type="checkbox" id="burger_nav_toggle" v-if="available" /> - <nav class="flex-col" v-if="available"> - <label for="burger_nav_toggle"> - <Icon name="solar:close-circle-broken" /> + <div class="Header"> + <header> + <NuxtLink class="header-text" to="/"> + ProPapier + </NuxtLink> + <label for="burger_nav_toggle" v-if="available"> + <Icon name="solar:hamburger-menu-broken" size="2em" /> </label> - <ul class="flex-col"> - <li>Home</li> - <li>Übersicht</li> - </ul> - </nav> - </header> + <input type="checkbox" id="burger_nav_toggle" v-if="available" /> + <nav class="flex-col" v-if="available"> + <label for="burger_nav_toggle"> + <Icon name="solar:close-circle-broken" /> + </label> + <ul class="flex-col"> + <li>Home</li> + <li>Übersicht</li> + </ul> + </nav> + </header> + <div id="subheader" /> + </div> </template> <script setup lang="ts"> diff --git a/app/pages/index.vue b/app/pages/index.vue index 8b566c4..b2b0a12 100755 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -12,13 +12,11 @@ :current-card-index="currentCardIndex" @update="updateCard()" /> - <div class="search-bar"> - <PpFormSearch - v-model="search" - label="Suche nach Klopapier!" - id="search_field" - /> - </div> + <Teleport to="#subheader"> + <div class="search-bar"> + <p>Preise. Schnell. Unkompliziert.</p> + </div> + </Teleport> <section class="content flex-col"> <aside class="filter-bar"> <button v-for="(button, index) in filterButtons" @click="() => sort(index)" :class="{ 'active': button.active }"> @@ -38,15 +36,6 @@ </div> </section> <PpToolbar> - <PpButton class="mini-button text-white transparent" @click="sort(currentSort)"> - <Icon class="icon" name="uil:refresh" mode="svg" /> - <span>Neu sortieren</span> - <span - class="dot" - :class="{ visible : isDirty}" - aria-hidden="true" - /> - </PpButton> <PpButton class="mini-button text-white transparent" @click="openModal(true, -1)"> <Icon class="icon" name="uil:plus" mode="svg" /> <span>Hinzufügen</span> @@ -63,15 +52,12 @@ import { PpPriceCardDialog, PpDeleteDialog, PpPriceCard } from '#components' const cards = useLocalStorage<Card[]>('cards', []) const currentSort = useLocalStorage<number>('sort', 0) -const isDirty = ref(false) const currentCard = ref<Card>() const currentCardIndex = ref<number>(-1) const modal = useTemplateRef<typeof PpPriceCardDialog>('modal') const deleteModal = useTemplateRef<typeof PpDeleteDialog>('deleteModal') const priceCards = useTemplateRef<(typeof PpPriceCard)[]>('priceCard') -const search = ref('') - const createCard = (uuid : string) : Card => ({ uuid, name: '', @@ -84,12 +70,12 @@ const createCard = (uuid : string) : Card => ({ const addCard = (card : Card) => { cards.value.unshift({ ...card }) - isDirty.value = true + sort() } const removeCard = (index : number) => { cards.value.splice(index, 1) - isDirty.value = true + sort() } const updateCard = () => { @@ -100,7 +86,7 @@ const updateCard = () => { const newCard = { ...currentCard.value! } cards.value.splice(currentCardIndex.value, 1, newCard) - isDirty.value = true + sort() } const openModal = (createNew : boolean, index : number) => { @@ -149,23 +135,17 @@ const sortBy = (key : 'ppr' | 'pps' | 'ppl') => { }) } -const sort = (index : number) => { +const sort = async (index : number = currentSort.value) => { currentSort.value = index filterButtons.value.forEach(button => { button.active = false }) filterButtons.value[index]!.active = true - switch (index) { - case 0: - sortBy('ppr') - break - case 1: - sortBy('pps') - break - case 2: - sortBy('ppl') - break - } + await nextTick() - isDirty.value = false + switch (index) { + case 0: return sortBy('ppr') + case 1: return sortBy('pps') + case 2: return sortBy('ppl') + } } </script> diff --git a/app/pages/other.vue b/app/pages/other.vue new file mode 100644 index 0000000..bd60632 --- /dev/null +++ b/app/pages/other.vue @@ -0,0 +1,165 @@ +<template> + <div> + <ClientOnly> + <PpDeleteDialog + ref="deleteModal" + :current-card-index="currentCardIndex" + @delete="removeCard(currentCardIndex)" + /> + <PpPriceCardDialog + ref="modal" + :current-card="currentCard" + :current-card-index="currentCardIndex" + @update="updateCard()" + /> + <div class="search-bar"> + <PpFormSearch + v-model="search" + label="Suche nach Klopapier!" + id="search_field" + /> + </div> + <section class="content flex-col"> + <aside class="filter-bar"> + <button v-for="(button, index) in filterButtons" @click="() => sort(index)" :class="{ 'active': button.active }"> + {{ button.label }} + </button> + </aside> + <div class="flex-col" role="list"> + <PpPriceCard + ref="priceCard" + v-for="(card, index) in cards" + :key="card.uuid" + :deletable="cards.length > 1" + :card="card" + @update="openModal(false, index)" + @remove="openDeleteModal()" + /> + </div> + </section> + <PpToolbar> + <PpButton class="mini-button text-white transparent" @click="sort(currentSort)"> + <Icon class="icon" name="uil:refresh" mode="svg" /> + <span>Neu sortieren</span> + <span + class="dot" + :class="{ visible : isDirty}" + aria-hidden="true" + /> + </PpButton> + <PpButton class="mini-button text-white transparent" @click="openModal(true, -1)"> + <Icon class="icon" name="uil:plus" mode="svg" /> + <span>Hinzufügen</span> + </PpButton> + </PpToolbar> + </ClientOnly> + </div> +</template> + +<script setup lang="ts"> +import type { Card } from '../../shared/Card' +import type { Button } from '../../shared/ButtonGroup' +import { PpPriceCardDialog, PpDeleteDialog, PpPriceCard } from '#components' + +const cards = useLocalStorage<Card[]>('cards', []) +const currentSort = useLocalStorage<number>('sort', 0) +const isDirty = ref(false) +const currentCard = ref<Card>() +const currentCardIndex = ref<number>(-1) +const modal = useTemplateRef<typeof PpPriceCardDialog>('modal') +const deleteModal = useTemplateRef<typeof PpDeleteDialog>('deleteModal') +const priceCards = useTemplateRef<(typeof PpPriceCard)[]>('priceCard') + +const search = ref('') + +const createCard = (uuid : string) : Card => ({ + uuid, + name: '', + price: '', + roles: '', + sheets: '', + layers: '', +}) + + +const addCard = (card : Card) => { + cards.value.unshift({ ...card }) + isDirty.value = true +} + +const removeCard = (index : number) => { + cards.value.splice(index, 1) + isDirty.value = true +} + +const updateCard = () => { + if (currentCardIndex.value === -1) { + addCard(currentCard.value!) + return + } + + const newCard = { ...currentCard.value! } + cards.value.splice(currentCardIndex.value, 1, newCard) + isDirty.value = true +} + +const openModal = (createNew : boolean, index : number) => { + if (createNew) { + currentCardIndex.value = -1 + currentCard.value = createCard(randomUUID()) + modal.value?.$el.showModal() + return + } + + currentCardIndex.value = index + currentCard.value = { ...cards.value[index]! } + + modal.value?.$el.showModal() + return +} + +const openDeleteModal = () => { + deleteModal.value?.$el.showModal() +} + +const filterButtons = ref<Button[]>([ + { + label: 'Rollen', + icon: 'uil:toilet-paper', + active: currentSort.value === 0, + }, + { + label: 'Blatt', + icon: 'uil:file-landscape', + active: currentSort.value === 1, + }, + { + label: 'Lagen', + icon: 'uil:layer-group', + active: currentSort.value === 2, + }, +]) + +const sortBy = (key : 'ppr' | 'pps' | 'ppl') => { + cards.value.sort((a, b) => { + const aCard = priceCards.value?.find(card => card.uuid === a.uuid) || null + const bCard = priceCards.value?.find(card => card.uuid === b.uuid) || null + if (!aCard || !bCard) return 0 + return aCard[key] - bCard[key] + }) +} + +const sort = (index : number) => { + currentSort.value = index + filterButtons.value.forEach(button => { button.active = false }) + filterButtons.value[index]!.active = true + + isDirty.value = false + + switch (index) { + case 0: return sortBy('ppr') + case 1: return sortBy('pps') + case 2: return sortBy('ppl') + } +} +</script>