Files
privacy.sexy/src/presentation/components/Scripts/View/Cards/CardList.vue
undergroundwires bf83c58982 Refactor Saas naming, structure and modules
- Add more documentation.
- Use `main.scss` instead of importing components individually. This
  improves productivity without compilation errors due to missing
  imports and allows for easier future file/folder changes and
  refactorings inside `./styles`.
- Use partials with underscored naming. Because it documents that the
  files should not be individually imported.
- Introduce `third-party-extensions` folder to group styles that
  overwrites third party components.
- Refactor variable names from generic to specific.
- Use Sass modules (`@use` and `@forward`) over depreciated `@import`
  syntax.
- Separate font assets from Sass files (`styles/`). Create `assets/`
  folder that will contain both.
- Create `_globals.css` for global styling of common element instead of
  using `App.vue`.
2021-11-14 17:48:49 +01:00

128 lines
3.9 KiB
Vue

<template>
<Responsive v-on:widthChanged="width = $event">
<!-- <div id="responsivity-debug">
Width: {{ width || 'undefined' }}
Size: <span v-if="width <= 500">small</span><span v-if="width > 500 && width < 750">medium</span><span v-if="width >= 750">big</span>
</div> -->
<div v-if="categoryIds != null && categoryIds.length > 0" class="cards">
<CardListItem
class="card"
v-bind:class="{
'small-screen': width <= 500,
'medium-screen': width > 500 && width < 750,
'big-screen': width >= 750
}"
v-for="categoryId of categoryIds"
:data-category="categoryId"
v-bind:key="categoryId"
:categoryId="categoryId"
:activeCategoryId="activeCategoryId"
v-on:selected="onSelected(categoryId, $event)">
</CardListItem>
</div>
<div v-else class="error">Something went bad 😢</div>
</Responsive>
</template>
<script lang="ts">
import CardListItem from './CardListItem.vue';
import Responsive from '@/presentation/components/Shared/Responsive.vue';
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/components/Shared/StatefulVue';
import { ICategory } from '@/domain/ICategory';
import { hasDirective } from './NonCollapsingDirective';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
@Component({
components: {
CardListItem,
Responsive,
},
})
export default class CardList extends StatefulVue {
public width: number = 0;
public categoryIds: number[] = [];
public activeCategoryId?: number = null;
public created() {
document.addEventListener('click', this.outsideClickListener);
}
public destroyed() {
document.removeEventListener('click', this.outsideClickListener);
}
public onSelected(categoryId: number, isExpanded: boolean) {
this.activeCategoryId = isExpanded ? categoryId : undefined;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.setCategories(newState.collection.actions);
this.activeCategoryId = undefined;
}
private setCategories(categories: ReadonlyArray<ICategory>): void {
this.categoryIds = categories.map((category) => category.id);
}
private onOutsideOfActiveCardClicked(clickedElement: Element): void {
if (isClickable(clickedElement) || hasDirective(clickedElement)) {
return;
}
this.collapseAllCards();
if (hasDirective(clickedElement)) {
return;
}
this.activeCategoryId = null;
}
private outsideClickListener(event: PointerEvent) {
if (this.areAllCardsCollapsed()) {
return;
}
const element = document.querySelector(`[data-category="${this.activeCategoryId}"]`);
const target = event.target as Element;
if (element && !element.contains(target)) {
this.onOutsideOfActiveCardClicked(target);
}
}
private collapseAllCards(): void {
this.activeCategoryId = undefined;
}
private areAllCardsCollapsed(): boolean {
return !this.activeCategoryId;
}
}
function isClickable(element: Element) {
const cursorName = window.getComputedStyle(element).cursor;
return [ 'pointer', 'move', 'grab'].some((name) => cursorName === name)
|| cursorName.includes('resize')
|| [ 'onclick', 'href'].some((attributeName) => element.hasAttribute(attributeName))
|| [ 'a', 'button'].some((tagName) => element.closest(`.${tagName}`));
}
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.cards {
display: flex;
flex-flow: row wrap;
font-family: $font-main;
gap: $card-gap;
/*
Padding is used to allow scale animation (growing size) for cards on hover.
It ensures that there's room to grow, so the animation is shown without overflowing with scrollbars.
*/
padding: 10px;
}
.error {
width: 100%;
text-align: center;
font-size: 3.5em;
font-family: $font-normal;
}
</style>