Skip to content

Commit

Permalink
web/satellite: common table component created
Browse files Browse the repository at this point in the history
Change-Id: Ie45a2c37257f3311c767adcea1a22f284b66f24e
  • Loading branch information
NikolaiYurchenko committed Jun 19, 2022
1 parent 43ed35e commit f7a3be5
Show file tree
Hide file tree
Showing 6 changed files with 338 additions and 3 deletions.
37 changes: 37 additions & 0 deletions web/satellite/src/components/common/TableItem.vue
@@ -0,0 +1,37 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<tr>
<th v-if="selectable" class="icon">
<v-checkbox @setData="onChange" />
</th>
<th v-for="(val, key, index) in item" :key="index" class="align-left data">
<p>{{ val }}</p>
</th>
<slot name="options" />
</tr>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import VCheckbox from "@/components/common/VCheckbox.vue";
// @vue/component
@Component({
components: { VCheckbox }
})
export default class TableItem extends Vue {
@Prop({ default: false })
public readonly selectable: boolean;
@Prop({ default: () => {} })
public readonly item: object;
public isSelected = false;
public onChange(value: boolean): void {
this.isSelected = value;
this.$emit('setSelected', value);
}
}
</script>
137 changes: 137 additions & 0 deletions web/satellite/src/components/common/TablePagination.vue
@@ -0,0 +1,137 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<div class="pagination-container">
<div class="pagination-container__pages">
<span class="pagination-container__pages__label">{{ label }}</span>
<div class="pagination-container__button" @click="prevPage">
<PaginationRightIcon class="pagination-container__button__image reversed" />
</div>
<div class="pagination-container__button" @click="nextPage">
<PaginationRightIcon class="pagination-container__button__image" />
</div>
</div>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import PaginationRightIcon from '@/../static/images/common/tablePaginationArrowRight.svg';
import { OnPageClickCallback } from '@/types/pagination';
// @vue/component
@Component({
components: {
PaginationRightIcon,
},
})
export default class TablePagination extends Vue {
private currentPageNumber = 1;
public isLoading = false;
@Prop({default: 0})
private readonly totalPageCount: number;
@Prop({default: 0})
private readonly limit: number;
@Prop({default: 0})
private readonly totalItemsCount: number;
@Prop({default: () => () => new Promise(() => false)})
private readonly onPageClickCallback: OnPageClickCallback;
public get label(): string {
const currentMaxPage = this.currentPageNumber * this.limit > this.totalItemsCount ?
this.totalItemsCount
: this.currentPageNumber * this.limit;
return `${this.currentPageNumber * this.limit - this.limit + 1} - ${currentMaxPage} of ${this.totalItemsCount}`
}
/**
* Indicates if current page is first.
*/
public get isFirstPage(): boolean {
return this.currentPageNumber === 1;
}
/**
* Indicates if current page is last.
*/
public get isLastPage(): boolean {
return this.currentPageNumber === this.totalPageCount;
}
/**
* nextPage fires after 'next' arrow click.
*/
public async nextPage(): Promise<void> {
if (this.isLastPage || this.isLoading) {
return;
}
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber + 1);
this.incrementCurrentPage();
this.isLoading = false;
}
/**
* prevPage fires after 'previous' arrow click.
*/
public async prevPage(): Promise<void> {
if (this.isFirstPage || this.isLoading) {
return;
}
this.isLoading = true;
await this.onPageClickCallback(this.currentPageNumber - 1);
this.decrementCurrentPage();
this.isLoading = false;
}
private incrementCurrentPage(): void {
this.currentPageNumber++;
}
private decrementCurrentPage(): void {
this.currentPageNumber--;
}
}
</script>

<style scoped lang="scss">
.pagination-container {
display: flex;
align-items: center;
justify-content: flex-end;
&__pages {
display: flex;
align-items: center;
&__label {
margin-right: 25px;
font-family: 'font_regular', sans-serif;
font-size: 14px;
line-height: 24px;
color: rgb(44 53 58 / 60%);
}
}
&__button {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
width: 15px;
height: 15px;
max-width: 15px;
max-height: 15px;
}
}
.reversed {
transform: rotate(180deg);
}
</style>
2 changes: 1 addition & 1 deletion web/satellite/src/components/common/VCheckbox.vue
Expand Up @@ -7,7 +7,7 @@
<input id="checkbox" v-model="checked" class="checkmark-input" type="checkbox" @change="onChange">
<span class="checkmark" :class="{'error': isCheckboxError}" />
</label>
<label class="label" for="checkbox">{{ label }}</label>
<label v-if="label" class="label" for="checkbox">{{ label }}</label>
</div>
</template>

Expand Down
150 changes: 150 additions & 0 deletions web/satellite/src/components/common/VTable.vue
@@ -0,0 +1,150 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.

<template>
<div class="table-wrapper">
<table class="base-table" border="0" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th v-if="selectable" class="icon" />
<slot name="head" />
</tr>
</thead>
<tbody>
<slot name="body" :selectable-items="selectableItems" @checkItem="checkItem" />
</tbody>
</table>
<div v-if="totalPageCount > 0" class="table-footer">
<span class="table-footer__label">{{ totalItemsCount }} {{ itemsLabel }}</span>
<table-pagination
:total-page-count="totalPageCount"
:total-items-count="totalItemsCount"
:limit="limit"
:on-page-click-callback="onPageClickCallback"
/>
</div>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import TablePagination from "@/components/common/TablePagination.vue";
import { OnPageClickCallback } from "@/types/pagination";
export type SelectableItem<T> = T & {
isSelected: boolean;
}
// @vue/component
@Component({
components: {
TablePagination,
}
})
export default class VTable extends Vue {
@Prop({ default: false })
public readonly selectable: boolean;
@Prop({default: 0})
private readonly totalPageCount: number;
@Prop({default: "items"})
private readonly itemsLabel: string;
@Prop({default: 0})
private readonly limit: number;
@Prop({default: () => []})
private readonly items: object[];
@Prop({default: 0})
private readonly totalItemsCount: number;
@Prop({default: () => () => new Promise(() => false)})
private readonly onPageClickCallback: OnPageClickCallback;
public selectableItems: SelectableItem<object>[] = [];
public mounted(): void {
this.selectableItems = this.items.map(item => ({ isSelected: false, ...item }));
}
public checkItem({ value, index }: { value: boolean, index: number }): void {
this.selectableItems[index].isSelected = value;
}
}
</script>

<style lang="scss">
.table-wrapper {
background: #fff;
box-shadow: 0 4px 32px rgb(0 0 0 / 4%);
border-radius: 12px;
}
.base-table {
display: table;
width: 100%;
z-index: 997;
th {
box-sizing: border-box;
padding: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-family: 'font_regular', sans-serif;
}
thead {
background: var(--c-block-gray);
text-transform: uppercase;
tr {
height: 52px;
font-size: 12px;
color: #6b7280;
}
}
tbody {
tr:hover {
background: #e6edf7;
}
th {
font-family: 'font_regular', sans-serif;
color: #111827;
font-size: 14px;
border-top: solid 1px #e5e7eb;
}
.data {
font-family: 'font_bold', sans-serif;
}
.data ~ .data {
font-family: 'font_regular', sans-serif;
}
}
}
.align-left {
text-align: left;
}
.overflow-visible {
overflow: visible !important;
}
.icon {
width: 5%;
overflow: visible !important;
}
.table-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px;
font-size: 14px;
line-height: 24px;
color: rgb(44 53 58 / 60%);
border-top: solid 1px #e5e7eb;
}
</style>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -1,5 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Checkbox.vue renders correctly 1`] = `<div class="wrap"><label class="container"><input id="checkbox" type="checkbox" class="checkmark-input"> <span class="checkmark"></span></label> <label for="checkbox" class="label"></label></div>`;
exports[`Checkbox.vue renders correctly 1`] = `
<div class="wrap"><label class="container"><input id="checkbox" type="checkbox" class="checkmark-input"> <span class="checkmark"></span></label>
<!---->
</div>
`;
exports[`Checkbox.vue renders correctly with error 1`] = `<div class="wrap"><label class="container"><input id="checkbox" type="checkbox" class="checkmark-input"> <span class="checkmark error"></span></label> <label for="checkbox" class="label"></label></div>`;
exports[`Checkbox.vue renders correctly with error 1`] = `
<div class="wrap"><label class="container"><input id="checkbox" type="checkbox" class="checkmark-input"> <span class="checkmark error"></span></label>
<!---->
</div>
`;

1 comment on commit f7a3be5

@storjrobot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on Storj Community Forum (official). There might be relevant details there:

https://forum.storj.io/t/release-preparation-v-1-58/18976/1

Please sign in to comment.