diff --git a/ui/components/atoms/loader/constants.ts b/ui/components/atoms/loader/constants.ts new file mode 100644 index 0000000..525bc93 --- /dev/null +++ b/ui/components/atoms/loader/constants.ts @@ -0,0 +1,11 @@ +export const colorConfig = { + blue: [ + 'rgba(222, 229, 255, 1)', + 'rgba(189, 204, 255, 1)', + 'rgba(155, 178, 255, 1)', + 'rgba(122, 153, 255, 1)', + 'rgba(89, 127, 255, 1)', + ], +}; + +export const disabledColor = 'rgba(232, 233, 235, 1)'; diff --git a/ui/components/atoms/loader/index.ts b/ui/components/atoms/loader/index.ts new file mode 100644 index 0000000..9a5222d --- /dev/null +++ b/ui/components/atoms/loader/index.ts @@ -0,0 +1,2 @@ +export { default as Loader } from './loader'; +export { LoaderVariant } from './loader'; diff --git a/ui/components/atoms/loader/loader.gif b/ui/components/atoms/loader/loader.gif new file mode 100644 index 0000000..4ae031d Binary files /dev/null and b/ui/components/atoms/loader/loader.gif differ diff --git a/ui/components/atoms/loader/loader.tsx b/ui/components/atoms/loader/loader.tsx new file mode 100644 index 0000000..882dd80 --- /dev/null +++ b/ui/components/atoms/loader/loader.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { colorConfig, disabledColor } from './constants'; +import loaderIcon from './loader.gif'; +import Image from 'next/image'; + +export enum LoaderVariant { + CIRCLE = 'circle', + DOTS = 'dots', +} + +type LoaderProps = { + label?: string; + disabled?: boolean; + dotCount?: number; + radius?: number; + gap?: number; + duration?: number; + variant?: LoaderVariant; +}; + +const Loader = ({ + label, + disabled, + dotCount = 5, + radius = 3, + gap = 8, + duration = 2000, + variant = LoaderVariant.DOTS, +}: LoaderProps): JSX.Element => { + const r = radius; + const g = gap; + const colors = colorConfig.blue; + + const getCX = (r, g, i) => r + (r * 2 + g) * i; + const width = getCX(r, g, dotCount - 1) + r; + + const getCircles = (count) => { + const arr = []; + + for (let i = 0; i < count; i++) { + let values = []; + for (let index = 0; index < count * 2 - 1; index++) { + values.push(index < i || index > count + i - 1 ? 0 : 1); + } + values = [0, ...values, 0]; + arr.push( + + {!disabled && ( + + )} + + ); + } + return arr; + }; + + if (variant === LoaderVariant.CIRCLE) { + return ...loading; + } + + return ( + + {getCircles(dotCount)} + + ); +}; + +export default Loader; diff --git a/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx b/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx new file mode 100644 index 0000000..72b262e --- /dev/null +++ b/ui/components/atoms/tableErrorMessage/TableErrorMessage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import noDataIcon from './img/tablePlaceholder.svg'; + +import styles from './index.module.css'; +import classNames from 'classnames'; +import Image from 'next/image'; + +const TableErrorMessage = () => { + return ( +
+ +

There is no data yet

+
+ ); +}; +export default TableErrorMessage; diff --git a/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg b/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg new file mode 100644 index 0000000..81d8136 --- /dev/null +++ b/ui/components/atoms/tableErrorMessage/img/tablePlaceholder.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ui/components/atoms/tableErrorMessage/index.module.css b/ui/components/atoms/tableErrorMessage/index.module.css new file mode 100644 index 0000000..087409c --- /dev/null +++ b/ui/components/atoms/tableErrorMessage/index.module.css @@ -0,0 +1,52 @@ +.errorTitle { + margin: 16px 0 0px; + font-size: 16px; +} + +.errorTitleWithBtn { + margin: 16px 0 8px; +} + +.descriptionError { + margin-bottom: 20px; + color: rgba(0, 0, 0, 0.4); + font-size: 14px; +} + +.errorScreen { + padding: 48px 0 32px; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.button { + margin-bottom: 16px; + font-size: 14px; + color: #597fff; + background-color: #eef1ff; + border: 1px solid #597fff; + border-radius: 7px; + padding: 10px 12px; + display: flex; + align-items: center; +} + +.button:last-child { + margin: 0; +} + +.button:hover { + background: #e8ecfa; + cursor: pointer; +} + +.button:active { + background: #d7dffa; +} + +.button svg { + margin-right: 4px; + fill: #597fff; +} diff --git a/ui/components/atoms/tableErrorMessage/index.ts b/ui/components/atoms/tableErrorMessage/index.ts new file mode 100644 index 0000000..1cbd085 --- /dev/null +++ b/ui/components/atoms/tableErrorMessage/index.ts @@ -0,0 +1 @@ +export { default as TableErrorMessage } from './TableErrorMessage'; diff --git a/ui/components/molecules/selectPlate/SelectPlate.module.css b/ui/components/molecules/selectPlate/SelectPlate.module.css new file mode 100644 index 0000000..f0708fa --- /dev/null +++ b/ui/components/molecules/selectPlate/SelectPlate.module.css @@ -0,0 +1,81 @@ +.selectPlate { + width: max-content; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + padding: 4px 12px; + user-select: none; + background-color: #f0f1f5; + border: 2px solid transparent; + border-radius: 7px; + transition: all ease 200ms; + } + + .selectPlate * { + transition: all ease 200ms; + } + + .selectPlate > * { + margin-right: 6px; + } + + .selectPlate > *:last-child { + margin-right: 0; + } + + .selectTitle { + font-weight: 600; + color: rgba(0, 0, 0, 0.8); + line-height: 24px; + text-transform: capitalize; + white-space: nowrap; + } + + .selectCount { + font-weight: 500; + color: rgba(0, 0, 0, 0.8); + line-height: 16px; + text-align: center; + padding: 2px 8px; + border-radius: 30px; + background-color: #fff; + } + + .selectClear { + display: flex; + align-items: center; + transition: all ease 200ms; + } + + .selectClear:hover { + transform: scale(1.2); + } + + .arrow { + fill: rgba(0, 0, 0, 0.8); + transform: rotateX(180deg); + } + + .arrowDisabled { + fill: rgba(0, 0, 0, 0.4); + } + + .expanded { + border: 2px solid #ebebf0; + } + + .active { + border: 2px solid #e3e8fd; + background-color: #eef1ff; + } + + .active span { + color: #3e66ec; + } + + .active svg { + fill: #3e66ec; + } + + \ No newline at end of file diff --git a/ui/components/molecules/selectPlate/img/Arrow.svg b/ui/components/molecules/selectPlate/img/Arrow.svg new file mode 100644 index 0000000..4f0226c --- /dev/null +++ b/ui/components/molecules/selectPlate/img/Arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/molecules/selectPlate/index.tsx b/ui/components/molecules/selectPlate/index.tsx new file mode 100644 index 0000000..0edc676 --- /dev/null +++ b/ui/components/molecules/selectPlate/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import style from './SelectPlate.module.css'; +import ArrowIcon from './img/Arrow.svg'; +import classNames from 'classnames'; +import Image from 'next/image'; + +type SelectPlateProps = { + onClick: () => void; + expanded: boolean; + title: string; + disable: boolean; +}; + +const SelectPlate = ({ onClick, expanded, title, disable }: SelectPlateProps): JSX.Element => { + return ( +
+ {title} + +
+ ); +}; + +export default SelectPlate; diff --git a/ui/components/molecules/singleSelect/SingleSelect.module.css b/ui/components/molecules/singleSelect/SingleSelect.module.css new file mode 100644 index 0000000..fb7427f --- /dev/null +++ b/ui/components/molecules/singleSelect/SingleSelect.module.css @@ -0,0 +1,19 @@ +.singleSelect { + position: relative; +} + +.dropdown { + padding: 0; + border-radius: 7px; + width: 100%; +} + +.item { + padding: 6px 12px; + transition: all ease 200ms; + cursor: pointer; +} + +.item:hover { + background-color: var(--col--gray3); +} diff --git a/ui/components/molecules/singleSelect/index.tsx b/ui/components/molecules/singleSelect/index.tsx new file mode 100644 index 0000000..e4acc98 --- /dev/null +++ b/ui/components/molecules/singleSelect/index.tsx @@ -0,0 +1 @@ +export { default as SingleSelect } from './singleSelect'; diff --git a/ui/components/molecules/singleSelect/singleSelect.tsx b/ui/components/molecules/singleSelect/singleSelect.tsx new file mode 100644 index 0000000..d915629 --- /dev/null +++ b/ui/components/molecules/singleSelect/singleSelect.tsx @@ -0,0 +1,81 @@ +import React, { useEffect, useRef, useState } from 'react'; +import style from './SingleSelect.module.css'; +import DropdownWrapper from '../dropdownWrapper/index'; +import classNames from 'classnames'; +import SelectPlate from '../selectPlate'; +import CustomScrollList from '../customScrollList'; +import { LimitOptions } from '../../../comman/types'; +import { useMedia } from '../../../hooks/useMedia'; +import { useHandleClickOutside } from '../../../hooks/useHandleClickOutside'; + +type SingleSelectProps = { + options: LimitOptions; + selectTitle?: string; + onChange: (value: any) => void; + initValue?: number; + disable?: boolean; + className?: string; +}; + +const SingleSelect = ({ + options, + selectTitle = '', + onChange, + initValue, + disable, + className, +}: SingleSelectProps): JSX.Element => { + const [selected, setSelected] = useState(initValue); + const [expanded, setExpanded] = useState(false); + const ref = useRef(); + const media = useMedia(0); + + const findTextByValue = (val) => { + if (!options || (!val && val !== 0)) return null; + const res = options?.filter((elem) => Object.values(elem).indexOf(val) > -1); + if (res.length > 0) return res[0].text; + return null; + }; + + useEffect(() => { + setSelected(initValue); + }, [initValue]); + + const clickHandler = (data) => { + if (!disable) { + setSelected(data); + onChange(data); + setExpanded(false); + } + }; + + useHandleClickOutside(ref.current, () => (media.greater.sm ? setExpanded(false) : null)); + + return ( +
+ setExpanded(!expanded)} + expanded={expanded} + title={findTextByValue(selected) || selectTitle} + disable={disable} + /> + setExpanded(false)} show={expanded}> + + {options?.map((el) => ( +
{ + clickHandler(el.value); + }} + > + {el.text} +
+ ))} +
+
+
+ ); +}; + +export default SingleSelect; diff --git a/ui/components/organisms/accountConent/accountContent.tsx b/ui/components/organisms/accountConent/accountContent.tsx index c530f27..fb3e98f 100644 --- a/ui/components/organisms/accountConent/accountContent.tsx +++ b/ui/components/organisms/accountConent/accountContent.tsx @@ -1,14 +1,82 @@ +import { ORDER_BY, SORT_BY } from "../../../comman/types"; +import { useTable } from "../../../hooks/useTable"; import { ConnectWalletButton } from "../../molecules/connectWalletButton"; +import { Table } from "../table"; +import { ScoringConfig } from "./constants"; +import iconMock from "./img/iconMock.svg"; import style from "./index.module.css"; const AccountContent = () => { + const mockData = { + data: [ + { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 }, + { id: "mina.mina", url: iconMock, time: 1707757181674 }, + { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 }, + { id: "mina.mina", url: iconMock, time: 1707757181674 }, + { id: "Evgeniy-dev.mina", url: iconMock, time: 1707757181674 }, + { id: "Vitality.mina", url: iconMock, time: 1707757181674 }, + { id: "@@@@@@@.mina", url: iconMock, time: 1707757181674 }, + ], + size: 100, + totalPages: 1, + pageable: { + sort: { + sorted: true, + unsorted: false, + empty: false, + }, + offset: 0, + pageNumber: 0, + pageSize: 100, + paged: true, + unpaged: false, + }, + last: false, + totalElements: 2, + number: 0, + sort: { + sorted: true, + unsorted: false, + empty: false, + }, + first: true, + numberOfElements: 100, + empty: false, + }; return (
My Names
-
+ { + console.log(data); + }} + onChangeLimit={(data) => { + console.log(data); + }} + onChangeSort={(data) => { + console.log(data); + }} + onChangeOrder={(data) => { + console.log(data); + }} + /> ); }; diff --git a/ui/components/organisms/accountConent/constants.ts b/ui/components/organisms/accountConent/constants.ts new file mode 100644 index 0000000..7c27127 --- /dev/null +++ b/ui/components/organisms/accountConent/constants.ts @@ -0,0 +1,31 @@ +import { SORT_BY } from "../../../comman/types"; +import { TableTemplates } from "../table/templates"; + +export const ScoringConfig = [ + { + colName: "names", + headerText: "Registered Names", + columnTemplate: TableTemplates.NAMES, + fields: { + value: "id", + url: "url", + }, + }, + { + colName: "button", + columnTemplate: TableTemplates.BUTTON, + headerText: "", + fields: { + value: "Manage", + }, + }, + { + colName: "Created", + columnTemplate: TableTemplates.TIME, + headerText: "Created", + fields: { + value: "time", + }, + sortBy: SORT_BY.SCORE, + }, +]; diff --git a/ui/components/organisms/accountConent/img/iconMock.svg b/ui/components/organisms/accountConent/img/iconMock.svg new file mode 100644 index 0000000..1dc8b95 --- /dev/null +++ b/ui/components/organisms/accountConent/img/iconMock.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/components/organisms/accountConent/index.module.css b/ui/components/organisms/accountConent/index.module.css index d6f8e1d..cdd07c3 100644 --- a/ui/components/organisms/accountConent/index.module.css +++ b/ui/components/organisms/accountConent/index.module.css @@ -2,12 +2,13 @@ display: flex; flex-direction: column; width: 100%; + padding: 0 40px; } .header { display: flex; justify-content: space-between; - padding: 16px 0 16px 16px; + padding: 16px 0 16px 0px; } .header > div:first-child { diff --git a/ui/components/organisms/navigation/navigation.tsx b/ui/components/organisms/navigation/navigation.tsx index 710c912..1a0a04b 100644 --- a/ui/components/organisms/navigation/navigation.tsx +++ b/ui/components/organisms/navigation/navigation.tsx @@ -23,8 +23,12 @@ const Navigation = () => { [style.active]: pathname.includes(url), })} > - + {title} ); diff --git a/ui/components/organisms/pager/Pager.module.css b/ui/components/organisms/pager/Pager.module.css new file mode 100644 index 0000000..6891a1b --- /dev/null +++ b/ui/components/organisms/pager/Pager.module.css @@ -0,0 +1,37 @@ +.pager { + display: flex; +} +.btn { + outline: none !important; + border: none; + background-color: transparent; + min-width: 32px; + height: 32px; + padding: 0 4px; + margin-right: 4px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 7px; + transition: all ease 200ms; + line-height: 16px; + user-select: none; + color: rgba(0, 0, 0, 0.8); + cursor: pointer; +} + +.btn:hover { + background-color: #f0f1f5; +} +.btn:last-child { + margin-right: 0; +} +.ellipsis { + cursor: default; +} +.ellipsis:hover { + background-color: transparent; +} +.active { + background-color: #f0f1f5; +} diff --git a/ui/components/organisms/pager/img/ArrowIcon.svg b/ui/components/organisms/pager/img/ArrowIcon.svg new file mode 100644 index 0000000..60324ba --- /dev/null +++ b/ui/components/organisms/pager/img/ArrowIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/organisms/pager/index.tsx b/ui/components/organisms/pager/index.tsx new file mode 100644 index 0000000..04e1177 --- /dev/null +++ b/ui/components/organisms/pager/index.tsx @@ -0,0 +1,124 @@ +import classNames from 'classnames'; +import React from 'react'; +import style from './Pager.module.css'; +import ArrowIcon from './img/ArrowIcon.svg'; +import Image from 'next/image'; +import { useMedia } from '../../../hooks/useMedia'; + +type PagerProps = { + page: number; + count?: number; + onChange: (value: number) => void; + pageNeighbours?: number; +}; + +const Pager = ({ page, count = 1, onChange, pageNeighbours = 1 }: PagerProps): JSX.Element => { + const showPrevButton = page !== 1; + const showNextButton = count !== page; + const media = useMedia(); + + const ELLIPSIS = '...'; + + const range = (from, to, step = 1) => { + let i = from; + const range = []; + + while (i <= to) { + range.push(i); + i += step; + } + + return range; + }; + + const fetchPageNumbers = () => { + const totalNumbers = pageNeighbours * 2 + 3; + const totalBlocks = totalNumbers + 2; + + if (count > totalBlocks) { + const startPage = Math.max(2, page - pageNeighbours); + const endPage = Math.min(count - 1, page + pageNeighbours); + let pages = range(startPage, endPage); + const hasLeftSpill = startPage > 2; + const hasRightSpill = count - endPage > 1; + const spillOffset = totalNumbers - (pages.length + 1); + switch (true) { + case hasLeftSpill && !hasRightSpill: { + const extraPages = + (page + 1 + '').length > 3 && !media.greater.sm + ? range(startPage - spillOffset + 2, startPage - 1) + : range(startPage - spillOffset, startPage - 1); + pages = [ELLIPSIS, ...extraPages, ...pages]; + break; + } + case !hasLeftSpill && hasRightSpill: { + const extraPages = range(endPage + 1, endPage + spillOffset); + pages = [...pages, ...extraPages, ELLIPSIS]; + break; + } + + case hasLeftSpill && hasRightSpill: + default: { + (page + 1 + '').length > 3 && !media.greater.sm + ? (pages = [ELLIPSIS, pages[1], ELLIPSIS]) + : (pages = [ELLIPSIS, ...pages, ELLIPSIS]); + break; + } + } + + return [1, ...pages, count]; + } + + return range(1, count); + }; + + const changeHandler = (value) => { + if (value > count || value < 1) return; + onChange(value - 1); + }; + + return ( +
+ {showPrevButton && ( + + )} + + {fetchPageNumbers().map((el, i) => { + if (el === ELLIPSIS) + return ( + + ); + + return ( + + ); + })} + + {showNextButton && ( + + )} +
+ ); +}; + +export default Pager; diff --git a/ui/components/organisms/pagination/index.module.css b/ui/components/organisms/pagination/index.module.css new file mode 100644 index 0000000..a5c497c --- /dev/null +++ b/ui/components/organisms/pagination/index.module.css @@ -0,0 +1,76 @@ +.pagination { + background-color: #fff; + position: sticky; + top: 0; + z-index: 12; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 0 16px 0; + user-select: none; +} + +.showing { + line-height: 32px; + flex-basis: 25%; + font-size: 14px; + color: rgba(0, 0, 0, 0.8); +} + +.showingMobile { + flex-basis: auto; +} + +.select { + flex-basis: 25%; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.select span { + margin-right: 8px; + font-size: 14px; + color: rgba(0, 0, 0, 0.8); +} + +.totalCount { + margin-bottom: 4px; + line-height: 18px; +} + +.showingLast { + color: #9c9ea6; + font-size: 12px; +} + +@media (max-width: 992px) { + .showingHiddenOnMobile { + display: none; + } +} + +@media (max-width: 768px) { + .pagination { + justify-content: center; + } + .MobileWithShowing { + flex-direction: column-reverse; + align-items: flex-start; + gap: 10px; + padding-right: 16px; + } + + .select span { + display: none; + } +} + +@media (max-width: 576px) { + .pagination { + padding: 12px 0; + justify-content: flex-start; + padding-left: 16px; + } +} diff --git a/ui/components/organisms/pagination/index.ts b/ui/components/organisms/pagination/index.ts new file mode 100644 index 0000000..8bdff8c --- /dev/null +++ b/ui/components/organisms/pagination/index.ts @@ -0,0 +1 @@ +export { default as Table } from './pagination'; diff --git a/ui/components/organisms/pagination/pagination.tsx b/ui/components/organisms/pagination/pagination.tsx new file mode 100644 index 0000000..743c738 --- /dev/null +++ b/ui/components/organisms/pagination/pagination.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import style from './index.module.css'; +import { LimitOptions } from '../../../comman/types'; +import Pager from '../pager'; +import { SingleSelect } from '../../molecules/singleSelect'; + +type PaginationProps = { + currentPage: number; + pagesCount: number; + pageLimit: number; + totalElements: number; + onChangePage: (value: number | string) => void; + onChangeLimit: (value: number) => void; + isLoading: boolean; + limitOptions?: LimitOptions; +}; + +const Pagination = ({ + currentPage, + pageLimit, + totalElements, + pagesCount, + onChangePage, + limitOptions, + onChangeLimit, + isLoading, +}: PaginationProps): JSX.Element => { + const nextOffset = pageLimit * (currentPage + 1); + const offset = pageLimit * currentPage; + + return ( +
+
+

+ Showing {offset + 1} - {nextOffset > totalElements ? totalElements : nextOffset} out of{' '} + {totalElements ?? '...'} +

+
+
+ onChangePage(page)} /> +
+
+ Show + +
+
+ ); +}; + +export default Pagination; diff --git a/ui/components/organisms/table/errorMessage.tsx b/ui/components/organisms/table/errorMessage.tsx new file mode 100644 index 0000000..58bfce5 --- /dev/null +++ b/ui/components/organisms/table/errorMessage.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import ClearIcon from './img/clear.svg'; + +import styles from './Table.module.css'; +import Image from 'next/image'; + +const ErrorMessage = ({ errorText, onClearStr }) => { + return ( +
+

{errorText ?? 'There is no data yet'}

+ {onClearStr && ( + + )} +
+ ); +}; +export default ErrorMessage; diff --git a/ui/components/organisms/table/img/ChevronIcon.svg b/ui/components/organisms/table/img/ChevronIcon.svg new file mode 100644 index 0000000..97e4524 --- /dev/null +++ b/ui/components/organisms/table/img/ChevronIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/organisms/table/img/SortIcon.svg b/ui/components/organisms/table/img/SortIcon.svg new file mode 100644 index 0000000..5491805 --- /dev/null +++ b/ui/components/organisms/table/img/SortIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/organisms/table/img/clear.svg b/ui/components/organisms/table/img/clear.svg new file mode 100644 index 0000000..65723bc --- /dev/null +++ b/ui/components/organisms/table/img/clear.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/components/organisms/table/index.module.css b/ui/components/organisms/table/index.module.css new file mode 100644 index 0000000..2dbd884 --- /dev/null +++ b/ui/components/organisms/table/index.module.css @@ -0,0 +1,59 @@ +.wrapper { + overflow: hidden; + overflow-x: scroll; +} + +.wrapper::-webkit-scrollbar { + display: none; +} + +.table { + overflow: hidden; + overflow-x: scroll; + width: 100%; + border-collapse: collapse; +} + +.table tbody tr:hover { + background-color: rgb(250, 250, 252); + transition: all 0.3s; + min-width: 100px; +} + +.table thead { + font-weight: bold; + border-top: 0.5px solid var(--col--gray8); + padding: 10px 0; +} + +.table thead th { + text-align: start; + white-space: nowrap; + font-size: 12px; +} + +.table th, +.table td { + padding: 15px; + text-align: left; + border-bottom: 0.5px solid var(--col--gray8); +} + +.loadingScreen { + width: 100%; + display: flex; + justify-content: center; +} + +.headerCellHover { + cursor: pointer; +} + +.sortIcon { + margin-bottom: -4px; +} + +.timeTemplate { + display: flex; + flex-direction: column; +} \ No newline at end of file diff --git a/ui/components/organisms/table/index.ts b/ui/components/organisms/table/index.ts new file mode 100644 index 0000000..1a876cf --- /dev/null +++ b/ui/components/organisms/table/index.ts @@ -0,0 +1 @@ +export { default as Table } from './table'; diff --git a/ui/components/organisms/table/table.tsx b/ui/components/organisms/table/table.tsx new file mode 100644 index 0000000..b4dc08d --- /dev/null +++ b/ui/components/organisms/table/table.tsx @@ -0,0 +1,109 @@ +import Image from 'next/image'; +import style from './index.module.css'; +import getCell from './templates'; + +import sortIcon from './img/SortIcon.svg'; +import classNames from 'classnames'; +import { ORDER_BY } from '../../../comman/types'; +import { TableErrorMessage } from '../../atoms/tableErrorMessage'; +import Pagination from '../pagination/pagination'; +import { Loader, LoaderVariant } from '../../atoms/loader'; + +const Table = ({ + data, + config: configs, + isLoading, + currentPage, + pageLimit, + totalElements, + pagesCount, + sortBy, + orderBy, + onChangePage, + limitOptions, + onChangeLimit, + onChangeSort, + onChangeOrder, +}) => { + const handleSort = (sort?: string): void => { + if (!sort) return; + if (sort === sortBy) { + return onChangeOrder(orderBy === ORDER_BY.DESC ? ORDER_BY.ASC : ORDER_BY.DESC); + } + onChangeSort(sort); + onChangeOrder(undefined); + }; + const showErrorMessage = !isLoading && (!data || data?.data?.length < 1 ); + + const renderPagination = () => { + return ( + + ); + }; + + return ( + <> + {renderPagination()} +
+
+ + + {configs.map(({ headerText, sortBy: configSortBy }) => ( + + ))} + + + {!isLoading && !showErrorMessage && ( + + {data?.data?.map((item, index) => { + return ( + + {configs.map((config, index) => { + return ; + })} + + ); + })} + + )} +
handleSort(configSortBy)} + className={classNames('t-inter-semi-bold', { + [style.headerCellHover]: configSortBy, + })} + > + {headerText} + {sortBy === configSortBy && ( + sort + )} +
{getCell({ data: item, config })}
+
+ {showErrorMessage && } + + {isLoading ? ( +
+ +
+ ) : ( + <> {renderPagination()} + )} + + ); +}; + +export default Table; diff --git a/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx b/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx new file mode 100644 index 0000000..0760743 --- /dev/null +++ b/ui/components/organisms/table/templates/accountTemplate/accountTemplate.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import style from '../index.module.css'; +import AccountName from '../../../../organisms/accountName'; + +const AccountTemplate = ({ data, config }) => { + const pk = data[config.fields?.pk]; + const img = data[config.fields.img]; + const name = data[config.fields.name]; + const view = config.view; + const isPkArray = Array.isArray(pk); + const newImg = isPkArray ? pk[0]?.[config?.fields?.nestedImg] : img; + const newName = isPkArray ? pk[0]?.[config?.fields?.nestedName] : name; + const noRedirectFromValue = config.fields.noRedirectFromValue; + const noRedirectFromData = data[config.fields.noRedirectFromData]; + const redirect = config.fields.getRedirectFromData + ? config.fields.getRedirectFromData(data) + : config.fields.redirect || 'account'; + const noRedirect = noRedirectFromData ?? (noRedirectFromValue ? noRedirectFromValue(pk) : config.fields.noRedirect); + + return ( +
+ config.fields.getRedirectLink(pk, data[config.fields.isValidator]) + : null + } + noRedirect={noRedirect} + /> +
+ ); +}; + +export default AccountTemplate; diff --git a/ui/components/organisms/table/templates/amountTemplate/index.module.css b/ui/components/organisms/table/templates/amountTemplate/index.module.css new file mode 100644 index 0000000..f48af85 --- /dev/null +++ b/ui/components/organisms/table/templates/amountTemplate/index.module.css @@ -0,0 +1,7 @@ +.addionValue { + font-size: 10px; + color: var(--col--gray4); + line-height: 16px; + text-transform: uppercase; + margin-left: 5px; +} diff --git a/ui/components/organisms/table/templates/amountTemplate/index.tsx b/ui/components/organisms/table/templates/amountTemplate/index.tsx new file mode 100644 index 0000000..0c50ef2 --- /dev/null +++ b/ui/components/organisms/table/templates/amountTemplate/index.tsx @@ -0,0 +1,29 @@ +import classNames from 'classnames'; +import styles from './index.module.css'; +import { formatNum } from '../../../../../comman/helpers'; + +type AmountTemplateProps = { + data: any; + config: { + fields: { + value: string; + additionValue?: string; + }; + }; +}; + +const AmountTemplate = ({ data, config }: AmountTemplateProps): JSX.Element => { + const value = data[config.fields.value]; + const additionValue = data[config.fields.additionValue] || config.fields.additionValue; + + return ( +
+ <> + {formatNum(value, 2, true, true)} + {additionValue && {additionValue}} + +
+ ); +}; + +export default AmountTemplate; diff --git a/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx b/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx new file mode 100644 index 0000000..0f889d7 --- /dev/null +++ b/ui/components/organisms/table/templates/buttonTemplate/buttonTemplate.tsx @@ -0,0 +1,18 @@ +import { Button } from "../../../../atoms/button"; +import { Variant } from "../../../../atoms/button/types"; + +type ButtonTemplateProps = { + data: any; + config: { + fields: { + value: string; + url: string; + }; + }; +}; +const ButtonTemplate = ({ data, config }: ButtonTemplateProps) => { + const buttonText = config.fields.value; + return