Skip to content

Commit

Permalink
Merge pull request #6 from SantiagoJavierRubio/client-styles
Browse files Browse the repository at this point in the history
Add styling to client
  • Loading branch information
SantiagoJavierRubio authored Dec 1, 2023
2 parents 01b5926 + 619355d commit 73bee10
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 54 deletions.
5 changes: 4 additions & 1 deletion apps/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
"@tanstack/react-query": "^5.9.0",
"@tanstack/react-table": "^8.10.7",
"axios": "^1.6.2",
"lucide-react": "^0.294.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-tooltip": "^5.24.0",
"tailwind-merge": "^2.0.0"
},
"devDependencies": {
"@types/react": "^18.2.37",
Expand Down
7 changes: 7 additions & 0 deletions apps/client/src/common/Container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { PropsWithChildren } from 'react'

export default function Container({ ...props }: PropsWithChildren) {
return (
<section className="max-h-screen h-screen max-w-6xl w-full overflow-y-auto p-4 no-scrollbar">{props.children}</section>
)
}
7 changes: 7 additions & 0 deletions apps/client/src/common/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Loader2 } from 'lucide-react'

export default function Loader() {
return (
<div className='w-full h-full flex items-center justify-center'><Loader2 className='animate-spin' size={32}/></div>
)
}
27 changes: 14 additions & 13 deletions apps/client/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@

:root {
color-scheme: light dark;
background-color: #0a0c10;
background-color: #010409;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
box-sizing: border-box;
}

/*
@layer utilities {
@variants responsive {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}

@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
} */
}

55 changes: 55 additions & 0 deletions apps/client/src/pages/Home/CommitTable/Cells.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Eye } from 'lucide-react'
import type { PropsWithChildren, HTMLAttributes } from 'react'
import { Tooltip } from 'react-tooltip'
import { twMerge } from 'tailwind-merge'

export function Cell({ children, className, ...props }: PropsWithChildren<HTMLAttributes<HTMLDivElement>>) {
return (
<div className={twMerge('py-5 px-2', className)} {...props}>{children}</div>
)
}

type AvatarCellProps = {
url: string
alt: string
profile_url: string
} & PropsWithChildren<HTMLAttributes<HTMLDivElement>>
export function AvatarCell({ url, alt, profile_url, ...props }: AvatarCellProps) {
return (
<>
<Cell className='p-0 aspect-square h-8 cursor-pointer group' {...props}>
<a href={profile_url} target='_blank' referrerPolicy='no-referrer' rel="noreferrer">
<img
data-tooltip-id='profile_link'
data-tooltip-content='Go to profile'
src={url}
alt={alt}
className=' object-fill rounded-full group-hover:scale-110 transition-all'
/>
</a>
</Cell>
<Tooltip id='profile_link' />
</>
)
}

type LinkCellProps = {
url: string
} & PropsWithChildren<HTMLAttributes<HTMLDivElement>>
export function LinkCell({ url, ...props }: LinkCellProps) {
return (
<>
<Cell className='cursor-pointer group' {...props}>
<a href={url} target='_blank' referrerPolicy='no-referrer' rel="noreferrer">
<Eye
data-tooltip-id='gh_link'
data-tooltip-content='Inspect on Github'
className='group-hover:stroke-[2.5] group-hover:scale-105 transition-all
stroke-textDark group-hover:stroke-textLight outline-none'
/>
</a>
</Cell>
<Tooltip id='gh_link' className='text-red-500' />
</>
)
}
179 changes: 141 additions & 38 deletions apps/client/src/pages/Home/CommitTable/CommitTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/* eslint-disable no-console */
import { ChangeEvent, useState } from 'react'
import { CommitListElement } from 'types'
import {
createColumnHelper,
useReactTable,
Expand All @@ -11,22 +9,83 @@ import {
getFilteredRowModel,
type SortingState
} from '@tanstack/react-table'
import { ArrowBigLeft, ArrowBigRight, Search } from 'lucide-react'
import type { CommitListElement } from 'types'

import useDebounce from '@/hooks/useDebounce'
import { AvatarCell, Cell, LinkCell } from './Cells'
import { SortableHeader, Header } from './Headers'

const columnHelper = createColumnHelper<CommitListElement>()
const columns = [
columnHelper.accessor('sha', { header: () => <span>Id</span>, cell: props => <span>{props.getValue().substring(0, 7)}</span> }),
columnHelper.accessor('sha', {
header: props => (
<SortableHeader
sortingType='alphabetical'
sortDirection={props.column.getIsSorted()}>
ID
</SortableHeader>
),
cell: props => <Cell id={props.getValue()} className='text-center'>{props.getValue().substring(0, 7)}</Cell>
}),
columnHelper.group({
id: 'author',
columns: [
columnHelper.display({ id: 'avatar', cell: props => <img src={props.row.original.author.avatar_url} alt={`${props.row.original.commit.author.name}'s avatar`} className='aspect-square rounded-full w-6'/> }),
columnHelper.accessor('commit.author.name', { id: 'name', header: () => <span>Author</span>, cell: props => <span>{props.getValue()}</span> }),
columnHelper.accessor('commit.author.email', { id: 'email', header: () => null, cell: () => null })
columnHelper.display({
id: 'avatar',
header: () => null,
cell: props => (
<AvatarCell
url={props.row.original.author.avatar_url}
alt={`${props.row.original.commit.author.name}'s avatar`}
profile_url={props.row.original.author.html_url}
/>)
}),
columnHelper.accessor('commit.author.name', {
id: 'name',
header: props => (
<SortableHeader
sortingType='alphabetical'
sortDirection={props.column.getIsSorted()}
className='text-left pr-16'>
Author
</SortableHeader>),
cell: props => <Cell className='whitespace-nowrap'>{props.getValue()}</Cell>
}),
columnHelper.accessor('commit.author.email',
{ id: 'email', header: () => null, cell: () => null })
]
}),
columnHelper.accessor('commit.message', { id: 'message', enableSorting: false, header: () => <span>Message</span>, cell: props => <span>{props.row.original.commit.message}</span> }),
columnHelper.accessor('commit.author.date', { id: 'date', header: () => <span>Date</span>, cell: props => <span>{new Date(props.getValue()).toLocaleDateString()}</span> })
columnHelper.accessor('commit.message', {
id: 'message',
enableSorting: false,
header: () => <Header>Message</Header>,
cell: props => (
<Cell className='text-sm italic whitespace-pre-wrap leading-3'>
{props.row.original.commit.message}
</Cell>
)
}),
columnHelper.accessor('commit.author.date', {
id: 'date',
header: props => (
<SortableHeader
sortingType='date'
sortDirection={props.column.getIsSorted()}
className='pr-4'>
Date
</SortableHeader>
),
cell: props => (
<Cell className='text-sm text-center'>
{new Date(props.getValue()).toLocaleDateString()}
</Cell>
)
}),
columnHelper.display({
id: 'link',
cell: props => <LinkCell url={props.row.original.html_url} />
})
]
export default function CommitTable({ commits }: { commits: CommitListElement[] }) {
const [sorting, setSorting] = useState<SortingState>([])
Expand Down Expand Up @@ -55,45 +114,89 @@ export default function CommitTable({ commits }: { commits: CommitListElement[]

return (
<>
<input type="text" name="filter" onChange={handleSortInput} value={filtering} />
<p>{table.getState().pagination.pageIndex + 1} of {table.getPageCount()}</p>
<table>
<thead>
{table.getHeaderGroups().map(hGroup => (
<tr key={hGroup.id}>
{hGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<div className={header.column.getCanSort() ? 'cursor-pointer select-none' : 'cursor-default'} onClick={header.column.getToggleSortingHandler()}>
{flexRender(header.column.columnDef.header, header.getContext())}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<div className='flex items-center justify-between my-4 pr-2' id='actions-top'>
<label
htmlFor='filter'
className='flex items-center justify-start gap-2 bg-bgLight w-fit focus-within:ring-2 ring-textLight px-2 py-1 rounded-lg'
>
<Search size={20} strokeWidth={3}/>
<input
type="text"
id="filter"
name="filter"
onChange={handleSortInput}
value={filtering}
className='bg-transparent outline-none text-textLight'
/>
</label>
<p>
Page {table.getState().pagination.pageIndex + table.getPageCount() > 0 ? 1 : 0} of {table.getPageCount()}
</p>
</div>
<table
className='ring-textLight ring-2 bg-bgLight rounded-lg w-full overflow-hidden text-textLight'
id='commits-table'
>
<thead>
{table.getHeaderGroups().map(hGroup => (
<tr key={hGroup.id}>
{hGroup.headers.map(header => (
<th key={header.id}>
{header.isPlaceholder ? null : (
<div
className={header.column.getCanSort() ? 'cursor-pointer select-none' : 'cursor-default'}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(header.column.columnDef.header, header.getContext())}
</div>
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr key={row.id} onClick={() => console.log(row.getValue('sha'))} >
{table.getRowModel().rows.length > 0 ? table.getRowModel().rows.map(row => (
<tr
key={row.id}
className="bg-bgDark border-y-2 border-textLight hover:bg-bgMedium/70"
>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
)) : (
<td colSpan={20}>
<Cell className='bg-bgDark text-center py-8 text-xl'>No results</Cell>
</td>
)}
</tbody>
</table>
<div>
<button disabled={!table.getCanPreviousPage()} onClick={() => table.previousPage()}>previous</button>
<button disabled={!table.getCanNextPage()} onClick={() => table.nextPage()}>next</button>
<label htmlFor='pageSize'>Size</label>
<select id="pageSize" name="page-size" onChange={(e) => table.setPageSize(parseInt(e.target.value))}>
<option defaultChecked value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
<div className='mt-4 w-full flex items-center justify-end gap-8 pr-2' id='actions-bottom'>
<label htmlFor='pageSize' className='flex items-center gap-4'>
<p>Show</p>
<select id="pageSize" name="page-size" onChange={(e) => table.setPageSize(parseInt(e.target.value))}>
<option defaultChecked value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</label>
<div id="page-navigation" className='flex items-center gap-6'>
<button
className='flex items-center gap-1 p-1 bg-bgLight disabled:opacity-25 rounded-lg'
disabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}>
<ArrowBigLeft size={24} />
</button>
<button
className='flex items-center gap-1 p-1 bg-bgLight disabled:opacity-25 rounded-lg'
disabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}>
<ArrowBigRight size={24} />
</button>
</div>
</div>
</>
)
Expand Down
43 changes: 43 additions & 0 deletions apps/client/src/pages/Home/CommitTable/Headers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { PropsWithChildren, HTMLAttributes } from 'react'
import { twMerge } from 'tailwind-merge'
import { ArrowDown, ArrowDownAZ, ArrowDownZA, ArrowUp, Calendar } from 'lucide-react'
import { SortDirection } from '@tanstack/react-table'

export function Header({ className, children, ...props }: PropsWithChildren<HTMLAttributes<HTMLDivElement>>) {
return (
<div className={twMerge('p-4 bg-bgLight h-10 flex items-center justify-center', className)} {...props}>
{children}
</div>
)
}

type SortableHeaderProps = {
sortingType: 'alphabetical' | 'date'
sortDirection: false | SortDirection
} & PropsWithChildren<HTMLAttributes<HTMLDivElement>>
export function SortableHeader({ sortingType, sortDirection, children, ...props }: SortableHeaderProps) {
return (
<Header {...props}>
{children}
<div className='ml-2 bg-transparent'>
{(sortingType === 'alphabetical') && (sortDirection === 'desc'
? <ArrowDownZA size={20} />
: sortDirection && <ArrowDownAZ size={20} />)}
{sortingType === 'date' && <DateSortIcon direction={sortDirection} /> }
</div>
</Header>
)
}
export function DateSortIcon({ direction }: { direction: SortableHeaderProps['sortDirection']}) {
if (!direction) return null
return (
<div className='relative'>
<Calendar size={20}/>
<div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/4 '>
{direction === 'asc'
? <ArrowUp size={12} strokeWidth={3}/>
: <ArrowDown size={12} strokeWidth={3} />}
</div>
</div>
)
}
Loading

0 comments on commit 73bee10

Please sign in to comment.