diff --git a/libs/deal-ticket/src/order-dialog.tsx b/libs/deal-ticket/src/order-dialog.tsx index 30c123666..2cc5c0c3e 100644 --- a/libs/deal-ticket/src/order-dialog.tsx +++ b/libs/deal-ticket/src/order-dialog.tsx @@ -37,7 +37,7 @@ export const OrderDialog = ({ return ( } + icon={} > {transaction.hash && (

diff --git a/libs/order-list/package.json b/libs/order-list/package.json new file mode 100644 index 000000000..e11fcc705 --- /dev/null +++ b/libs/order-list/package.json @@ -0,0 +1,4 @@ +{ + "name": "@vegaprotocol/order-list", + "version": "0.0.1" +} diff --git a/libs/order-list/project.json b/libs/order-list/project.json index a85b132a6..66b7ea639 100644 --- a/libs/order-list/project.json +++ b/libs/order-list/project.json @@ -4,6 +4,26 @@ "projectType": "library", "tags": [], "targets": { + "build": { + "executor": "@nrwl/web:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/order-list", + "tsConfig": "libs/order-list/tsconfig.lib.json", + "project": "libs/order-list/package.json", + "entryFile": "libs/order-list/src/index.ts", + "external": ["react/jsx-runtime"], + "rollupConfig": "@nrwl/react/plugins/bundle-rollup", + "compiler": "babel", + "assets": [ + { + "glob": "libs/order-list/README.md", + "input": ".", + "output": "." + } + ] + } + }, "lint": { "executor": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], diff --git a/libs/order-list/src/index.ts b/libs/order-list/src/index.ts index 3d3fcc27c..5e75f9108 100644 --- a/libs/order-list/src/index.ts +++ b/libs/order-list/src/index.ts @@ -1,2 +1,2 @@ -export * from './order-list'; -export * from './order-list-container'; +export * from './lib/order-list'; +export * from './lib/order-list-container'; diff --git a/libs/order-list/src/__generated__/OrderFields.ts b/libs/order-list/src/lib/__generated__/OrderFields.ts similarity index 100% rename from libs/order-list/src/__generated__/OrderFields.ts rename to libs/order-list/src/lib/__generated__/OrderFields.ts diff --git a/libs/order-list/src/__generated__/OrderSub.ts b/libs/order-list/src/lib/__generated__/OrderSub.ts similarity index 100% rename from libs/order-list/src/__generated__/OrderSub.ts rename to libs/order-list/src/lib/__generated__/OrderSub.ts diff --git a/libs/order-list/src/__generated__/Orders.ts b/libs/order-list/src/lib/__generated__/Orders.ts similarity index 100% rename from libs/order-list/src/__generated__/Orders.ts rename to libs/order-list/src/lib/__generated__/Orders.ts diff --git a/libs/order-list/src/order-list-container.tsx b/libs/order-list/src/lib/order-list-container.tsx similarity index 100% rename from libs/order-list/src/order-list-container.tsx rename to libs/order-list/src/lib/order-list-container.tsx diff --git a/libs/order-list/src/order-list-manager.spec.tsx b/libs/order-list/src/lib/order-list-manager.spec.tsx similarity index 55% rename from libs/order-list/src/order-list-manager.spec.tsx rename to libs/order-list/src/lib/order-list-manager.spec.tsx index 70bd19a7b..a49c1dd02 100644 --- a/libs/order-list/src/order-list-manager.spec.tsx +++ b/libs/order-list/src/lib/order-list-manager.spec.tsx @@ -1,17 +1,17 @@ import { render, screen } from '@testing-library/react'; import { OrderListManager } from './order-list-manager'; -import * as useOrdersHook from './use-orders'; +import * as useDataProviderHook from '@vegaprotocol/react-helpers'; import type { Orders_party_orders } from './__generated__/Orders'; +import * as orderListMock from './order-list'; +import { forwardRef } from 'react'; -jest.mock('./order-list', () => ({ - OrderList: () =>

OrderList
, -})); +jest.mock('./order-list'); test('Renders a loading state while awaiting orders', () => { - jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ - orders: [], + jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({ + data: [], loading: true, - error: null, + error: undefined, }); render(); expect(screen.getByText('Loading...')).toBeInTheDocument(); @@ -19,8 +19,8 @@ test('Renders a loading state while awaiting orders', () => { test('Renders an error state', () => { const errorMsg = 'Oops! An Error'; - jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ - orders: [], + jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({ + data: [], loading: false, error: new Error(errorMsg), }); @@ -31,10 +31,13 @@ test('Renders an error state', () => { }); test('Renders the order list if orders provided', async () => { - jest.spyOn(useOrdersHook, 'useOrders').mockReturnValue({ - orders: [{ id: '1' } as Orders_party_orders], + // @ts-ignore Orderlist is read only but we need to override with the forwardref to + // avoid warnings about padding refs + orderListMock.OrderList = forwardRef(() =>
OrderList
); + jest.spyOn(useDataProviderHook, 'useDataProvider').mockReturnValue({ + data: [{ id: '1' } as Orders_party_orders], loading: false, - error: null, + error: undefined, }); render(); expect(await screen.findByText('OrderList')).toBeInTheDocument(); diff --git a/libs/order-list/src/lib/order-list-manager.tsx b/libs/order-list/src/lib/order-list-manager.tsx new file mode 100644 index 000000000..fd0321270 --- /dev/null +++ b/libs/order-list/src/lib/order-list-manager.tsx @@ -0,0 +1,79 @@ +import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; +import { OrderList } from './order-list'; +import type { OrderFields } from './__generated__/OrderFields'; +import { useDataProvider } from '@vegaprotocol/react-helpers'; +import { + ordersDataProvider, + prepareIncomingOrders, + sortOrders, +} from './orders-data-provider'; +import { useCallback, useMemo, useRef } from 'react'; +import type { AgGridReact } from 'ag-grid-react'; +import type { OrderSub_orders } from './__generated__/OrderSub'; +import isEqual from 'lodash/isEqual'; + +interface OrderListManagerProps { + partyId: string; +} + +export const OrderListManager = ({ partyId }: OrderListManagerProps) => { + const gridRef = useRef(null); + const variables = useMemo(() => ({ partyId }), [partyId]); + + // Apply updates to the table + const update = useCallback((delta: OrderSub_orders[]) => { + if (!gridRef.current) { + return false; + } + + const incoming = prepareIncomingOrders(delta); + + const update: OrderFields[] = []; + const add: OrderFields[] = []; + + incoming.forEach((d) => { + if (!gridRef.current) { + return; + } + + const rowNode = gridRef.current.api.getRowNode(d.id); + + if (rowNode) { + if (!isEqual) { + update.push(d); + } + } else { + add.push(d); + } + }); + + if (update.length || add.length) { + gridRef.current.api.applyTransactionAsync({ + update, + add, + addIndex: 0, + }); + } + + return true; + }, []); + + const { data, error, loading } = useDataProvider( + ordersDataProvider, + update, + variables + ); + + const orders = useMemo(() => { + if (!data) { + return null; + } + return sortOrders(data); + }, [data]); + + return ( + + {(data) => } + + ); +}; diff --git a/libs/order-list/src/order-list.spec.tsx b/libs/order-list/src/lib/order-list.spec.tsx similarity index 93% rename from libs/order-list/src/order-list.spec.tsx rename to libs/order-list/src/lib/order-list.spec.tsx index eb9b5bb4a..2cbc332ce 100644 --- a/libs/order-list/src/order-list.spec.tsx +++ b/libs/order-list/src/lib/order-list.spec.tsx @@ -12,7 +12,7 @@ import { OrderList } from './order-list'; test('No orders message shown', async () => { await act(async () => { - render(); + render(); }); expect(screen.getByText('No orders')).toBeInTheDocument(); }); @@ -77,7 +77,7 @@ const limitOrder: Orders_party_orders = { test('Correct columns are rendered', async () => { await act(async () => { - render(); + render(); }); const headers = screen.getAllByRole('columnheader'); @@ -87,7 +87,7 @@ test('Correct columns are rendered', async () => { 'Amount', 'Type', 'Status', - 'Remaining', + 'Filled', 'Price', 'Time In Force', 'Created At', @@ -96,7 +96,7 @@ test('Correct columns are rendered', async () => { test('Correct formatting applied for market order', async () => { await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); @@ -117,7 +117,7 @@ test('Correct formatting applied for market order', async () => { test('Correct formatting applied for GTT limit order', async () => { await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); const expectedValues = [ @@ -144,7 +144,7 @@ test('Correct formatting applied for a rejected order', async () => { rejectionReason: OrderRejectionReason.InsufficientAssetBalance, }; await act(async () => { - render(); + render(); }); const cells = screen.getAllByRole('gridcell'); expect(cells[3]).toHaveTextContent( diff --git a/libs/order-list/src/lib/order-list.tsx b/libs/order-list/src/lib/order-list.tsx new file mode 100644 index 000000000..42ea5e8fd --- /dev/null +++ b/libs/order-list/src/lib/order-list.tsx @@ -0,0 +1,89 @@ +import { OrderTimeInForce, OrderStatus, Side } from '@vegaprotocol/types'; +import type { Orders_party_orders } from './__generated__/Orders'; +import { formatNumber, getDateTimeFormat } from '@vegaprotocol/react-helpers'; +import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; +import type { ValueFormatterParams } from 'ag-grid-community'; +import type { AgGridReact } from 'ag-grid-react'; +import { AgGridColumn } from 'ag-grid-react'; +import { forwardRef } from 'react'; + +interface OrderListProps { + data: Orders_party_orders[] | null; +} + +export const OrderList = forwardRef( + ({ data }, ref) => { + return ( + data.id} + > + + { + const prefix = data.side === Side.Buy ? '+' : '-'; + return prefix + value; + }} + /> + + { + if (value === OrderStatus.Rejected) { + return `${value}: ${data.rejectionReason}`; + } + + return value; + }} + /> + { + return `${Number(data.size) - Number(data.remaining)}/${data.size}`; + }} + /> + { + if (data.type === 'Market') { + return '-'; + } + return formatNumber(value, data.market.decimalPlaces); + }} + /> + { + if (value === OrderTimeInForce.GTT && data.expiresAt) { + const expiry = getDateTimeFormat().format( + new Date(data.expiresAt) + ); + return `${value}: ${expiry}`; + } + + return value; + }} + /> + { + return getDateTimeFormat().format(new Date(value)); + }} + /> + + ); + } +); diff --git a/libs/order-list/src/lib/orders-data-provider.ts b/libs/order-list/src/lib/orders-data-provider.ts new file mode 100644 index 000000000..8f8492f79 --- /dev/null +++ b/libs/order-list/src/lib/orders-data-provider.ts @@ -0,0 +1,104 @@ +import { gql } from '@apollo/client'; +import { makeDataProvider } from '@vegaprotocol/react-helpers'; +import type { OrderFields } from './__generated__/OrderFields'; +import type { Orders, Orders_party_orders } from './__generated__/Orders'; +import type { OrderSub } from './__generated__/OrderSub'; +import orderBy from 'lodash/orderBy'; +import uniqBy from 'lodash/uniqBy'; + +const ORDER_FRAGMENT = gql` + fragment OrderFields on Order { + id + market { + id + name + decimalPlaces + tradableInstrument { + instrument { + code + } + } + } + type + side + size + status + rejectionReason + price + timeInForce + remaining + expiresAt + createdAt + updatedAt + } +`; + +export const ORDERS_QUERY = gql` + ${ORDER_FRAGMENT} + query Orders($partyId: ID!) { + party(id: $partyId) { + id + orders { + ...OrderFields + } + } + } +`; + +export const ORDERS_SUB = gql` + ${ORDER_FRAGMENT} + subscription OrderSub($partyId: ID!) { + orders(partyId: $partyId) { + ...OrderFields + } + } +`; + +// A single update can contain the same order with multiple updates, so we need to find +// the latest version of the order and only update using that +export const sortOrders = (orders: OrderFields[]) => { + return orderBy( + orders, + (o) => { + if (!o.updatedAt) return new Date(o.createdAt).getTime(); + return new Date(o.updatedAt).getTime(); + }, + 'desc' + ); +}; + +export const uniqOrders = (orders: OrderFields[]) => { + return uniqBy(orders, 'id'); +}; + +export const prepareIncomingOrders = (delta: OrderFields[]) => { + const sortedOrders = sortOrders(delta); + const incoming = uniqOrders(sortedOrders); + return incoming; +}; + +const update = (draft: OrderFields[], delta: OrderFields[]) => { + const incoming = prepareIncomingOrders(delta); + + // Add or update incoming orders + incoming.forEach((order) => { + const index = draft.findIndex((o) => o.id === order.id); + if (index === -1) { + draft.unshift(order); + } else { + draft[index] = order; + } + }); +}; + +const getData = (responseData: Orders): Orders_party_orders[] | null => + responseData?.party?.orders || null; +const getDelta = (subscriptionData: OrderSub) => subscriptionData.orders || []; + +export const ordersDataProvider = makeDataProvider( + ORDERS_QUERY, + ORDERS_SUB, + update, + getData, + getDelta +); diff --git a/libs/order-list/src/order-list-manager.tsx b/libs/order-list/src/order-list-manager.tsx deleted file mode 100644 index 7a2b13915..000000000 --- a/libs/order-list/src/order-list-manager.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; -import { useOrders } from './use-orders'; -import { OrderList } from './order-list'; -import type { OrderFields } from './__generated__/OrderFields'; - -interface OrderListManagerProps { - partyId: string; -} - -export const OrderListManager = ({ partyId }: OrderListManagerProps) => { - const { orders, loading, error } = useOrders(partyId); - - return ( - loading={loading} error={error} data={orders}> - {(data) => } - - ); -}; diff --git a/libs/order-list/src/order-list.tsx b/libs/order-list/src/order-list.tsx deleted file mode 100644 index c48f10cd8..000000000 --- a/libs/order-list/src/order-list.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { OrderTimeInForce, OrderStatus, Side } from '@vegaprotocol/types'; -import type { Orders_party_orders } from './__generated__/Orders'; -import { - formatNumber, - getDateTimeFormat, - t, - useApplyGridTransaction, -} from '@vegaprotocol/react-helpers'; -import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; -import type { GridApi, ValueFormatterParams } from 'ag-grid-community'; -import { AgGridColumn } from 'ag-grid-react'; -import { useRef, useState } from 'react'; - -interface OrderListProps { - orders: Orders_party_orders[]; -} - -export const OrderList = ({ orders }: OrderListProps) => { - // Store initial orders for initial table data set, further updates - // are handled by the effect below - const [initialOrders] = useState(orders); - const gridApi = useRef(null); - useApplyGridTransaction(orders, gridApi.current); - - return ( - { - gridApi.current = params.api; - }} - getRowNodeId={(data) => data.id} - > - - { - const prefix = data.side === Side.Buy ? '+' : '-'; - return prefix + value; - }} - /> - - { - if (value === OrderStatus.Rejected) { - return `${value}: ${data.rejectionReason}`; - } - - return value; - }} - /> - { - return `${Number(data.size) - Number(data.remaining)}/${data.size}`; - }} - /> - { - if (data.type === 'Market') { - return '-'; - } - return formatNumber(value, data.market.decimalPlaces); - }} - /> - { - if (value === OrderTimeInForce.GTT && data.expiresAt) { - const expiry = getDateTimeFormat().format(new Date(data.expiresAt)); - return `${value}: ${expiry}`; - } - - return value; - }} - /> - { - return getDateTimeFormat().format(new Date(value)); - }} - /> - - ); -}; diff --git a/libs/order-list/src/use-orders.spec.tsx b/libs/order-list/src/use-orders.spec.tsx deleted file mode 100644 index bce747a0c..000000000 --- a/libs/order-list/src/use-orders.spec.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import type { MockedResponse } from '@apollo/client/testing'; -import { MockedProvider } from '@apollo/client/testing'; -import { renderHook } from '@testing-library/react-hooks'; -import type { OrderFields } from './__generated__/OrderFields'; -import type { Orders } from './__generated__/Orders'; -import type { OrderSub } from './__generated__/OrderSub'; -import { - OrderStatus, - Side, - OrderType, - OrderTimeInForce, -} from '@vegaprotocol/types'; -import type { ReactNode } from 'react'; -import { ORDERS_QUERY, ORDERS_SUB, useOrders } from './use-orders'; - -const partyId = '0x123'; - -function generateOrder(order?: Partial): OrderFields { - return { - __typename: 'Order', - id: '1', - market: { - __typename: 'Market', - id: 'market-id', - name: 'market-name', - decimalPlaces: 0, - tradableInstrument: { - __typename: 'TradableInstrument', - instrument: { - __typename: 'Instrument', - code: 'instrument-code', - }, - }, - }, - type: OrderType.Market, - side: Side.Buy, - size: '10', - status: OrderStatus.Active, - rejectionReason: null, - price: '', - timeInForce: OrderTimeInForce.GTC, - remaining: '10', - createdAt: '2022-01-01T00:00:00', - updatedAt: null, - expiresAt: null, - ...order, - }; -} - -function setup(mocks: MockedResponse[] = [], id: string | null) { - const wrapper = ({ children }: { children: ReactNode }) => ( - {children} - ); - return renderHook(() => useOrders(id as string), { wrapper }); -} - -test('Fetches and subscribes to orders and merges appropriately', async () => { - const order = generateOrder(); - const mockOrderQuery: MockedResponse = { - request: { - query: ORDERS_QUERY, - variables: { partyId }, - }, - result: { - data: { - party: { - __typename: 'Party', - id: partyId, - orders: [order], - }, - }, - }, - }; - - const updatedOrder = generateOrder({ - id: '1', - remaining: '5', - updatedAt: '2022-01-01T00:01:00', - }); - const newOrder = generateOrder({ - id: '2', - createdAt: '2022-01-01T01:00:00', - }); - const mockOrderSub: MockedResponse = { - request: { - query: ORDERS_SUB, - variables: { partyId }, - }, - result: { - data: { - orders: [updatedOrder, newOrder], - }, - }, - delay: 100, - }; - const { result, waitForNextUpdate } = setup( - [mockOrderQuery, mockOrderSub], - partyId - ); - expect(result.current.loading).toBe(true); - expect(result.current.error).toBe(null); - await waitForNextUpdate(); - expect(result.current.orders).toEqual([order]); - expect(result.current.loading).toBe(false); - await waitForNextUpdate(); - expect(result.current.orders).toEqual([newOrder, updatedOrder]); -}); - -test('Returns an error if fetch fails', async () => { - const error = new Error('Something failed'); - const mockFailedOrderQuery: MockedResponse = { - request: { - query: ORDERS_QUERY, - variables: { partyId }, - }, - error, - }; - const { result, waitForNextUpdate } = setup([mockFailedOrderQuery], partyId); - expect(result.current.loading).toBe(true); - expect(result.current.error).toBe(null); - await waitForNextUpdate(); - expect(result.current.error).toEqual(error); - expect(result.current.loading).toBe(false); -}); - -test('No queries are made if no pubkey provided', () => { - const mockQuery: MockedResponse = { - request: { - query: ORDERS_QUERY, - variables: { partyId }, - }, - newData: jest.fn(), - }; - const { result } = setup([mockQuery], null); - expect(mockQuery.newData).not.toBeCalled(); - expect(result.current.loading).toBe(false); - expect(result.current.error).toBe(null); -}); diff --git a/libs/order-list/src/use-orders.ts b/libs/order-list/src/use-orders.ts deleted file mode 100644 index 9bae2ad90..000000000 --- a/libs/order-list/src/use-orders.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { gql, useApolloClient } from '@apollo/client'; -import uniqBy from 'lodash/uniqBy'; -import orderBy from 'lodash/orderBy'; -import type { Orders, OrdersVariables } from './__generated__/Orders'; -import type { OrderSub, OrderSubVariables } from './__generated__/OrderSub'; -import type { OrderFields } from './__generated__/OrderFields'; - -const ORDER_FRAGMENT = gql` - fragment OrderFields on Order { - id - market { - id - name - decimalPlaces - tradableInstrument { - instrument { - code - } - } - } - type - side - size - status - rejectionReason - price - timeInForce - remaining - expiresAt - createdAt - updatedAt - } -`; - -export const ORDERS_QUERY = gql` - ${ORDER_FRAGMENT} - query Orders($partyId: ID!) { - party(id: $partyId) { - id - orders { - ...OrderFields - } - } - } -`; - -export const ORDERS_SUB = gql` - ${ORDER_FRAGMENT} - subscription OrderSub($partyId: ID!) { - orders(partyId: $partyId) { - ...OrderFields - } - } -`; - -interface UseOrders { - orders: OrderFields[]; - error: Error | null; - loading: boolean; -} - -export const useOrders = (partyId: string): UseOrders => { - const client = useApolloClient(); - const [orders, setOrders] = useState([]); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const mergeOrders = useCallback((update: OrderFields[]) => { - // A subscription payload can contain multiple updates for a single order so we need to first - // sort them by updatedAt (or createdAt if the order hasn't been updated) with the newest first, - // then use uniqBy, which selects the first occuring order for an id to ensure we only get the latest order - setOrders((curr) => { - const sorted = orderBy( - [...curr, ...update], - (o) => { - if (!o.updatedAt) return new Date(o.createdAt).getTime(); - return new Date(o.updatedAt).getTime(); - }, - 'desc' - ); - return uniqBy(sorted, 'id'); - }); - }, []); - - // Make initial fetch - useEffect(() => { - const fetchOrders = async () => { - if (!partyId) return; - - setLoading(true); - - try { - const res = await client.query({ - query: ORDERS_QUERY, - variables: { partyId }, - }); - - if (!res.data.party?.orders?.length) return; - - mergeOrders(res.data.party.orders); - } catch (err) { - setError( - err instanceof Error ? err : new Error('Something went wrong') - ); - } finally { - setLoading(false); - } - }; - - fetchOrders(); - }, [mergeOrders, partyId, client]); - - // Start subscription - useEffect(() => { - if (!partyId) return; - - const sub = client - .subscribe({ - query: ORDERS_SUB, - variables: { partyId }, - }) - .subscribe(({ data }) => { - if (!data?.orders) { - return; - } - mergeOrders(data.orders); - }); - - return () => { - if (sub) { - sub.unsubscribe(); - } - }; - }, [client, partyId, mergeOrders]); - - return { orders, error, loading }; -}; diff --git a/libs/ui-toolkit/src/components/async-renderer/async-renderer.tsx b/libs/ui-toolkit/src/components/async-renderer/async-renderer.tsx index 451b25354..6fe2e9925 100644 --- a/libs/ui-toolkit/src/components/async-renderer/async-renderer.tsx +++ b/libs/ui-toolkit/src/components/async-renderer/async-renderer.tsx @@ -8,8 +8,7 @@ interface AsyncRendererProps { children: (data: T) => ReactNode; } -// eslint-disable-next-line -export function AsyncRenderer({ +export function AsyncRenderer({ loading, error, data, diff --git a/libs/ui-toolkit/src/components/loader/loader.tsx b/libs/ui-toolkit/src/components/loader/loader.tsx index 9d7a46935..fdedc65aa 100644 --- a/libs/ui-toolkit/src/components/loader/loader.tsx +++ b/libs/ui-toolkit/src/components/loader/loader.tsx @@ -1,6 +1,10 @@ import { useEffect, useState } from 'react'; -export const Loader = () => { +interface LoaderProps { + size?: 'small' | 'large'; +} + +export const Loader = ({ size = 'large' }: LoaderProps) => { const [, forceRender] = useState(false); useEffect(() => { @@ -11,13 +15,17 @@ export const Loader = () => { return () => clearInterval(interval); }, []); + const wrapperClasses = size === 'small' ? 'w-[15px] h-[15px]' : 'w-64 h-64'; + const gridItemClasses = size === 'small' ? 'w-[5px] h-[5px]' : 'w-16 h-16'; + const items = size === 'small' ? 9 : 16; + return (
-
- {new Array(16).fill(null).map((_, i) => { +
+ {new Array(items).fill(null).map((_, i) => { return (
0.75 ? 1 : 0, diff --git a/netlify.toml b/netlify.toml index d31ba46ec..7abf75d68 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,2 +1,5 @@ [build] - ignore = "node ignore-netlify-build.js" \ No newline at end of file + ignore = "node ignore-netlify-build.js" + +[functions] + included_files = ["!node_modules/@sentry/cli/sentry-cli"] \ No newline at end of file