Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: column visibility settings #583

Merged
merged 22 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e400655
feat: column visibility settings
DanielSchiavini Jan 6, 2025
2cf326b
Merge branch 'feat/table-filters' into feat/table-settings
DanielSchiavini Jan 6, 2025
c5a529b
fix: self-review and cypress tests
DanielSchiavini Jan 6, 2025
d4dbc61
fix: collateral vs debt
DanielSchiavini Jan 6, 2025
1716c75
Merge branch 'feat/table-filters' into feat/table-settings
DanielSchiavini Jan 7, 2025
caf0c43
Merge branch 'main' into feat/table-settings
DanielSchiavini Jan 7, 2025
f02aaca
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/ta…
DanielSchiavini Jan 8, 2025
28014a2
feat: hide only the graphs, average graph data
DanielSchiavini Jan 8, 2025
1aae2c3
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/ta…
DanielSchiavini Jan 8, 2025
9f28dc2
fix: paper box shadow
DanielSchiavini Jan 8, 2025
901e3f8
feat: implement shadows from design system depending on elevation
DanielSchiavini Jan 8, 2025
18a22a8
fix: leftover review comments #575
DanielSchiavini Jan 8, 2025
bfbcf92
fix: bad imports
DanielSchiavini Jan 8, 2025
4f0ded7
Merge branch 'main' into feat/table-settings
DanielSchiavini Jan 8, 2025
900f755
Merge branch 'main' of github.com:curvefi/curve-frontend into feat/ta…
DanielSchiavini Jan 9, 2025
8f0e35b
fix: background url
DanielSchiavini Jan 9, 2025
fae8631
Merge branch 'main' into feat/table-settings
DanielSchiavini Jan 13, 2025
27978b3
fix: chart toggle test
DanielSchiavini Jan 13, 2025
854c14c
feat: add filters to sticky header, fix switch spacing
DanielSchiavini Jan 14, 2025
ef3b1d8
fix: filter spacing, progress animation
DanielSchiavini Jan 14, 2025
b219f2e
Merge branch 'main' into feat/table-settings
DanielSchiavini Jan 14, 2025
ba17a40
fix: table header height + test
DanielSchiavini Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions apps/loan/src/components/PageLlamaMarkets/LendingMarketsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
import { LendingMarketsFilters } from '@/components/PageLlamaMarkets/LendingMarketsFilters'
import { useSortFromQueryString } from '@ui-kit/hooks/useSortFromQueryString'
import { DeepKeys } from '@tanstack/table-core/build/lib/utils'
import { ColumnVisibilityGroup, useColumnSettings } from '@ui-kit/shared/ui/TableColumnVisibilityPopover'

const { ColumnWidth, Spacing, MinWidth, MaxWidth } = SizesAndSpaces
const { ColumnWidth, Spacing, MaxWidth } = SizesAndSpaces

const columnHelper = createColumnHelper<LendingVault>()

Expand Down Expand Up @@ -59,13 +60,39 @@ const columns = [
meta: { type: 'numeric' },
size: ColumnWidth.sm,
}),
// following columns are used to configure and filter tanstack, but they are displayed together in PoolTitleCell
hidden('blockchainId'),
hidden('assets.collateral.symbol'),
hidden('assets.borrowed.symbol'),
] satisfies ColumnDef<LendingVault, any>[]

const DEFAULT_SORT = [{ id: 'totalSupplied.usdTotal', desc: true }]

const DEFAULT_VISIBILITY: ColumnVisibilityGroup[] = [
{
label: t`Markets`,
columns: [
{ label: t`Available Liquidity`, columnId: 'totalSupplied.usdTotal', active: true },
{ label: t`Utilization`, columnId: 'utilizationPercent', active: true },
],
},
{
label: t`Borrow`,
columns: [
{ label: t`Collateral`, columnId: 'assets.collateral.symbol', active: true },
// { label: t`Credit Limit`, columnId: '???', active: true },
{ label: t`Rate Chart`, columnId: 'rates.borrowApyPcent', active: true },
],
},
{
label: t`Lend`,
columns: [
// { label: t`Available Liquidity`, columnId: 'totalSupplied.usdTotal', active: true },
{ label: t`Rate Chart`, columnId: 'rates.lendApyPcent', active: true },
],
},
]

export const LendingMarketsTable = ({
DanielSchiavini marked this conversation as resolved.
Show resolved Hide resolved
onReload,
data,
Expand All @@ -76,6 +103,8 @@ export const LendingMarketsTable = ({
headerHeight: string
}) => {
const [columnFilters, columnFiltersById, setColumnFilter] = useColumnFilters()
const { columnSettings, columnVisibility, onColumnVisibilityChange, toggleColumnVisibility } =
useColumnSettings(DEFAULT_VISIBILITY)

const [sorting, onSortingChange] = useSortFromQueryString(DEFAULT_SORT)
const table = useReactTable({
Expand All @@ -84,8 +113,9 @@ export const LendingMarketsTable = ({
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
state: { sorting, columnFilters },
state: { sorting, columnVisibility, columnFilters },
onSortingChange,
onColumnVisibilityChange,
maxMultiSortColCount: 3, // allow 3 columns to be sorted at once
})

Expand All @@ -102,10 +132,12 @@ export const LendingMarketsTable = ({
subtitle={t`Select a market to view more details`}
onReload={onReload}
learnMoreUrl="https://docs.curve.fi/lending/overview/"
columnVisibilityGroups={columnSettings}
toggleColumnVisibility={toggleColumnVisibility}
>
<LendingMarketsFilters columnFilters={columnFiltersById} setColumnFilter={setColumnFilter} data={data} />
</TableFilters>
<DataTable table={table} headerHeight={headerHeight} rowHeight={'3xl'} emptyText={t`No markets found`} />
<DataTable table={table} headerHeight={headerHeight} rowHeight="3xl" emptyText={t`No markets found`} />
</Stack>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ import Typography from '@mui/material/Typography'
import { PoolBadges } from '@/components/PageLlamaMarkets/cells/PoolTitleCell/PoolBadges'
import { PoolWarnings } from '@/components/PageLlamaMarkets/cells/PoolTitleCell/PoolWarnings'
import { getImageBaseUrl } from '@/ui/utils'
import { cleanColumnId } from '@ui-kit/shared/ui/TableColumnVisibilityPopover'

const { Spacing } = SizesAndSpaces

export const PoolTitleCell = ({ getValue, row }: CellContext<LendingVault, LendingVault['assets']>) => {
const coins = useMemo(() => Object.values(getValue()), [getValue])
export const PoolTitleCell = ({ getValue, row, table }: CellContext<LendingVault, LendingVault['assets']>) => {
const showCollateral = table.getColumn(cleanColumnId('assets.collateral.symbol'))!.getIsVisible()
const coins = useMemo(() => {
const { borrowed, collateral } = getValue()
return showCollateral ? [collateral, borrowed] : [borrowed]
}, [getValue, showCollateral])
const { blockchainId } = row.original
const imageBaseUrl = getImageBaseUrl(blockchainId)
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Fragment, useMemo } from 'react'
import { useMemo } from 'react'
import Select from '@mui/material/Select'
import Slider from '@mui/material/Slider'
import { DeepKeys } from '@tanstack/table-core/build/lib/utils'
import { get } from 'lodash'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { cleanColumnId } from '@ui-kit/shared/ui/TableColumnVisibilityPopover'

/**
* Get the maximum value from a field in an array of objects.
Expand All @@ -31,7 +32,7 @@ export const MinimumSliderFilter = <T extends unknown>({
field: DeepKeys<T>
format: (value: number) => string
}) => {
const id = field.replaceAll('.', '_')
const id = cleanColumnId(field)
const max = useMemo(() => getMaxValueFromData(data, field), [data, field])
const [value] = (columnFilters[id] ?? [0, max]) as [number, number] // tanstack expects a [min, max] tuple
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Fragment, ReactNode, useMemo } from 'react'
import { ReactNode, useMemo } from 'react'
import { get, identity, sortBy, sortedUniq } from 'lodash'
import Select from '@mui/material/Select'
import Typography from '@mui/material/Typography'
import MenuItem from '@mui/material/MenuItem'
import { DeepKeys } from '@tanstack/table-core/build/lib/utils'
import { cleanColumnId } from '@ui-kit/shared/ui/TableColumnVisibilityPopover'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'

const { Spacing } = SizesAndSpaces
Expand Down Expand Up @@ -36,7 +37,7 @@ export const MultiSelectFilter = <T extends unknown>({
renderItem?: (value: string) => ReactNode
}) => {
const options = useMemo(() => getSortedStrings(data, field), [data, field])
const id = field.replaceAll('.', '_')
const id = cleanColumnId(field)
const value = (columnFilters[id] ?? []) as string[]
return (
<Select
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import { Dispatch, FunctionComponent } from 'react'
import Switch from '@mui/material/Switch'
import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
import { FormControlLabel } from '@mui/material'
import { t } from '@lingui/macro'

type AdvancedModeSwitcherProps = {
label?: string
advancedMode: [boolean, Dispatch<boolean>]
}

export const AdvancedModeSwitcher: FunctionComponent<AdvancedModeSwitcherProps> = ({
advancedMode: [advancedMode, onChange],
advancedMode: [checked, onChange],
label,
}) => (
<Box display="inline-flex" alignItems="center">
}) => {
const control = (
<Switch
checked={advancedMode}
onChange={() => onChange(!advancedMode)}
color="primary"
inputProps={{ ...(label && { 'aria-label': label }) }}
checked={checked}
onChange={() => onChange(!checked)}
inputProps={{ ...(!label && { 'aria-label': t`Advanced mode` }) }}
size="small"
/>
{label && (
<Typography
variant="headingXsBold"
display="inline-block"
// lineHeight to center vertically with the switch. Extra '&' specificity needed to override default.
sx={{ marginLeft: 2, marginRight: 4, '&': { lineHeight: '37px' } }}
>
{label}
</Typography>
)}
</Box>
)
)
return (
<Box display="inline-flex" alignItems="center">
{label ? <FormControlLabel control={control} label={label} sx={{ marginLeft: 2 }} /> : control}
</Box>
)
}
19 changes: 19 additions & 0 deletions packages/curve-ui-kit/src/shared/icons/ToolkitIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createSvgIcon } from '@mui/material/utils'

export const ToolkitIcon = createSvgIcon(
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M5.57133 15.3126L8.23196 12.6521L7.34821 11.7683L4.68752 14.4288L4.1919 13.9332C4.13389 13.8751 4.065 13.8291 3.98918 13.7976C3.91337 13.7662 3.8321 13.75 3.75002 13.75C3.66795 13.75 3.58668 13.7662 3.51086 13.7976C3.43504 13.8291 3.36616 13.8751 3.30815 13.9332L0.808147 16.4332C0.750086 16.4912 0.704026 16.5601 0.6726 16.6359C0.641175 16.7117 0.625 16.793 0.625 16.8751C0.625 16.9571 0.641175 17.0384 0.6726 17.1142C0.704026 17.1901 0.750086 17.2589 0.808147 17.3169L2.68315 19.1919C2.74116 19.25 2.81004 19.2961 2.88586 19.3275C2.96168 19.3589 3.04295 19.3751 3.12502 19.3751C3.20709 19.3751 3.28836 19.3589 3.36418 19.3275C3.44 19.2961 3.50888 19.25 3.5669 19.1919L6.0669 16.6919C6.12496 16.6339 6.17102 16.5651 6.20244 16.4892C6.23387 16.4134 6.25004 16.3321 6.25004 16.2501C6.25004 16.168 6.23387 16.0867 6.20244 16.0109C6.17102 15.9351 6.12496 15.8662 6.0669 15.8082L5.57133 15.3126ZM3.12502 17.8663L2.13383 16.8751L3.75002 15.2589L4.74121 16.2501L3.12502 17.8663Z"
fill="currentColor"
/>
<path
d="M15 18.75C14.0058 18.7489 13.0526 18.3535 12.3496 17.6505C11.6466 16.9474 11.2511 15.9943 11.25 15C11.2505 14.6733 11.2948 14.3481 11.3818 14.0332L5.96681 8.61835C5.65188 8.70525 5.32671 8.74954 5 8.75004C4.38902 8.75253 3.78676 8.60509 3.24603 8.32064C2.7053 8.0362 2.24261 7.62344 1.89852 7.11856C1.55444 6.61368 1.33948 6.03209 1.27248 5.42479C1.20548 4.81749 1.2885 4.20303 1.51425 3.63529L1.86281 2.7466L3.93313 4.81691C4.05211 4.93055 4.21031 4.99395 4.37484 4.99395C4.53938 4.99395 4.69758 4.93055 4.81656 4.81691C4.87466 4.75892 4.92076 4.69004 4.95221 4.61421C4.98366 4.53838 4.99984 4.4571 4.99984 4.375C4.99984 4.29291 4.98366 4.21163 4.95221 4.1358C4.92076 4.05997 4.87466 3.99109 4.81656 3.9331L2.746 1.86191L3.63556 1.51372C4.20335 1.28826 4.81776 1.20548 5.42497 1.27264C6.03217 1.3398 6.61364 1.55485 7.11843 1.89895C7.62322 2.24304 8.03592 2.70568 8.32037 3.24633C8.60482 3.78697 8.75234 4.38913 8.75 5.00004C8.74953 5.32675 8.7052 5.65193 8.61819 5.96685L14.0332 11.3815C14.3481 11.2947 14.6733 11.2505 15 11.25C15.611 11.2475 16.2133 11.395 16.754 11.6794C17.2947 11.9638 17.7574 12.3766 18.1015 12.8815C18.4456 13.3864 18.6605 13.968 18.7275 14.5753C18.7945 15.1826 18.7115 15.797 18.4857 16.3648L18.1375 17.2535L16.0669 15.1832C15.9479 15.0695 15.7897 15.0061 15.6252 15.0061C15.4606 15.0061 15.3024 15.0695 15.1834 15.1832C15.1253 15.2411 15.0792 15.31 15.0477 15.3858C15.0162 15.4616 15 15.5429 15 15.625C15 15.7071 15.0161 15.7884 15.0476 15.8642C15.079 15.9401 15.125 16.009 15.1831 16.067L17.2534 18.1375L16.3647 18.4862C15.9304 18.659 15.4674 18.7485 15 18.75ZM6.28906 7.17272L12.8271 13.711L12.6776 14.0918C12.5313 14.4578 12.4741 14.8533 12.5107 15.2457C12.5474 15.6381 12.6769 16.0161 12.8885 16.3486C13.1001 16.6811 13.3878 16.9585 13.7278 17.1578C14.0677 17.3572 14.4503 17.4728 14.8437 17.4952L14.2993 16.9507C14.1252 16.7766 13.987 16.57 13.8928 16.3425C13.7985 16.115 13.75 15.8712 13.75 15.625C13.75 15.3787 13.7985 15.1349 13.8928 14.9075C13.987 14.68 14.1252 14.4733 14.2993 14.2992C14.6565 13.9583 15.1314 13.7681 15.6252 13.7681C16.119 13.7682 16.5939 13.9585 16.951 14.2995L17.4951 14.8438C17.4727 14.4503 17.3571 14.0678 17.1577 13.7278C16.9583 13.3878 16.6809 13.1002 16.3484 12.8885C16.0159 12.6769 15.6378 12.5474 15.2454 12.5108C14.853 12.4741 14.4575 12.5313 14.0915 12.6777L13.7106 12.8267L7.17312 6.28922L7.32262 5.90835C7.46899 5.54241 7.52621 5.14691 7.48955 4.7545C7.45289 4.36208 7.32341 3.98402 7.1118 3.65153C6.90019 3.31903 6.61252 3.04165 6.27255 2.84227C5.93257 2.6429 5.55005 2.52727 5.15656 2.50491L5.70069 3.04935C5.87484 3.22342 6.01298 3.4301 6.10723 3.65758C6.20148 3.88505 6.24999 4.12887 6.24999 4.3751C6.24999 4.62133 6.20148 4.86514 6.10723 5.09262C6.01298 5.3201 5.87484 5.52677 5.70069 5.70085C5.34346 6.0418 4.8686 6.23201 4.37478 6.23195C3.88096 6.23189 3.40615 6.04157 3.049 5.70054L2.50488 5.15629C2.52718 5.54978 2.64278 5.93232 2.84213 6.27231C3.04148 6.6123 3.31886 6.89999 3.65136 7.1116C3.98385 7.32321 4.36193 7.45268 4.75434 7.48932C5.14676 7.52595 5.54227 7.4687 5.90819 7.32229L6.28906 7.17272Z"
fill="currentColor"
/>
<path
d="M18.2016 1.78134C17.8445 1.44037 17.3697 1.25012 16.8759 1.25012C16.3822 1.25012 15.9074 1.44037 15.5502 1.78134L10.875 6.45634L11.7588 7.34009L16.4338 2.66509C16.5529 2.55151 16.7111 2.48814 16.8757 2.48814C17.0403 2.48814 17.1985 2.55151 17.3176 2.66509C17.4346 2.78247 17.5002 2.94142 17.5002 3.10713C17.5002 3.27283 17.4346 3.43178 17.3176 3.54916L12.6426 8.22416L13.5264 9.10797L18.2014 4.43328C18.5526 4.08142 18.75 3.60456 18.75 3.10736C18.7501 2.61017 18.5528 2.13327 18.2016 1.78134Z"
fill="currentColor"
/>
</svg>,
'Toolkit',
)
129 changes: 129 additions & 0 deletions packages/curve-ui-kit/src/shared/ui/TableColumnVisibilityPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import Popover from '@mui/material/Popover'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import Switch from '@mui/material/Switch'
import { SizesAndSpaces } from '@ui-kit/themes/design/1_sizes_spaces'
import { useCallback, useMemo, useState } from 'react'
import { FormControlLabel } from '@mui/material'
import { OnChangeFn, VisibilityState } from '@tanstack/react-table'

export type ColumnVisibility = {
columnId: string
active: boolean
label: string
}
export type ColumnVisibilityGroup = {
columns: ColumnVisibility[]
label: string
}
const { Spacing } = SizesAndSpaces

// when we define columns in nested objects, tanstack replaces dots with underscores internally
export const cleanColumnId = (field: string) => field.replaceAll('.', '_')
0xAlunara marked this conversation as resolved.
Show resolved Hide resolved

/**
* Dialog that allows to toggle visibility of columns in a table.
*/
export const TableColumnVisibilityPopover = ({
columnVisibilityGroups,
toggleColumnVisibility,
open,
onClose,
anchorEl,
}: {
open: boolean
onClose: () => void
columnVisibilityGroups: ColumnVisibilityGroup[]
toggleColumnVisibility: (columnId: string) => void
anchorEl: HTMLButtonElement
}) => (
<Popover
open={open}
onClose={onClose}
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
slotProps={{
paper: {
sx: { padding: Spacing.md },
},
}}
>
<Stack gap={Spacing.md}>
{columnVisibilityGroups.map(({ columns, label }) => (
<Stack key={label} gap={Spacing.md}>
<Typography variant="headingXsBold" sx={{ borderBottom: (t) => `1px solid ${t.design.Layer[1].Outline}` }}>
{label}
</Typography>
{columns.map(({ columnId, active, label }) => (
<FormControlLabel
key={columnId}
control={
<Switch
data-testid={`visibility-toggle-${cleanColumnId(columnId)}`}
checked={active}
onChange={() => toggleColumnVisibility(columnId)}
size="small"
/>
}
label={label}
/>
))}
</Stack>
))}
</Stack>
</Popover>
)

/**
* Hook to manage column visibility settings. Currently saved in the state.
*/
export const useColumnSettings = (groups: ColumnVisibilityGroup[]) => {
/** current visibility settings in grouped format */
const [columnSettings, setColumnSettings] = useState(groups)

/** toggle visibility of a column by its id */
const toggleColumnVisibility = useCallback(
(columnId: string): void =>
setColumnSettings((prev) =>
prev.map((group) => ({
...group,
columns: group.columns.map((column) =>
column.columnId === columnId ? { ...column, active: !column.active } : column,
),
})),
),
[],
)

/** current visibility state as used internally by tanstack */
const columnVisibility: Record<string, boolean> = useMemo(
() =>
columnSettings.reduce(
(acc, group) => ({
...acc,
...group.columns.reduce((acc, column) => ({ ...acc, [cleanColumnId(column.columnId)]: column.active }), {}),
}),
{},
),
[columnSettings],
)

/** callback to update visibility state by ID, used by tanstack */
const onColumnVisibilityChange: OnChangeFn<VisibilityState> = useCallback(
(newVisibility) => {
const visibility = typeof newVisibility === 'function' ? newVisibility(columnVisibility) : newVisibility
setColumnSettings((prev) =>
prev.map((category) => ({
...category,
columns: category.columns.map((column) => ({
...column,
active: visibility[cleanColumnId(column.columnId)],
})),
})),
)
},
[columnVisibility],
)

return { columnSettings, columnVisibility, onColumnVisibilityChange, toggleColumnVisibility }
}
Loading
Loading