Commit 3a9f3d18 authored by tom's avatar tom

contract ABI filters

parent 27872bfe
...@@ -32,7 +32,7 @@ const RESTRICTED_MODULES = { ...@@ -32,7 +32,7 @@ const RESTRICTED_MODULES = {
{ {
name: '@chakra-ui/react', name: '@chakra-ui/react',
importNames: [ importNames: [
'Menu', 'useToast', 'useDisclosure', 'useClipboard', 'Tooltip', 'Skeleton', 'IconButton', 'Button', 'Link', 'Tag', 'Switch', 'Menu', 'useToast', 'useDisclosure', 'useClipboard', 'Tooltip', 'Skeleton', 'IconButton', 'Button', 'ButtonGroup', 'Link', 'Tag', 'Switch',
'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter', 'Image', 'Popover', 'PopoverTrigger', 'PopoverContent', 'PopoverBody', 'PopoverFooter',
'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer', 'DrawerRoot', 'DrawerBody', 'DrawerContent', 'DrawerOverlay', 'DrawerBackdrop', 'DrawerTrigger', 'Drawer',
'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription', 'Alert', 'AlertIcon', 'AlertTitle', 'AlertDescription',
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
"npm": "10.9.0" "npm": "10.9.0"
}, },
"scripts": { "scripts": {
"postinstall": "yarn chakra:typegen",
"dev": "./tools/scripts/dev.sh", "dev": "./tools/scripts/dev.sh",
"dev:preset": "./tools/scripts/dev.preset.sh", "dev:preset": "./tools/scripts/dev.preset.sh",
"dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js", "dev:preset:sync": "tsc -p ./tools/preset-sync/tsconfig.json && node ./tools/preset-sync/index.js",
...@@ -44,7 +45,7 @@ ...@@ -44,7 +45,7 @@
"@blockscout/bens-types": "1.4.1", "@blockscout/bens-types": "1.4.1",
"@blockscout/stats-types": "2.0.0", "@blockscout/stats-types": "2.0.0",
"@blockscout/visualizer-types": "0.2.0", "@blockscout/visualizer-types": "0.2.0",
"@chakra-ui/react": "3.4.0", "@chakra-ui/react": "3.8.0",
"@cloudnouns/kit": "1.1.6", "@cloudnouns/kit": "1.1.6",
"@emotion/react": "11.14.0", "@emotion/react": "11.14.0",
"@growthbook/growthbook-react": "0.21.0", "@growthbook/growthbook-react": "0.21.0",
......
import type { ButtonProps as ChakraButtonProps } from '@chakra-ui/react'; import type { ButtonProps as ChakraButtonProps, ButtonGroupProps as ChakraButtonGroupProps } from '@chakra-ui/react';
import { import {
AbsoluteCenter, AbsoluteCenter,
Button as ChakraButton, Button as ChakraButton,
ButtonGroup as ChakraButtonGroup,
Span, Span,
Spinner, Spinner,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
import { Skeleton } from './skeleton';
interface ButtonLoadingProps { interface ButtonLoadingProps {
loading?: boolean; loading?: boolean;
loadingText?: React.ReactNode; loadingText?: React.ReactNode;
...@@ -61,3 +64,61 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ...@@ -61,3 +64,61 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
); );
}, },
); );
export interface ButtonGroupProps extends ChakraButtonGroupProps {}
export const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>(
function ButtonGroup(props, ref) {
const { ...rest } = props;
return (
<ChakraButtonGroup ref={ ref } { ...rest }/>
);
},
);
export interface ButtonGroupRadioProps extends Omit<ChakraButtonGroupProps, 'children' | 'onChange'> {
children: Array<React.ReactElement<ButtonProps>>;
onChange?: (value: string) => void;
defaultValue?: string;
loading?: boolean;
}
export const ButtonGroupRadio = React.forwardRef<HTMLDivElement, ButtonGroupRadioProps>(
function ButtonGroupRadio(props, ref) {
const { children, onChange, variant = 'segmented', defaultValue, loading = false, ...rest } = props;
const firstChildValue = React.useMemo(() => {
const firstChild = Array.isArray(children) ? children[0] : undefined;
return typeof firstChild?.props.value === 'string' ? firstChild.props.value : undefined;
}, [ children ]);
const [ value, setValue ] = React.useState<string | undefined>(defaultValue ?? firstChildValue);
const handleItemClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
const value = event.currentTarget.value;
setValue(value);
onChange?.(value);
}, [ onChange ]);
const clonedChildren = React.Children.map(children, (child: React.ReactElement<ButtonProps>) => {
return React.cloneElement(child, {
onClick: handleItemClick,
selected: value === child.props.value,
variant,
});
});
return (
<Skeleton loading={ loading }>
<ChakraButtonGroup
ref={ ref }
gap={ 0 }
{ ...rest }
>
{ clonedChildren }
</ChakraButtonGroup>
</Skeleton>
);
},
);
...@@ -140,7 +140,7 @@ export const SelectRoot = React.forwardRef< ...@@ -140,7 +140,7 @@ export const SelectRoot = React.forwardRef<
<ChakraSelect.Root <ChakraSelect.Root
{ ...props } { ...props }
ref={ ref } ref={ ref }
positioning={{ sameWidth: false, ...props.positioning }} positioning={{ sameWidth: false, ...props.positioning, offset: { mainAxis: 4, ...props.positioning?.offset } }}
> >
{ props.asChild ? ( { props.asChild ? (
props.children props.children
......
...@@ -45,6 +45,15 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -45,6 +45,15 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } }, DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } },
}, },
}, },
segmented: {
fg: {
DEFAULT: { value: { _light: '{colors.blue.600}', _dark: '{colors.blue.300}' } },
selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } },
},
border: {
DEFAULT: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } },
},
},
hero: { hero: {
bg: { bg: {
DEFAULT: { DEFAULT: {
......
...@@ -131,6 +131,36 @@ export const recipe = defineRecipe({ ...@@ -131,6 +131,36 @@ export const recipe = defineRecipe({
}, },
}, },
}, },
segmented: {
bg: 'transparent',
color: 'button.segmented.fg',
borderColor: 'button.segmented.border',
borderWidth: '2px',
borderStyle: 'solid',
borderRadius: 'none',
_hover: {
color: 'link.primary.hover',
},
_selected: {
bg: 'button.segmented.border',
color: 'button.segmented.fg.selected',
_hover: {
bg: 'button.segmented.border',
color: 'button.segmented.fg.selected',
},
},
_notFirst: {
borderLeftWidth: '0',
},
_first: {
borderTopLeftRadius: 'base',
borderBottomLeftRadius: 'base',
},
_last: {
borderTopRightRadius: 'base',
borderBottomRightRadius: 'base',
},
},
plain: { plain: {
bg: 'transparent', bg: 'transparent',
color: 'inherit', color: 'inherit',
......
...@@ -29,12 +29,12 @@ export const recipe = defineSlotRecipe({ ...@@ -29,12 +29,12 @@ export const recipe = defineSlotRecipe({
}, },
}, },
trigger: { trigger: {
fontWeight: '600',
outline: '0', outline: '0',
minW: 'var(--tabs-height)', minW: 'var(--tabs-height)',
height: 'var(--tabs-height)', height: 'var(--tabs-height)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
fontWeight: 'medium',
position: 'relative', position: 'relative',
cursor: 'button', cursor: 'button',
gap: '2', gap: '2',
...@@ -130,7 +130,6 @@ export const recipe = defineSlotRecipe({ ...@@ -130,7 +130,6 @@ export const recipe = defineSlotRecipe({
variant: { variant: {
solid: { solid: {
trigger: { trigger: {
fontWeight: '600',
borderRadius: 'base', borderRadius: 'base',
color: 'tabs.solid.fg', color: 'tabs.solid.fg',
bg: 'transparent', bg: 'transparent',
...@@ -157,7 +156,6 @@ export const recipe = defineSlotRecipe({ ...@@ -157,7 +156,6 @@ export const recipe = defineSlotRecipe({
}, },
}, },
trigger: { trigger: {
fontWeight: '500',
color: 'tabs.secondary.fg', color: 'tabs.secondary.fg',
bg: 'transparent', bg: 'transparent',
borderWidth: '2px', borderWidth: '2px',
......
...@@ -20,7 +20,7 @@ const AddressContract = ({ tabs, isLoading, shouldRender }: Props) => { ...@@ -20,7 +20,7 @@ const AddressContract = ({ tabs, isLoading, shouldRender }: Props) => {
} }
return ( return (
<RoutedTabs tabs={ tabs } variant="outline" colorScheme="gray" size="sm" listProps={ TAB_LIST_PROPS } isLoading={ isLoading }/> <RoutedTabs tabs={ tabs } variant="secondary" size="sm" listProps={ TAB_LIST_PROPS } isLoading={ isLoading }/>
); );
}; };
......
import { chakra, Flex } from '@chakra-ui/react'; import { chakra, createListCollection, Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { SelectContent, SelectControl, SelectItem, SelectRoot, SelectValueText } from 'toolkit/chakra/select';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import LinkNewTab from 'ui/shared/links/LinkNewTab'; import LinkNewTab from 'ui/shared/links/LinkNewTab';
import Select from 'ui/shared/select/Select';
export interface Item { export interface Item {
address: string; address: string;
...@@ -25,19 +25,20 @@ interface Props { ...@@ -25,19 +25,20 @@ interface Props {
const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, items, isLoading, label }: Props) => { const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, items, isLoading, label }: Props) => {
const handleItemSelect = React.useCallback((value: string) => { const handleItemSelect = React.useCallback(({ value }: { value: Array<string> }) => {
const nextOption = items.find(({ address }) => address === value); const nextOption = items.find(({ address }) => address === value[0]);
if (nextOption) { if (nextOption) {
onItemSelect(nextOption); onItemSelect(nextOption);
} }
}, [ items, onItemSelect ]); }, [ items, onItemSelect ]);
const options = React.useMemo(() => { const collection = React.useMemo(() => {
return items.map(({ address, name }) => ({ label: name || address, value: address })); const options = items.map(({ address, name }) => ({ label: name || address, value: address }));
return createListCollection({ items: options });
}, [ items ]); }, [ items ]);
if (isLoading) { if (isLoading) {
return <Skeleton h={ 6 } w={{ base: '300px', lg: '500px' }} className={ className }/>; return <Skeleton loading h={ 6 } w={{ base: '300px', lg: '500px' }} className={ className }/>;
} }
if (items.length === 0) { if (items.length === 0) {
...@@ -58,15 +59,23 @@ const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect, ...@@ -58,15 +59,23 @@ const ContractSourceAddressSelector = ({ className, selectedItem, onItemSelect,
return ( return (
<Flex columnGap={ 3 } rowGap={ 2 } alignItems="center" className={ className }> <Flex columnGap={ 3 } rowGap={ 2 } alignItems="center" className={ className }>
<chakra.span fontWeight={ 500 } fontSize="sm">{ label }</chakra.span> <chakra.span fontWeight={ 500 } fontSize="sm">{ label }</chakra.span>
<Select <SelectRoot
options={ options } collection={ collection }
name="contract-source-address" variant="outline"
defaultValue={ selectedItem.address } defaultValue={ [ selectedItem.address ] }
onChange={ handleItemSelect } onValueChange={ handleItemSelect }
isLoading={ isLoading } >
maxW={{ base: '180px', lg: 'none' }} <SelectControl maxW={{ base: '180px', lg: 'none' }} loading={ isLoading }>
fontWeight={ 600 } <SelectValueText placeholder="Select contract"/>
/> </SelectControl>
<SelectContent>
{ collection.items.map((item) => (
<SelectItem item={ item } key={ item.value }>
{ item.label }
</SelectItem>
)) }
</SelectContent>
</SelectRoot>
<Flex columnGap={ 2 } alignItems="center"> <Flex columnGap={ 2 } alignItems="center">
<CopyToClipboard text={ selectedItem.address } ml={ 0 }/> <CopyToClipboard text={ selectedItem.address } ml={ 0 }/>
<LinkNewTab <LinkNewTab
......
import { Alert, Button, Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useWeb3Wallet from 'lib/web3/useWallet'; import useWeb3Wallet from 'lib/web3/useWallet';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
interface Props { interface Props {
...@@ -25,7 +27,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => { ...@@ -25,7 +27,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => {
onClick={ web3Wallet.connect } onClick={ web3Wallet.connect }
size="sm" size="sm"
variant="outline" variant="outline"
isLoading={ web3Wallet.isOpen } loading={ web3Wallet.isOpen }
loadingText="Connect wallet" loadingText="Connect wallet"
> >
Connect wallet Connect wallet
...@@ -52,7 +54,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => { ...@@ -52,7 +54,7 @@ const ContractConnectWallet = ({ isLoading }: Props) => {
})(); })();
return ( return (
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<Alert status={ web3Wallet.address ? 'success' : 'warning' }> <Alert status={ web3Wallet.address ? 'success' : 'warning' }>
{ content } { content }
</Alert> </Alert>
......
import { Alert } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Alert } from 'toolkit/chakra/alert';
interface Props { interface Props {
isLoading?: boolean; isLoading?: boolean;
} }
const ContractCustomAbiAlert = ({ isLoading }: Props) => { const ContractCustomAbiAlert = ({ isLoading }: Props) => {
return ( return (
<Skeleton isLoaded={ !isLoading }> <Alert status="warning" loading={ isLoading }>
<Alert status="warning">
Note: Contract with custom ABI is only meant for debugging purpose and it is the user’s responsibility to ensure that the provided ABI Note: Contract with custom ABI is only meant for debugging purpose and it is the user’s responsibility to ensure that the provided ABI
matches the contract, otherwise errors may occur or results returned may be incorrect. matches the contract, otherwise errors may occur or results returned may be incorrect.
Blockscout is not responsible for any losses that arise from the use of Read & Write contract. Blockscout is not responsible for any losses that arise from the use of Read & Write contract.
</Alert> </Alert>
</Skeleton>
); );
}; };
......
import { Button, Flex, useDisclosure } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
...@@ -7,8 +7,10 @@ import type { SmartContract } from 'types/api/contract'; ...@@ -7,8 +7,10 @@ import type { SmartContract } from 'types/api/contract';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import { Button } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal'; import CustomAbiModal from 'ui/customAbi/CustomAbiModal/CustomAbiModal';
import Skeleton from 'ui/shared/chakra/Skeleton';
import RawDataSnippet from 'ui/shared/RawDataSnippet'; import RawDataSnippet from 'ui/shared/RawDataSnippet';
import AuthGuard from 'ui/snippets/auth/AuthGuard'; import AuthGuard from 'ui/snippets/auth/AuthGuard';
import useIsAuth from 'ui/snippets/auth/useIsAuth'; import useIsAuth from 'ui/snippets/auth/useIsAuth';
...@@ -58,7 +60,7 @@ const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => { ...@@ -58,7 +60,7 @@ const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => {
const updateButton = React.useMemo(() => { const updateButton = React.useMemo(() => {
return ( return (
<Skeleton isLoaded={ !isLoading } ml="auto" mr="3" borderRadius="base"> <Skeleton loading={ isLoading } ml="auto" mr="3" borderRadius="base">
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
...@@ -91,19 +93,19 @@ const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => { ...@@ -91,19 +93,19 @@ const ContractMethodsCustom = ({ isLoading: isLoadingProp }: Props) => {
onChange={ filters.onChange } onChange={ filters.onChange }
isLoading={ isLoading } isLoading={ isLoading }
/> />
<ContractMethodsContainer isLoading={ isLoading } isEmpty={ abi.length === 0 } type={ filters.methodType }> { /* <ContractMethodsContainer isLoading={ isLoading } isEmpty={ abi.length === 0 } type={ filters.methodType }>
<ContractAbi abi={ abi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/> <ContractAbi abi={ abi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/>
</ContractMethodsContainer> </ContractMethodsContainer> */ }
</> </>
) : ( ) : (
<> <>
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
Add custom ABIs for this contract and access when logged into your account. Helpful for debugging, Add custom ABIs for this contract and access when logged into your account. Helpful for debugging,
functional testing and contract interaction. functional testing and contract interaction.
</Skeleton> </Skeleton>
<AuthGuard onAuthSuccess={ modal.onOpen }> <AuthGuard onAuthSuccess={ modal.onOpen }>
{ ({ onClick }) => ( { ({ onClick }) => (
<Skeleton isLoaded={ !isLoading } w="fit-content"> <Skeleton loading={ isLoading } w="fit-content">
<Button <Button
size="sm" size="sm"
onClick={ onClick } onClick={ onClick }
......
...@@ -3,8 +3,8 @@ import React from 'react'; ...@@ -3,8 +3,8 @@ import React from 'react';
import type { MethodType } from './types'; import type { MethodType } from './types';
import { ButtonGroupRadio, Button } from 'toolkit/chakra/button';
import FilterInput from 'ui/shared/filters/FilterInput'; import FilterInput from 'ui/shared/filters/FilterInput';
import RadioButtonGroup from 'ui/shared/radioButtonGroup/RadioButtonGroup';
import type { MethodsFilters } from './useMethodsFilters'; import type { MethodsFilters } from './useMethodsFilters';
import { TYPE_FILTER_OPTIONS } from './utils'; import { TYPE_FILTER_OPTIONS } from './utils';
...@@ -18,8 +18,8 @@ interface Props { ...@@ -18,8 +18,8 @@ interface Props {
const ContractMethodsFilters = ({ defaultMethodType, defaultSearchTerm, onChange, isLoading }: Props) => { const ContractMethodsFilters = ({ defaultMethodType, defaultSearchTerm, onChange, isLoading }: Props) => {
const handleTypeChange = React.useCallback((value: MethodType) => { const handleTypeChange = React.useCallback((value: string) => {
onChange({ type: 'method_type', value }); onChange({ type: 'method_type', value: value as MethodType });
}, [ onChange ]); }, [ onChange ]);
const handleSearchTermChange = React.useCallback((value: string) => { const handleSearchTermChange = React.useCallback((value: string) => {
...@@ -28,21 +28,25 @@ const ContractMethodsFilters = ({ defaultMethodType, defaultSearchTerm, onChange ...@@ -28,21 +28,25 @@ const ContractMethodsFilters = ({ defaultMethodType, defaultSearchTerm, onChange
return ( return (
<Flex columnGap={ 3 } rowGap={ 3 } flexDir={{ base: 'column', lg: 'row' }}> <Flex columnGap={ 3 } rowGap={ 3 } flexDir={{ base: 'column', lg: 'row' }}>
<RadioButtonGroup<MethodType> <ButtonGroupRadio
name="contract-methods-filter"
defaultValue={ defaultMethodType } defaultValue={ defaultMethodType }
options={ TYPE_FILTER_OPTIONS }
onChange={ handleTypeChange } onChange={ handleTypeChange }
w={{ lg: 'fit-content' }} w={{ lg: 'fit-content' }}
isLoading={ isLoading } loading={ isLoading }
/> >
{ TYPE_FILTER_OPTIONS.map((option) => (
<Button key={ option.value } value={ option.value } size="sm" px={ 3 }>
{ option.title }
</Button>
)) }
</ButtonGroupRadio>
<FilterInput <FilterInput
initialValue={ defaultSearchTerm } initialValue={ defaultSearchTerm }
onChange={ handleSearchTermChange } onChange={ handleSearchTermChange }
placeholder="Search by method name" placeholder="Search by method name"
w={{ base: '100%', lg: '450px' }} w={{ base: '100%', lg: '450px' }}
size="xs" size="sm"
isLoading={ isLoading } loading={ isLoading }
/> />
</Flex> </Flex>
); );
......
...@@ -58,7 +58,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }: ...@@ -58,7 +58,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }:
isLoading={ isInitialLoading } isLoading={ isInitialLoading }
/> />
</div> </div>
<ContractMethodsContainer { /* <ContractMethodsContainer
key={ selectedItem.address } key={ selectedItem.address }
isLoading={ isInitialLoading || contractQuery.isPending } isLoading={ isInitialLoading || contractQuery.isPending }
isEmpty={ abi.length === 0 } isEmpty={ abi.length === 0 }
...@@ -72,7 +72,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }: ...@@ -72,7 +72,7 @@ const ContractMethodsProxy = ({ implementations, isLoading: isInitialLoading }:
visibleItems={ filters.visibleItems } visibleItems={ filters.visibleItems }
sourceAddress={ selectedItem.address } sourceAddress={ selectedItem.address }
/> />
</ContractMethodsContainer> </ContractMethodsContainer> */ }
</Flex> </Flex>
); );
}; };
......
...@@ -36,9 +36,9 @@ const ContractMethodsRegular = ({ abi, isLoading }: Props) => { ...@@ -36,9 +36,9 @@ const ContractMethodsRegular = ({ abi, isLoading }: Props) => {
onChange={ filters.onChange } onChange={ filters.onChange }
isLoading={ isLoading } isLoading={ isLoading }
/> />
<ContractMethodsContainer isLoading={ isLoading } isEmpty={ formattedAbi.length === 0 } type={ filters.methodType }> { /* <ContractMethodsContainer isLoading={ isLoading } isEmpty={ formattedAbi.length === 0 } type={ filters.methodType }>
<ContractAbi abi={ formattedAbi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/> <ContractAbi abi={ formattedAbi } tab={ tab } addressHash={ addressHash } visibleItems={ filters.visibleItems }/>
</ContractMethodsContainer> </ContractMethodsContainer> */ }
</Flex> </Flex>
); );
}; };
......
...@@ -80,21 +80,21 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder ...@@ -80,21 +80,21 @@ export default function useContractTabs(data: Address | undefined, isPlaceholder
component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>, component: <ContractDetails mainContractQuery={ contractQuery } channel={ channel } addressHash={ data.hash }/>,
subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>, subTabs: CONTRACT_DETAILS_TAB_IDS as unknown as Array<string>,
}, },
// contractQuery.data?.abi && { contractQuery.data?.abi && {
// id: [ 'read_write_contract' as const, 'read_contract' as const, 'write_contract' as const ], id: [ 'read_write_contract' as const, 'read_contract' as const, 'write_contract' as const ],
// title: 'Read/Write contract', title: 'Read/Write contract',
// component: <ContractMethodsRegular abi={ contractQuery.data.abi } isLoading={ contractQuery.isPlaceholderData }/>, component: <ContractMethodsRegular abi={ contractQuery.data.abi } isLoading={ contractQuery.isPlaceholderData }/>,
// }, },
// verifiedImplementations.length > 0 && { verifiedImplementations.length > 0 && {
// id: [ 'read_write_proxy' as const, 'read_proxy' as const, 'write_proxy' as const ], id: [ 'read_write_proxy' as const, 'read_proxy' as const, 'write_proxy' as const ],
// title: 'Read/Write proxy', title: 'Read/Write proxy',
// component: <ContractMethodsProxy implementations={ verifiedImplementations } isLoading={ contractQuery.isPlaceholderData }/>, component: <ContractMethodsProxy implementations={ verifiedImplementations } isLoading={ contractQuery.isPlaceholderData }/>,
// }, },
// config.features.account.isEnabled && { config.features.account.isEnabled && {
// id: [ 'read_write_custom_methods' as const, 'read_custom_methods' as const, 'write_custom_methods' as const ], id: [ 'read_write_custom_methods' as const, 'read_custom_methods' as const, 'write_custom_methods' as const ],
// title: 'Custom ABI', title: 'Custom ABI',
// component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>, component: <ContractMethodsCustom isLoading={ contractQuery.isPlaceholderData }/>,
// }, },
// hasMudTab && { // hasMudTab && {
// id: 'mud_system' as const, // id: 'mud_system' as const,
// title: 'MUD System', // title: 'MUD System',
......
import { import { Box } from '@chakra-ui/react';
Box,
Button,
} from '@chakra-ui/react';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
...@@ -13,6 +10,7 @@ import type { ResourceErrorAccount } from 'lib/api/resources'; ...@@ -13,6 +10,7 @@ import type { ResourceErrorAccount } from 'lib/api/resources';
import { resourceKey } from 'lib/api/resources'; import { resourceKey } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import { Button } from 'toolkit/chakra/button';
import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress'; import FormFieldAddress from 'ui/shared/forms/fields/FormFieldAddress';
import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; import FormFieldText from 'ui/shared/forms/fields/FormFieldText';
...@@ -23,7 +21,7 @@ export type FormData = CustomAbi | { ...@@ -23,7 +21,7 @@ export type FormData = CustomAbi | {
type Props = { type Props = {
data: FormData; data: FormData;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess?: () => Promise<void>; onSuccess?: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void; setAlertVisible: (isAlertVisible: boolean) => void;
}; };
...@@ -36,7 +34,7 @@ type Inputs = { ...@@ -36,7 +34,7 @@ type Inputs = {
const NAME_MAX_LENGTH = 255; const NAME_MAX_LENGTH = 255;
const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisible }) => { const CustomAbiForm: React.FC<Props> = ({ data, onOpenChange, onSuccess, setAlertVisible }) => {
const formApi = useForm<Inputs>({ const formApi = useForm<Inputs>({
defaultValues: { defaultValues: {
contract_address_hash: data?.contract_address_hash || '', contract_address_hash: data?.contract_address_hash || '',
...@@ -82,7 +80,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -82,7 +80,7 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
return [ response, ...(prevData || []) ]; return [ response, ...(prevData || []) ];
}); });
await onSuccess?.(); await onSuccess?.();
onClose(); onOpenChange({ open: false });
}, },
onError: (error: ResourceErrorAccount<CustomAbiErrors>) => { onError: (error: ResourceErrorAccount<CustomAbiErrors>) => {
const errorMap = error.payload?.errors; const errorMap = error.payload?.errors;
...@@ -110,15 +108,15 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -110,15 +108,15 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
<FormFieldAddress<Inputs> <FormFieldAddress<Inputs>
name="contract_address_hash" name="contract_address_hash"
placeholder="Smart contract address (0x...)" placeholder="Smart contract address (0x...)"
isRequired required
bgColor="dialog.bg" bgColor="dialog.bg"
isReadOnly={ Boolean(data && 'contract_address_hash' in data) } readOnly={ Boolean(data && 'contract_address_hash' in data) }
mb={ 5 } mb={ 5 }
/> />
<FormFieldText<Inputs> <FormFieldText<Inputs>
name="name" name="name"
placeholder="Project name" placeholder="Project name"
isRequired required
rules={{ rules={{
maxLength: NAME_MAX_LENGTH, maxLength: NAME_MAX_LENGTH,
}} }}
...@@ -128,10 +126,10 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -128,10 +126,10 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
<FormFieldText<Inputs> <FormFieldText<Inputs>
name="abi" name="abi"
placeholder="Custom ABI [{...}] (JSON format)" placeholder="Custom ABI [{...}] (JSON format)"
isRequired required
asComponent="Textarea" asComponent="Textarea"
bgColor="dialog.bg" bgColor="dialog.bg"
size="lg" size="2xl"
minH="300px" minH="300px"
mb={ 8 } mb={ 8 }
/> />
...@@ -139,8 +137,8 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi ...@@ -139,8 +137,8 @@ const CustomAbiForm: React.FC<Props> = ({ data, onClose, onSuccess, setAlertVisi
<Button <Button
size="lg" size="lg"
type="submit" type="submit"
isDisabled={ !formApi.formState.isDirty } disabled={ !formApi.formState.isDirty }
isLoading={ isPending } loading={ isPending }
> >
{ data && 'id' in data ? 'Save' : 'Create custom ABI' } { data && 'id' in data ? 'Save' : 'Create custom ABI' }
</Button> </Button>
......
...@@ -7,25 +7,25 @@ import FormModal from 'ui/shared/FormModal'; ...@@ -7,25 +7,25 @@ import FormModal from 'ui/shared/FormModal';
import CustomAbiForm, { type FormData } from './CustomAbiForm'; import CustomAbiForm, { type FormData } from './CustomAbiForm';
type Props = { type Props = {
isOpen: boolean; open: boolean;
onClose: () => void; onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess?: () => Promise<void>; onSuccess?: () => Promise<void>;
data: FormData; data: FormData;
}; };
const CustomAbiModal: React.FC<Props> = ({ isOpen, onClose, data, onSuccess }) => { const CustomAbiModal: React.FC<Props> = ({ open, onOpenChange, data, onSuccess }) => {
const title = data && 'id' in data ? 'Edit custom ABI' : 'New custom ABI'; const title = data && 'id' in data ? 'Edit custom ABI' : 'New custom ABI';
const text = !(data && 'id' in data) ? 'Double check the ABI matches the contract to prevent errors or incorrect results.' : ''; const text = !(data && 'id' in data) ? 'Double check the ABI matches the contract to prevent errors or incorrect results.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false); const [ isAlertVisible, setAlertVisible ] = useState(false);
const renderForm = useCallback(() => { const renderForm = useCallback(() => {
return <CustomAbiForm data={ data } onClose={ onClose } onSuccess={ onSuccess } setAlertVisible={ setAlertVisible }/>; return <CustomAbiForm data={ data } onOpenChange={ onOpenChange } onSuccess={ onSuccess } setAlertVisible={ setAlertVisible }/>;
}, [ data, onClose, onSuccess ]); }, [ data, onOpenChange, onSuccess ]);
return ( return (
<FormModal<CustomAbi> <FormModal<CustomAbi>
isOpen={ isOpen } open={ open }
onClose={ onClose } onOpenChange={ onOpenChange }
title={ title } title={ title }
text={ text } text={ text }
renderForm={ renderForm } renderForm={ renderForm }
......
import { chakra, Input, InputGroup, InputLeftElement, InputRightElement, useColorModeValue } from '@chakra-ui/react';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group';
import type { SkeletonProps } from 'toolkit/chakra/skeleton';
import { Skeleton } from 'toolkit/chakra/skeleton';
import ClearButton from 'ui/shared/ClearButton'; import ClearButton from 'ui/shared/ClearButton';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
type Props = { interface Props extends Omit<SkeletonProps, 'onChange' | 'loading'> {
onChange?: (searchTerm: string) => void; onChange?: (searchTerm: string) => void;
className?: string; loading?: boolean;
size?: 'xs' | 'sm' | 'md' | 'lg'; size?: 'sm' | 'md' | 'lg';
placeholder: string; placeholder: string;
initialValue?: string; initialValue?: string;
isLoading?: boolean;
type?: React.HTMLInputTypeAttribute; type?: React.HTMLInputTypeAttribute;
name?: string; name?: string;
}; };
const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialValue, isLoading, type, name }: Props) => { const FilterInput = ({ onChange, size = 'sm', placeholder, initialValue, type, name, loading = false, ...rest }: Props) => {
const [ filterQuery, setFilterQuery ] = useState(initialValue || ''); const [ filterQuery, setFilterQuery ] = useState(initialValue || '');
const inputRef = React.useRef<HTMLInputElement>(null); const inputRef = React.useRef<HTMLInputElement>(null);
const iconColor = useColorModeValue('blackAlpha.600', 'whiteAlpha.600');
const handleFilterQueryChange = useCallback((event: ChangeEvent<HTMLInputElement>) => { const handleFilterQueryChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target; const { value } = event.target;
...@@ -35,22 +35,25 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal ...@@ -35,22 +35,25 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal
inputRef?.current?.focus(); inputRef?.current?.focus();
}, [ onChange ]); }, [ onChange ]);
const startElement = <IconSvg name="search" color={{ _light: 'blackAlpha.600', _dark: 'whiteAlpha.600' }} boxSize={ 4 }/>;
const endElement = filterQuery ? <ClearButton onClick={ handleFilterQueryClear }/> : null;
return ( return (
<Skeleton <Skeleton
isLoaded={ !isLoading }
className={ className }
minW="250px" minW="250px"
borderRadius="base" borderRadius="base"
loading={ loading }
{ ...rest }
> >
<InputGroup <InputGroup
size={ size } startElement={ startElement }
startElementProps={{ px: 2 }}
startOffset="32px"
endElement={ endElement }
endElementProps={{ px: 0, w: '32px' }}
endOffset="32px"
> >
<InputLeftElement
pointerEvents="none"
>
<IconSvg name="search" color={ iconColor } boxSize={ 4 }/>
</InputLeftElement>
<Input <Input
ref={ inputRef } ref={ inputRef }
size={ size } size={ size }
...@@ -63,15 +66,9 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal ...@@ -63,15 +66,9 @@ const FilterInput = ({ onChange, className, size = 'sm', placeholder, initialVal
type={ type } type={ type }
name={ name } name={ name }
/> />
{ filterQuery ? (
<InputRightElement>
<ClearButton onClick={ handleFilterQueryClear }/>
</InputRightElement>
) : null }
</InputGroup> </InputGroup>
</Skeleton> </Skeleton>
); );
}; };
export default chakra(FilterInput); export default FilterInput;
...@@ -50,6 +50,7 @@ const FormFieldText = < ...@@ -50,6 +50,7 @@ const FormFieldText = <
<Textarea <Textarea
{ ...field } { ...field }
autoComplete="off" autoComplete="off"
flexGrow={ 1 }
{ ...inputProps as TextareaProps } { ...inputProps as TextareaProps }
onBlur={ handleBlur } onBlur={ handleBlur }
/> />
......
import { chakra, IconButton, Tooltip, useColorModeValue } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { IconButton } from 'toolkit/chakra/icon-button';
import { Link } from 'toolkit/chakra/link';
import { Tooltip } from 'toolkit/chakra/tooltip';
import IconSvg from '../IconSvg'; import IconSvg from '../IconSvg';
interface Props { interface Props {
...@@ -10,25 +14,21 @@ interface Props { ...@@ -10,25 +14,21 @@ interface Props {
} }
const LinkNewTab = ({ className, label, href }: Props) => { const LinkNewTab = ({ className, label, href }: Props) => {
const iconColor = useColorModeValue('gray.400', 'gray.500');
return ( return (
<Tooltip label={ label }> <Tooltip content={ label }>
<IconButton <IconButton
asChild
aria-label={ label ?? 'Open link' } aria-label={ label ?? 'Open link' }
icon={ <IconSvg name="open-link" boxSize={ 5 }/> } color="link.secondary"
w="20px" _hover={{ color: 'link.primary.hover' }}
h="20px"
color={ iconColor }
variant="simple"
display="inline-block"
flexShrink={ 0 }
as="a"
href={ href }
target="_blank"
className={ className } className={ className }
borderRadius={ 0 } borderRadius={ 0 }
/> >
<Link href={ href } target="_blank">
<IconSvg name="open-link" boxSize={ 5 }/>
</Link>
</IconButton>
</Tooltip> </Tooltip>
); );
}; };
......
import { chakra, ButtonGroup, Button, Flex, useRadio, useRadioGroup } from '@chakra-ui/react'; import { chakra, Flex, useRadioGroup } from '@chakra-ui/react';
import type { ChakraProps, UseRadioProps } from '@chakra-ui/react'; import type { ChakraProps, UseRadioProps } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import Skeleton from 'ui/shared/chakra/Skeleton'; import { Button, ButtonGroup } from 'toolkit/chakra/button';
import { Skeleton } from 'toolkit/chakra/skeleton';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
// TODO @tom2drum remove this component
type RadioItemProps = { type RadioItemProps = {
title: string; title: string;
icon?: IconName; icon?: IconName;
...@@ -20,10 +22,10 @@ type RadioItemProps = { ...@@ -20,10 +22,10 @@ type RadioItemProps = {
type RadioButtonProps = UseRadioProps & RadioItemProps; type RadioButtonProps = UseRadioProps & RadioItemProps;
const RadioButton = (props: RadioButtonProps) => { const RadioButton = (props: RadioButtonProps) => {
const { getInputProps, getRadioProps } = useRadio(props); // const { getInputProps, getRadioProps } = useRadio(props);
const input = getInputProps(); // const input = getInputProps();
const checkbox = getRadioProps(); // const checkbox = getRadioProps();
if (props.onlyIcon) { if (props.onlyIcon) {
return ( return (
...@@ -33,9 +35,9 @@ const RadioButton = (props: RadioButtonProps) => { ...@@ -33,9 +35,9 @@ const RadioButton = (props: RadioButtonProps) => {
variant="radio_group" variant="radio_group"
selected={ props.isChecked } selected={ props.isChecked }
> >
<input { ...input }/> { /* <input { ...input }/> */ }
<Flex <Flex
{ ...checkbox } // { ...checkbox }
> >
<IconSvg name={ props.icon } boxSize={ 5 }/> <IconSvg name={ props.icon } boxSize={ 5 }/>
</Flex> </Flex>
...@@ -50,10 +52,10 @@ const RadioButton = (props: RadioButtonProps) => { ...@@ -50,10 +52,10 @@ const RadioButton = (props: RadioButtonProps) => {
variant="radio_group" variant="radio_group"
selected={ props.isChecked } selected={ props.isChecked }
> >
<input { ...input }/> { /* <input { ...input }/> */ }
<Flex <Flex
alignItems="center" alignItems="center"
{ ...checkbox } // { ...checkbox }
> >
{ props.title } { props.title }
{ props.contentAfter } { props.contentAfter }
...@@ -63,7 +65,7 @@ const RadioButton = (props: RadioButtonProps) => { ...@@ -63,7 +65,7 @@ const RadioButton = (props: RadioButtonProps) => {
}; };
type RadioButtonGroupProps<T extends string> = { type RadioButtonGroupProps<T extends string> = {
onChange: (value: T) => void; onChange: ({ value }: { value: T }) => void;
name: string; name: string;
defaultValue: string; defaultValue: string;
options: Array<{ value: T } & RadioItemProps>; options: Array<{ value: T } & RadioItemProps>;
...@@ -73,14 +75,14 @@ type RadioButtonGroupProps<T extends string> = { ...@@ -73,14 +75,14 @@ type RadioButtonGroupProps<T extends string> = {
}; };
const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, options, autoWidth = false, className, isLoading }: RadioButtonGroupProps<T>) => { const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, options, autoWidth = false, className, isLoading }: RadioButtonGroupProps<T>) => {
const { getRootProps, getRadioProps } = useRadioGroup({ name, defaultValue, onChange }); const { getRootProps } = useRadioGroup({ name, defaultValue, onValueChange: onChange });
const group = getRootProps(); const root = getRootProps();
return ( return (
<Skeleton isLoaded={ !isLoading }> <Skeleton loading={ isLoading }>
<ButtonGroup <ButtonGroup
{ ...group } { ...root }
className={ className } className={ className }
isAttached isAttached
size="sm" size="sm"
...@@ -88,8 +90,8 @@ const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, opti ...@@ -88,8 +90,8 @@ const RadioButtonGroup = <T extends string>({ onChange, name, defaultValue, opti
gridTemplateColumns={ `repeat(${ options.length }, ${ autoWidth ? 'auto' : '1fr' })` } gridTemplateColumns={ `repeat(${ options.length }, ${ autoWidth ? 'auto' : '1fr' })` }
> >
{ options.map((option) => { { options.map((option) => {
const props = getRadioProps({ value: option.value }); // const props = getRadioProps({ value: option.value });
return <RadioButton { ...props } key={ option.value } { ...option }/>; return <RadioButton key={ option.value } { ...option }/>;
}) } }) }
</ButtonGroup> </ButtonGroup>
</Skeleton> </Skeleton>
......
import React from 'react'; import React from 'react';
import { Button } from 'toolkit/chakra/button'; import { Button, ButtonGroupRadio } from 'toolkit/chakra/button';
import { Checkbox } from 'toolkit/chakra/checkbox'; import { Checkbox } from 'toolkit/chakra/checkbox';
import { IconButton } from 'toolkit/chakra/icon-button'; import { IconButton } from 'toolkit/chakra/icon-button';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
...@@ -216,7 +216,24 @@ const ButtonShowcase = () => { ...@@ -216,7 +216,24 @@ const ButtonShowcase = () => {
</Link> </Link>
</Sample> </Sample>
</SamplesStack> </SamplesStack>
<SectionSubHeader>Button Group Radio</SectionSubHeader>
<SamplesStack>
<Sample>
<ButtonGroupRadio>
<Button value="option-1">Option 1</Button>
<Button value="option-2">Option 2</Button>
<Button value="option-3">Option 3</Button>
</ButtonGroupRadio>
<ButtonGroupRadio loading>
<Button value="option-1">Option 1</Button>
<Button value="option-2">Option 2</Button>
<Button value="option-3">Option 3</Button>
</ButtonGroupRadio>
</Sample>
</SamplesStack>
</Section> </Section>
</Container> </Container>
); );
}; };
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import { Field } from 'toolkit/chakra/field'; import { Field } from 'toolkit/chakra/field';
import { Input } from 'toolkit/chakra/input'; import { Input } from 'toolkit/chakra/input';
import { InputGroup } from 'toolkit/chakra/input-group'; import { InputGroup } from 'toolkit/chakra/input-group';
import FilterInput from 'ui/shared/filters/FilterInput';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts'; import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHeader } from './parts';
...@@ -119,12 +120,23 @@ const InputShowcase = () => { ...@@ -119,12 +120,23 @@ const InputShowcase = () => {
</Field> </Field>
</Sample> </Sample>
<Sample label="with start element"> <Sample label="with start element">
<InputGroup startElement={ <IconSvg name="search" boxSize={ 5 }/> }> <InputGroup startElement={ <IconSvg name="collection" boxSize={ 5 }/> }>
<Input placeholder="Search"/> <Input placeholder="Type in something"/>
</InputGroup> </InputGroup>
</Sample> </Sample>
</SamplesStack> </SamplesStack>
</Section> </Section>
<Section>
<SectionHeader>Examples</SectionHeader>
<SectionSubHeader>Filter input</SectionSubHeader>
<SamplesStack>
<Sample>
<FilterInput placeholder="Search by method name"/>
<FilterInput placeholder="Search by method name" loading/>
</Sample>
</SamplesStack>
</Section>
</Container> </Container>
); );
}; };
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment