Skip to content

Commit

Permalink
feat(widgets): allow chains to be disabled in ChainSearchMenu (hyperl…
Browse files Browse the repository at this point in the history
…ane-xyz#5581)

### Description

<!--
What's included in this PR?
-->
Updates `<ChainSearchMenu />` to disable chains when its status is
`disabled`

- Add `showDisabledChains` boolean to props. When false it will make the
chain items unable to be selected. Defaults to `true`
- Add disabled to the chain metadata because its required by
`SearchMenu` component
- Now disabled items will be shown at the bottom
- New storybook for this use case

### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

<!--
- Fixes #[issue number here]
-->

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->
Yes
### Testing

<!--
What kind of testing have these changes undergone?

None/Manual/Unit Tests
-->
Manual and visual testing with storybook
  • Loading branch information
Xaroz authored Feb 27, 2025
1 parent 6723512 commit ae8f7c6
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/witty-houses-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperlane-xyz/widgets': minor
---

Allow chains to be disabled in ChainSearchMenu
55 changes: 49 additions & 6 deletions typescript/widgets/src/chains/ChainSearchMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {
ChainMap,
ChainMetadata,
ChainName,
ChainStatus,
mergeChainMetadataMap,
} from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ProtocolType, objMap } from '@hyperlane-xyz/utils';

import {
SearchMenu,
Expand Down Expand Up @@ -64,6 +65,10 @@ export interface ChainSearchMenuProps {
showAddChainButton?: boolean;
// Field by which data will be sorted by default
defaultSortField?: DefaultSortField;
/**
* Allow chains to be shown as disabled. Defaults to `false`
*/
shouldDisableChains?: boolean;
}

export function ChainSearchMenu({
Expand All @@ -76,6 +81,7 @@ export function ChainSearchMenu({
showAddChainButton,
showAddChainMenu,
defaultSortField,
shouldDisableChains = false,
}: ChainSearchMenuProps) {
const [drilldownChain, setDrilldownChain] = useState<ChainName | undefined>(
showChainDetails,
Expand All @@ -88,11 +94,22 @@ export function ChainSearchMenu({
chainMetadata,
overrideChainMetadata,
);
return { mergedMetadata, listData: Object.values(mergedMetadata) };
}, [chainMetadata, overrideChainMetadata]);
const disabledChainMetadata = getDisabledChains(
mergedMetadata,
shouldDisableChains,
);
return {
mergedMetadata: disabledChainMetadata,
listData: Object.values(disabledChainMetadata),
};
}, [chainMetadata, overrideChainMetadata, shouldDisableChains]);

const { ListComponent, searchFn, sortOptions, defaultSortState } =
useCustomizedListItems(customListItemField, defaultSortField);
useCustomizedListItems(
customListItemField,
shouldDisableChains,
defaultSortField,
);

if (drilldownChain && mergedMetadata[drilldownChain]) {
const isLocalOverrideChain = !chainMetadata[drilldownChain];
Expand Down Expand Up @@ -225,12 +242,14 @@ function chainSearch({
sort,
filter,
customListItemField,
shouldDisableChains,
}: {
data: ChainMetadata[];
query: string;
sort: SortState<ChainSortByOption>;
filter: ChainFilterState;
customListItemField?: CustomListItemField;
shouldDisableChains?: boolean;
}) {
const queryFormatted = query.trim().toLowerCase();
return (
Expand All @@ -257,6 +276,14 @@ function chainSearch({
})
// Sort options
.sort((c1, c2) => {
if (shouldDisableChains) {
// If one chain is disabled and the other is not, place the disabled chain at the bottom
const c1Disabled = c1.availability?.status === ChainStatus.Disabled;
const c2Disabled = c2.availability?.status === ChainStatus.Disabled;
if (c1Disabled && !c2Disabled) return 1;
if (!c1Disabled && c2Disabled) return -1;
}

// Special case handling for if the chains are being sorted by the
// custom field provided to ChainSearchMenu
if (customListItemField && sort.sortBy === customListItemField.header) {
Expand Down Expand Up @@ -290,6 +317,7 @@ function chainSearch({
*/
function useCustomizedListItems(
customListItemField,
shouldDisableChains: boolean,
defaultSortField?: DefaultSortField,
) {
// Create closure of ChainListItem but with customField pre-bound
Expand All @@ -303,8 +331,8 @@ function useCustomizedListItems(
// Bind the custom field to the search function
const searchFn = useCallback(
(args: Parameters<typeof chainSearch>[0]) =>
chainSearch({ ...args, customListItemField }),
[customListItemField],
chainSearch({ ...args, shouldDisableChains, customListItemField }),
[customListItemField, shouldDisableChains],
);

// Merge the custom field into the sort options if a custom field exists
Expand Down Expand Up @@ -333,3 +361,18 @@ function useCustomizedListItems(

return { ListComponent, searchFn, sortOptions, defaultSortState };
}

function getDisabledChains(
chainMetadata: ChainMap<ChainMetadata>,
shouldDisableChains: boolean,
) {
if (!shouldDisableChains) return chainMetadata;

return objMap(chainMetadata, (_, chain) => {
if (chain.availability?.status === ChainStatus.Disabled) {
return { ...chain, disabled: true };
}

return chain;
});
}
1 change: 1 addition & 0 deletions typescript/widgets/src/components/SearchMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export function SearchMenu<
e.stopPropagation();
if (results.length === 1) {
const item = results[0];
if (item.disabled) return;
isEditMode ? onClickEditItem(item) : onClickItem(item);
}
},
Expand Down
35 changes: 35 additions & 0 deletions typescript/widgets/src/stories/ChainSearchMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meta, StoryObj } from '@storybook/react';

import { chainMetadata } from '@hyperlane-xyz/registry';
import { ChainDisabledReason, ChainStatus } from '@hyperlane-xyz/sdk';
import { pick } from '@hyperlane-xyz/utils';

import {
Expand Down Expand Up @@ -84,3 +85,37 @@ export const WithOverrideChain = {
showAddChainButton: true,
},
} satisfies Story;

export const WithDisabledChains = {
args: {
chainMetadata: pick(chainMetadata, ['alfajores', 'base']),
overrideChainMetadata: {
arbitrum: {
...chainMetadata['arbitrum'],
availability: {
status: ChainStatus.Disabled,
reasons: [ChainDisabledReason.Deprecated],
},
},
ethereum: {
...chainMetadata['ethereum'],
availability: {
status: ChainStatus.Disabled,
},
},
},
onChangeOverrideMetadata: () => {},
showAddChainButton: true,
defaultSortField: 'custom',
shouldDisableChains: true,
customListItemField: {
header: 'Warp Routes',
data: {
alfajores: { display: '1 token', sortValue: 1 },
arbitrum: { display: '2 tokens', sortValue: 2 },
ethereum: { display: '1 token', sortValue: 1 },
base: { display: '2 tokens', sortValue: 2 },
},
},
},
} satisfies Story;

0 comments on commit ae8f7c6

Please sign in to comment.