Bläddra i källkod

feat: simplified grid

Alexandru Cambose 1 månad sedan
förälder
incheckning
7f9292af0e

+ 2 - 2
packages/component-library/src/SymbolPairTag/index.stories.tsx

@@ -1,5 +1,5 @@
 import type { Meta, StoryObj } from "@storybook/react";
-import CryptoIcon from "cryptocurrency-icons/svg/color/btc.svg";
+import BtcIcon from "cryptocurrency-icons/svg/color/btc.svg";
 
 import { SymbolPairTag as SymbolPairTagComponent } from "./index.jsx";
 const meta = {
@@ -29,7 +29,7 @@ export const SymbolPairTag = {
   args: {
     displaySymbol: "BTC/USD",
     isLoading: false,
-    icon: <CryptoIcon width="100%" height="100%" viewBox="0 0 32 32" />,
+    icon: <BtcIcon width="100%" height="100%" viewBox="0 0 32 32" />,
     description: "Bitcoin",
   },
 } satisfies StoryObj<typeof SymbolPairTagComponent>;

+ 4 - 4
packages/component-library/src/TableGrid/dummy-row-data.ts

@@ -13,7 +13,7 @@ export const dummyRowData = [
   { id: 11, feed: "LTC/USD", price: 228.77, confidence: 5.66 },
   { id: 12, feed: "TRX/USD", price: 0.12, confidence: 6.44 },
   { id: 13, feed: "NEAR/USD", price: 9.83, confidence: 7.15 },
-  { id: 14, feed: "APT/USD", price: 11.25, confidence: 3.90 },
+  { id: 14, feed: "APT/USD", price: 11.25, confidence: 3.9 },
   { id: 15, feed: "SUI/USD", price: 1.77, confidence: 9.28 },
   { id: 16, feed: "ARB/USD", price: 1.54, confidence: 11.32 },
   { id: 17, feed: "OP/USD", price: 2.12, confidence: 10.44 },
@@ -24,7 +24,7 @@ export const dummyRowData = [
   { id: 21, feed: "ETH/USD", price: 2940.18, confidence: 7.54 },
   { id: 22, feed: "SOL/USD", price: 153.22, confidence: 2.17 },
   { id: 23, feed: "DOGE/USD", price: 0.25, confidence: 11.28 },
-  { id: 24, feed: "BNB/USD", price: 612.93, confidence: 3.10 },
+  { id: 24, feed: "BNB/USD", price: 612.93, confidence: 3.1 },
   { id: 25, feed: "XRP/USD", price: 0.79, confidence: 5.94 },
   { id: 26, feed: "ADA/USD", price: 1.52, confidence: 9.45 },
   { id: 27, feed: "DOT/USD", price: 28.65, confidence: 1.93 },
@@ -46,7 +46,7 @@ export const dummyRowData = [
   { id: 41, feed: "SOL/USD", price: 161.83, confidence: 1.99 },
   { id: 42, feed: "DOGE/USD", price: 0.19, confidence: 8.74 },
   { id: 43, feed: "BNB/USD", price: 595.27, confidence: 3.85 },
-  { id: 44, feed: "XRP/USD", price: 0.73, confidence: 6.20 },
+  { id: 44, feed: "XRP/USD", price: 0.73, confidence: 6.2 },
   { id: 45, feed: "ADA/USD", price: 1.48, confidence: 10.51 },
   { id: 46, feed: "DOT/USD", price: 30.12, confidence: 2.09 },
   { id: 47, feed: "MATIC/USD", price: 2.19, confidence: 12.88 },
@@ -58,7 +58,7 @@ export const dummyRowData = [
   { id: 52, feed: "APT/USD", price: 11.42, confidence: 4.44 },
   { id: 53, feed: "SUI/USD", price: 1.79, confidence: 8.93 },
   { id: 54, feed: "ARB/USD", price: 1.59, confidence: 10.13 },
-  { id: 55, feed: "OP/USD", price: 2.31, confidence: 9.20 },
+  { id: 55, feed: "OP/USD", price: 2.31, confidence: 9.2 },
   { id: 56, feed: "FIL/USD", price: 37.02, confidence: 2.72 },
   { id: 57, feed: "ICP/USD", price: 18.67, confidence: 6.01 },
   { id: 58, feed: "BTC/USD", price: 59_243.82, confidence: 3.92 },

+ 0 - 11
packages/component-library/src/TableGrid/full-width-cell-renderer.tsx

@@ -1,11 +0,0 @@
-import type { ICellRendererParams } from 'ag-grid-community';
-
-export const FullWidthCellRenderer = colDefs => (props: ICellRendererParams) => {
-    console.log({props, colDefs});
-  return (
-    <div className="full-width-panel p-4">
-      <h3 className="font-bold">Full Width Row</h3>
-      <p>Row data: {JSON.stringify(props.data)}</p>
-    </div>
-  );
-};

+ 12 - 0
packages/component-library/src/TableGrid/index.module.scss

@@ -15,3 +15,15 @@
 
   height: 100%;
 }
+
+.defaultCellContainer {
+  height: 100%;
+  display: flex;
+  align-items: center;
+}
+
+
+.skeletonContainer {
+  height: theme.spacing(10);
+  width: 100%;
+}

+ 36 - 13
packages/component-library/src/TableGrid/index.stories.tsx

@@ -1,7 +1,9 @@
 import { ChartLine } from "@phosphor-icons/react/dist/ssr/ChartLine";
 import type { Meta, StoryObj } from "@storybook/react";
+import BtcIcon from "cryptocurrency-icons/svg/color/btc.svg";
 
 import { Badge } from "../Badge";
+import { SymbolPairTag } from "../SymbolPairTag";
 import { dummyRowData } from "./dummy-row-data";
 import { TableGrid as TableGridComponent } from "./index.jsx";
 
@@ -41,16 +43,36 @@ const meta = {
 } satisfies Meta<typeof TableGridComponent>;
 export default meta;
 
-export const PriceCellRenderer = ({ value }: { value: number }) =>  (
-    <span>
-      {`$${value.toLocaleString(undefined, {
-        minimumFractionDigits: 2,
-        maximumFractionDigits: 2,
-      })}`}
-    </span>
-  );
+const PriceCellRenderer = ({ value }: { value: number }) => (
+  <span style={{ height: "100%", display: "flex", alignItems: "center" }}>
+    {`$${value.toLocaleString(undefined, {
+      minimumFractionDigits: 2,
+      maximumFractionDigits: 2,
+    })}`}
+  </span>
+);
 
-export const ConfidenceCellRenderer = ({ value }: { value: number }) =>  <span>{`+/- ${value.toFixed(2)}%`}</span>;
+const ConfidenceCellRenderer = ({ value }: { value: number }) => (
+  <span
+    style={{ height: "100%", display: "flex", alignItems: "center" }}
+  >{`+/- ${value.toFixed(2)}%`}</span>
+);
+
+const FeedCellRenderer = ({ value }: { value: string }) => (
+  <div style={{ height: "100%", display: "flex", alignItems: "center" }}>
+    <SymbolPairTag
+      displaySymbol={value}
+      icon={<BtcIcon />}
+      description={value}
+    />
+  </div>
+);
+
+const FeedCellRendererLoading = () => (
+  <div style={{ height: "100%", display: "flex", alignItems: "center" }}>
+    <SymbolPairTag isLoading />
+  </div>
+);
 
 const args = {
   colDefs: [
@@ -61,22 +83,23 @@ const args = {
     {
       headerName: "PRICE FEED",
       field: "feed",
+      cellRenderer: FeedCellRenderer,
+      loadingCellRenderer: FeedCellRendererLoading,
+      flex: 2,
     },
     {
       headerName: "PRICE",
       field: "price",
-      flex: 2,
+      flex: 3,
       cellRenderer: PriceCellRenderer,
     },
     {
       headerName: "CONFIDENCE",
       field: "confidence",
       cellRenderer: ConfidenceCellRenderer,
-      context: {
-        loadingSkeletonWidth: 20,
-      }
     },
   ],
+  rowHeight: 70,
   rowData: dummyRowData,
 };
 

+ 91 - 87
packages/component-library/src/TableGrid/index.tsx

@@ -6,78 +6,87 @@ import {
   themeQuartz,
 } from "ag-grid-community";
 import { AgGridReact } from "ag-grid-react";
-import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
+import {
+  forwardRef,
+  ReactNode,
+  useCallback,
+  useImperativeHandle,
+  useMemo,
+  useRef,
+  useState,
+} from "react";
 
 import { Card } from "../Card";
 import { Paginator } from "../Paginator";
 import { Skeleton } from "../Skeleton";
-import { useMediaQueryBreakpoint } from '../useMediaQuery';
-import { FullWidthCellRenderer } from './full-width-cell-renderer';
 import styles from "./index.module.scss";
-import type { TableGridProps } from './table-grid-props';
+import type { TableGridProps } from "./table-grid-props";
+
 // Register all Community features
-ModuleRegistry.registerModules([AllCommunityModule,  TextFilterModule,
-  ClientSideRowModelModule,]);
+ModuleRegistry.registerModules([
+  AllCommunityModule,
+  TextFilterModule,
+  ClientSideRowModelModule,
+]);
 
-const SkeletonCellRenderer = (props: { value?: unknown }) => {
+const SkeletonCellRenderer = (props: { value?: ReactNode }) => {
   if (!props.value) {
-    return <Skeleton fill />;
+    return <div className={styles.defaultCellContainer}><div className={styles.skeletonContainer}><Skeleton fill /></div></div>;
   }
-  return <span>{props.value}</span>;
+  return <div className={styles.defaultCellContainer}>{props.value}</div>;
 };
 
-export const TableGrid = forwardRef(<TData extends Record<string, unknown>>({
-  rowData,
-  colDefs,
-  isLoading,
-  cardProps,
-  pagination,
-  ...props
-}: TableGridProps<TData>, ref: React.Ref<AgGridReact<TData>>) => {
-  const gridRef = useRef<AgGridReact<TData>>(null);
-  const [pageSize, setPageSize] = useState(10);
-  const [currentPage, setCurrentPage] = useState(1);
-  const [totalPages, setTotalPages] = useState(1);
-  useImperativeHandle(ref, () => gridRef.current);
-
-  const defaultColDef = useMemo(() => {
-    return {
-      cellRenderer: SkeletonCellRenderer,
-      flex: 1,
-    };
-  }, []);
+export const TableGrid = forwardRef(
+  <TData extends Record<string, unknown>>(
+    {
+      rowData,
+      colDefs,
+      isLoading,
+      cardProps,
+      pagination,
+      ...props
+    }: TableGridProps<TData>,
+    ref: React.Ref<AgGridReact<TData> | null>,
+  ) => {
+    const gridRef = useRef<AgGridReact<TData>>(null);
+    const [pageSize, setPageSize] = useState(10);
+    const [currentPage, setCurrentPage] = useState(1);
+    const [totalPages, setTotalPages] = useState(1);
+    useImperativeHandle(ref, () => gridRef.current);
 
-  const isLargeScreen = useMediaQueryBreakpoint("md");
-  useEffect(() => {
-    if(!gridRef.current?.api) return;  
-    gridRef.current.api.redrawRows();
-    gridRef.current.api.resetRowHeights()
-    gridRef.current.api.onRowHeightChanged(); 
-  }, [isLargeScreen]);
-
-  const mappedColDefs = useMemo(() => {
-    return colDefs.map((colDef) => {
+    const defaultColDef = useMemo(() => {
       return {
-        ...colDef,
-        cellRenderer: isLoading ? SkeletonCellRenderer : colDef.cellRenderer,
+        cellRenderer: SkeletonCellRenderer,
+        flex: 1,
       };
-    });
-  }, [colDefs, isLoading]);
+    }, []);
 
-  const fullWidthCellRendererComponent = useMemo(() => FullWidthCellRenderer(colDefs), [colDefs]);
-  
-  const onPaginationChanged = useCallback(() => {
-    if (gridRef.current?.api) {
-      const api = gridRef.current.api;
-      setPageSize(api.paginationGetPageSize());
-      setCurrentPage(api.paginationGetCurrentPage() + 1);
-      setTotalPages(api.paginationGetTotalPages());
-    }
-  }, []);
-  const onPageChange = useCallback((newPage: number) => {
-    gridRef.current?.api.paginationGoToPage(newPage - 1);
-  }, []);
-  const tableGrid = (
+    const mappedColDefs = useMemo(() => {
+      return colDefs.map((colDef) => {
+        return {
+          ...colDef,
+          // the types in ag-grid are `any` for the cellRenderers
+          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+          cellRenderer: isLoading
+            ? (colDef.loadingCellRenderer ?? SkeletonCellRenderer)
+            : colDef.cellRenderer,
+        };
+      });
+    }, [colDefs, isLoading]);
+
+    const onPaginationChanged = useCallback(() => {
+      if (gridRef.current?.api) {
+        const api = gridRef.current.api;
+        setPageSize(api.paginationGetPageSize());
+        setCurrentPage(api.paginationGetCurrentPage() + 1);
+        setTotalPages(api.paginationGetTotalPages());
+      }
+    }, []);
+    const onPageChange = useCallback((newPage: number) => {
+      gridRef.current?.api.paginationGoToPage(newPage - 1);
+    }, []);
+
+    const tableGrid = (
       <AgGridReact<TData>
         className={styles.tableGrid}
         ref={gridRef}
@@ -87,40 +96,35 @@ export const TableGrid = forwardRef(<TData extends Record<string, unknown>>({
         columnDefs={mappedColDefs}
         theme={themeQuartz}
         domLayout="autoHeight"
-        fullWidthCellRenderer={fullWidthCellRendererComponent}
-        getRowHeight={() => {
-    if (!isLargeScreen) {
-      return 100;
-    }
-  }}
-  isFullWidthRow={() => !isLargeScreen}   pagination={pagination ?? false}
+        pagination={pagination ?? false}
         paginationPageSize={pageSize}
         suppressPaginationPanel
         onPaginationChanged={onPaginationChanged}
         {...props}
       />
-  );
-  if (!cardProps && !pagination) {
-    return tableGrid;
-  }
-  return (
-    <Card
-      footer={
-        pagination && (
-          <Paginator
-            numPages={totalPages}
-            currentPage={currentPage}
-            onPageChange={onPageChange}
-            pageSize={pageSize}
-            onPageSizeChange={setPageSize}
-          />
-        )
-      }
-      {...cardProps}
-    >
-      {tableGrid}
-    </Card>
-  );
-});
+    );
+    if (!cardProps && !pagination) {
+      return tableGrid;
+    }
+    return (
+      <Card
+        footer={
+          pagination && (
+            <Paginator
+              numPages={totalPages}
+              currentPage={currentPage}
+              onPageChange={onPageChange}
+              pageSize={pageSize}
+              onPageSizeChange={setPageSize}
+            />
+          )
+        }
+        {...cardProps}
+      >
+        {tableGrid}
+      </Card>
+    );
+  },
+);
 
 TableGrid.displayName = "TableGrid";

+ 6 - 6
packages/component-library/src/TableGrid/table-grid-props.ts

@@ -1,18 +1,18 @@
-import type { ColDef } from 'ag-grid-community';
-import type { AgGridReactProps } from 'ag-grid-react';
+import type { ColDef } from "ag-grid-community";
+import type { AgGridReactProps } from "ag-grid-react";
 
 import type { Props as CardProps } from "../Card";
 
 type ExtendedColDef<TData> = ColDef<TData> & {
-  loadingSkeletonWidth?: number;
+  loadingCellRenderer?: ColDef<TData>["cellRenderer"];
 };
 
 export type TableGridProps<TData extends Record<string, unknown>> = {
   rowData: TData[];
   colDefs: ExtendedColDef<TData>[];
   isLoading?: boolean;
-  cardProps?: Omit<CardProps<"div">, "children" | "footer"> & { nonInteractive?: true };
+  cardProps?: Omit<CardProps<"div">, "children" | "footer"> & {
+    nonInteractive?: true;
+  };
   pagination?: boolean;
 } & Omit<AgGridReactProps<TData>, "rowData" | "defaultColDef" | "columnDefs">;
-
-

+ 0 - 9
packages/component-library/src/useMediaQuery/index.module.scss

@@ -1,9 +0,0 @@
-@use "../theme";
-
-:export {
-  breakpointSm: #{theme.map-get-strict(theme.$breakpoints, "sm")};
-  breakpointMd: #{theme.map-get-strict(theme.$breakpoints, "md")};
-  breakpointLg: #{theme.map-get-strict(theme.$breakpoints, "lg")};
-  breakpointXl: #{theme.map-get-strict(theme.$breakpoints, "xl")};
-  breakpoint2Xl: #{theme.map-get-strict(theme.$breakpoints, "2xl")};
-}

+ 0 - 40
packages/component-library/src/useMediaQuery/index.ts

@@ -1,40 +0,0 @@
-import { useEffect, useState } from 'react';
-
-import styles from './index.module.scss';
-
-export const MEDIA_BREAKPOINTS: Record<string, string> = {
-  sm: styles.breakpointSm ?? "0",
-  md: styles.breakpointMd ?? "0",
-  lg: styles.breakpointLg ?? "0",
-  xl: styles.breakpointXl ?? "0",
-  "2xl": styles.breakpoint2Xl ?? "0",
-}
-
-const mediaQuery = (breakpoint: string) => `(min-width: ${breakpoint})`
-
-export const useMediaQueryBreakpoint = (breakpoint: keyof typeof MEDIA_BREAKPOINTS, callback?: (matches: boolean) => void) => {
-  return useMediaQuery(mediaQuery(MEDIA_BREAKPOINTS[breakpoint] ?? ''), callback);
-}
-
-export const useMediaQuery = (query: string, callback?: (matches: boolean) => void) => {
-  const [matches, setMatches] = useState(false);
-
-  useEffect(() => {
-    const media = globalThis.window.matchMedia(query);
-
-    setMatches(media.matches);
-
-    const listener = (event: MediaQueryListEvent) => {
-      setMatches(event.matches);
-      callback?.(event.matches);
-    };
-
-    media.addEventListener("change", listener);
-
-    return () => {
-      media.removeEventListener("change", listener);
-    };
-  }, [query, callback]);
-
-  return matches;
-};