Skip to content

Commit

Permalink
Adds tooltip to the vouch chip icons (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
soaresa authored Nov 24, 2024
1 parent 82be75e commit e0504e5
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 149 deletions.
21 changes: 21 additions & 0 deletions web-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"react-dom": "^18.3.1",
"react-qr-code": "^2.0.14",
"react-router-dom": "^6.23.1",
"react-tooltip": "^5.28.0",
"styled-components": "^6.1.11"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion web-app/src/modules/core/routes/Validators/Validators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { gql, useQuery } from '@apollo/client';
import Page from '../../../ui/Page';
import ValidatorsTable from './components/ValidatorsTable';
import ValidatorsStats from './components/ValidatorsStats';
import VouchesTable from './components/VouchesTable';
import VouchesTable from './components/Vouch/VouchesTable';
import ToggleButton from '../../../ui/ToggleButton';

const GET_VALIDATORS = gql`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
XCircleIcon,
} from '@heroicons/react/20/solid';
// import ProgressBar from './ProgressBar';
import Vouches from './Vouches';
import Vouches from './Vouch/Vouches';

// Define icons for each status
const statusIcons = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,4 @@ function formatPercentage(value: number) {
return `${(value / 10).toFixed(1)}%`;
}

// print percentage 0-100
/*function formatPercentage(value: number, didChange: boolean, didIncrease: boolean) {
if (didChange) {
return didIncrease ? `+${value}%` : `-${value}%`;
}
return `${value}%`;
}*/

export default ValidatorsStats;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const FamilyIcon: React.FC<{ family: string }> = ({ family }) => {
const bgColor = family && family.length > 8 ? `#${family.slice(2, 8)}` : `#f87171`;
return (
<span
className="inline-block w-3 h-3 rounded-full ml-2"
style={{ backgroundColor: bgColor }}
></span>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// VouchChip.tsx
import React from 'react';
import clsx from 'clsx';
import { legendItems } from './legendItems';
import { formatAddress } from '../../../../../../utils';
import { FamilyIcon } from './FamilyIcon';

interface VouchDetails {
inSet: boolean;
compliant: boolean;
epochsToExpire: number;
handle?: string;
address: string;
family: string;
}

export const VouchChip: React.FC<{ vouch: VouchDetails; index: number }> = ({ vouch, index }) => {
// Determine which icons to display based on vouch properties
const activeIcons = legendItems.filter((item) => {
switch (item.title) {
case 'In Set':
return vouch.inSet;
case 'Compliant':
return vouch.compliant;
case 'Non-Compliant':
return !vouch.compliant;
case 'Expiring Soon':
return vouch.epochsToExpire <= 7 && vouch.epochsToExpire > 0;
case 'Expired':
return vouch.epochsToExpire <= 0;
default:
return false;
}
});

return (
<span
key={index}
className={clsx(
'inline-flex items-center px-2 py-1 rounded-full text-sm font-medium text-black',
)}
style={{
backgroundColor: '#F0F0F0',
marginRight: '8px',
marginBottom: '8px',
}}
>
{activeIcons.map((item, idx) => (
<span key={idx} className="relative group mr-1">
<item.Icon className={clsx('w-4 h-4', item.color)} />
<span className="absolute bottom-full mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2 whitespace-nowrap">
{item.title}
</span>
</span>
))}

{/* Display Family Icon */}
{vouch.family && (
<span className="relative group mr-1">
<FamilyIcon family={vouch.family} />
<span className="absolute bottom-full mb-1 hidden group-hover:block bg-black text-white text-xs rounded py-1 px-2 whitespace-nowrap">
Family Color
</span>
</span>
)}

{/* Display Handle or Address */}
{vouch.handle || formatAddress(vouch.address)}
<span className="ml-1" style={{ fontSize: 8 }}>
({vouch.epochsToExpire})
</span>
</span>
);
};

export default VouchChip;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import clsx from 'clsx';
import { legendItems } from './legendItems';

const VouchLegend: React.FC = () => {
return (
<div className="mt-4 p-4 bg-[#F5F5F5] text-[#525252] rounded-md">
<h2 className="text-lg font-bold mb-2">Vouch Legend</h2>
<ul className="list-disc pl-5 space-y-1">
{legendItems.map((item, index) => (
<li key={index} className="flex items-center space-x-2">
<item.Icon className={clsx('w-5 h-5', item.color)} />
<span className="font-bold">{item.title}:</span>
<span>{item.description}</span>
</li>
))}
</ul>
</div>
);
};

export default VouchLegend;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC, useState } from 'react';
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
import { CheckIcon, XMarkIcon } from '@heroicons/react/24/outline';

import AccountAddress from '../../../../ui/AccountAddress';
import AccountAddress from '../../../../../ui/AccountAddress';

interface Vouches {
compliant: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import React from 'react';
import {
CheckIcon,
XMarkIcon,
ExclamationTriangleIcon,
ClockIcon,
GlobeAltIcon,
} from '@heroicons/react/20/solid';
import { CheckIcon, XMarkIcon } from '@heroicons/react/20/solid';
import clsx from 'clsx';
import AccountAddress from '../../../../ui/AccountAddress';
import { ValidatorVouches, VouchDetails } from '../../../../interface/Validator.interface';
import AccountAddress from '../../../../../ui/AccountAddress';
import { ValidatorVouches, VouchDetails } from '../../../../../interface/Validator.interface';
import { VouchChip } from './VouchChip';
import { FamilyIcon } from './FamilyIcon';
import { formatAddress } from '../../../../../../utils';

type VouchesRowProps = {
validator: ValidatorVouches;
showExpired: boolean;
};

function formatAddress(address: string): string {
return address.slice(0, 4) + '...' + address.slice(-4);
}

const VouchesRow: React.FC<VouchesRowProps> = ({ validator, showExpired }) => {
return (
<tr
Expand Down Expand Up @@ -71,53 +64,4 @@ const VouchesRow: React.FC<VouchesRowProps> = ({ validator, showExpired }) => {
);
};

const FamilyIcon: React.FC<{ family: string }> = ({ family }) => {
const bgColor = family && family.length > 8 ? `#${family.slice(2, 8)}` : `#f87171`;
return (
<span
className="inline-block w-3 h-3 rounded-full ml-2"
style={{ backgroundColor: bgColor }}
></span>
);
};

const VouchChip: React.FC<{ vouch: VouchDetails; index: number }> = ({ vouch, index }) => {
// Generate background color for the chip based on the family (hexadecimal)
return (
<span
key={index}
className={clsx(
'inline-flex items-center px-2 py-1 rounded-full text-sm font-medium text-black',
)}
style={{
backgroundColor: '#F0F0F0',
marginRight: '8px',
marginBottom: '8px',
}}
>
{vouch.inSet && <GlobeAltIcon className="w-4 h-4 text-blue-500 mr-1" />}
{vouch.compliant ? (
<CheckIcon className="w-4 h-4 text-green-500 mr-1" />
) : (
<XMarkIcon className="w-4 h-4 text-red-500 mr-1" />
)}

{vouch.epochsToExpire <= 7 && vouch.epochsToExpire > 0 && (
<ExclamationTriangleIcon className="w-4 h-4 text-yellow-500 mr-1" />
)}

{vouch.epochsToExpire <= 0 && <ClockIcon className="w-4 h-4 text-red-500 mr-1" />}

{/* handle or address 1234...5678*/}
{vouch.handle || formatAddress(vouch.address)}
<span className="ml-1" style={{ fontSize: 8 }}>
{' '}
({vouch.epochsToExpire})
</span>

<FamilyIcon family={vouch.family} />
</span>
);
};

export default VouchesRow;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { gql, useQuery } from '@apollo/client';
import SortableTh from './SortableTh';
import SortableTh from '../SortableTh';
import VouchesRow from './VouchesRow';
import { ValidatorVouches } from '../../../../interface/Validator.interface';
import { ValidatorVouches } from '../../../../../interface/Validator.interface';
import VouchesRowSkeleton from './VouchesRowSkeleton';
import VouchesLegend from './VouchesLegend';
import VouchLegend from './VouchLegend';

const GET_VALIDATORS = gql`
query GetValidatorsVouches {
Expand Down Expand Up @@ -179,7 +179,7 @@ const VouchesTable: React.FC = () => {
))}
</tbody>
</table>
<VouchesLegend />
<VouchLegend />
<p className="text-sm text-gray-500 mt-2">
The data in this table is updated every 60 seconds.
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// legendItems.tsx
import {
GlobeAltIcon,
CheckIcon,
XMarkIcon,
ExclamationTriangleIcon,
ClockIcon,
} from '@heroicons/react/20/solid';

export interface LegendItem {
Icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
color: string;
title: string;
description: string;
}

export const legendItems: LegendItem[] = [
{
Icon: GlobeAltIcon,
color: 'text-blue-500', // Color for "In Set"
title: 'In Set',
description: 'The validator is part of the current active set.',
},
{
Icon: CheckIcon,
color: 'text-green-500', // Color for "Compliant"
title: 'Compliant',
description: 'The validator is compliant with audit qualifications.',
},
{
Icon: XMarkIcon,
color: 'text-red-500', // Color for "Non-Compliant"
title: 'Non-Compliant',
description: 'The validator is not compliant with audit qualifications.',
},
{
Icon: ExclamationTriangleIcon,
color: 'text-yellow-500', // Color for "Expiring Soon"
title: 'Expiring Soon',
description: 'Vouch will expire in less than 7 epochs.',
},
{
Icon: ClockIcon,
color: 'text-red-500', // Color for "Expired"
title: 'Expired',
description: 'The vouch has expired.',
},
{
Icon: () => (
<span
className="inline-block w-3 h-3 rounded-full"
style={{ backgroundColor: 'purple', margin: '0px 4px' }} // Color for "Family"
></span>
),
color: '', // No icon, so no color
title: 'Family Color',
description: 'Represents the vouch family grouping.',
},
];
Loading

0 comments on commit e0504e5

Please sign in to comment.