Commit af65971f authored by tom's avatar tom

update tabs styles and fix adaptive tabs issue when right slot should fill all available width

parent f41fac0e
...@@ -15,13 +15,17 @@ import useAdaptiveTabs from './useAdaptiveTabs'; ...@@ -15,13 +15,17 @@ import useAdaptiveTabs from './useAdaptiveTabs';
import useScrollToActiveTab from './useScrollToActiveTab'; import useScrollToActiveTab from './useScrollToActiveTab';
import { menuButton, getTabValue } from './utils'; import { menuButton, getTabValue } from './utils';
interface SlotProps extends HTMLChakraProps<'div'> {
widthAllocation?: 'available' | 'fixed';
}
export interface BaseProps { export interface BaseProps {
tabs: Array<TabItemRegular>; tabs: Array<TabItemRegular>;
listProps?: HTMLChakraProps<'div'> | (({ isSticky, activeTab }: { isSticky: boolean; activeTab: string }) => HTMLChakraProps<'div'>); listProps?: HTMLChakraProps<'div'> | (({ isSticky, activeTab }: { isSticky: boolean; activeTab: string }) => HTMLChakraProps<'div'>);
rightSlot?: React.ReactNode; rightSlot?: React.ReactNode;
rightSlotProps?: HTMLChakraProps<'div'>; rightSlotProps?: SlotProps;
leftSlot?: React.ReactNode; leftSlot?: React.ReactNode;
leftSlotProps?: HTMLChakraProps<'div'>; leftSlotProps?: SlotProps;
stickyEnabled?: boolean; stickyEnabled?: boolean;
isLoading?: boolean; isLoading?: boolean;
} }
...@@ -37,6 +41,24 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = { ...@@ -37,6 +41,24 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
visibility: 'hidden', visibility: 'hidden',
}; };
const getItemStyles = (index: number, tabsCut: number | undefined) => {
if (tabsCut === undefined) {
return HIDDEN_ITEM_STYLES as never;
}
return index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never;
};
const getMenuStyles = (tabsLength: number, tabsCut: number | undefined) => {
if (tabsCut === undefined) {
return {
opacity: 0,
};
}
return tabsCut >= tabsLength ? HIDDEN_ITEM_STYLES : {};
};
const AdaptiveTabsList = (props: Props) => { const AdaptiveTabsList = (props: Props) => {
const { const {
...@@ -101,7 +123,16 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -101,7 +123,16 @@ const AdaptiveTabsList = (props: Props) => {
...(typeof listProps === 'function' ? listProps({ isSticky, activeTab }) : listProps) ...(typeof listProps === 'function' ? listProps({ isSticky, activeTab }) : listProps)
} }
> >
{ leftSlot && <Box ref={ leftSlotRef } { ...leftSlotProps }> { leftSlot } </Box> } { leftSlot && (
<Box
ref={ leftSlotRef }
{ ...leftSlotProps }
flexGrow={ leftSlotProps?.widthAllocation === 'available' && tabsCut !== undefined ? 1 : undefined }
>
{ leftSlot }
</Box>
)
}
{ tabsList.slice(0, isLoading ? 5 : Infinity).map((tab, index) => { { tabsList.slice(0, isLoading ? 5 : Infinity).map((tab, index) => {
const value = getTabValue(tab); const value = getTabValue(tab);
const ref = tabsRefs[index]; const ref = tabsRefs[index];
...@@ -116,9 +147,9 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -116,9 +147,9 @@ const AdaptiveTabsList = (props: Props) => {
key="menu" key="menu"
ref={ ref } ref={ ref }
tabs={ tabs } tabs={ tabs }
tabsCut={ tabsCut } tabsCut={ tabsCut ?? 0 }
isActive={ activeTabIndex > 0 && tabsCut > 0 && activeTabIndex >= tabsCut } isActive={ activeTabIndex > 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut }
{ ...(tabsCut >= tabs.length ? HIDDEN_ITEM_STYLES : {}) } { ...getMenuStyles(tabs.length, tabsCut) }
/> />
); );
} }
...@@ -130,7 +161,7 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -130,7 +161,7 @@ const AdaptiveTabsList = (props: Props) => {
ref={ ref } ref={ ref }
scrollSnapAlign="start" scrollSnapAlign="start"
flexShrink={ 0 } flexShrink={ 0 }
{ ...(index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never) } { ...getItemStyles(index, tabsCut) }
> >
<Skeleton loading={ isLoading }> <Skeleton loading={ isLoading }>
{ typeof tab.title === 'function' ? tab.title() : tab.title } { typeof tab.title === 'function' ? tab.title() : tab.title }
...@@ -140,8 +171,16 @@ const AdaptiveTabsList = (props: Props) => { ...@@ -140,8 +171,16 @@ const AdaptiveTabsList = (props: Props) => {
); );
}) } }) }
{ {
rightSlot ? rightSlot ? (
<Box ref={ rightSlotRef } ml="auto" { ...rightSlotProps }> { rightSlot } </Box> : <Box
ref={ rightSlotRef }
ml="auto"
{ ...rightSlotProps }
flexGrow={ rightSlotProps?.widthAllocation === 'available' && tabsCut !== undefined ? 1 : undefined }
>
{ rightSlot }
</Box>
) :
null null
} }
</TabsList> </TabsList>
......
...@@ -40,7 +40,7 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea ...@@ -40,7 +40,7 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<PopoverBody display="flex" flexDir="column"> <PopoverBody display="flex" flexDir="column" rowGap={ 2 } px={ 0 }>
{ tabs.slice(tabsCut).map((tab) => { { tabs.slice(tabsCut).map((tab) => {
const value = getTabValue(tab); const value = getTabValue(tab);
...@@ -48,7 +48,12 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea ...@@ -48,7 +48,12 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea
<TabsTrigger <TabsTrigger
key={ value } key={ value }
value={ value } value={ value }
w="fit-content" w="100%"
py="5px"
borderRadius="none"
_hover={{
bg: 'tabs.solid.bg.selected',
}}
> >
{ typeof tab.title === 'function' ? tab.title() : tab.title } { typeof tab.title === 'function' ? tab.title() : tab.title }
<TabsCounter count={ tab.count }/> <TabsCounter count={ tab.count }/>
......
...@@ -3,9 +3,9 @@ import React from 'react'; ...@@ -3,9 +3,9 @@ import React from 'react';
import type { TabItem } from './types'; import type { TabItem } from './types';
export default function useAdaptiveTabs(tabs: Array<TabItem>, disabled?: boolean) { export default function useAdaptiveTabs(tabs: Array<TabItem>, disabled?: boolean) {
// to avoid flickering we set initial value to 0 // to avoid flickering we set initial value to undefined
// so there will be no displayed tabs initially // so there will be no displayed tabs initially
const [ tabsCut, setTabsCut ] = React.useState(disabled ? tabs.length : 0); const [ tabsCut, setTabsCut ] = React.useState<number | undefined>(disabled ? tabs.length : undefined);
const [ tabsRefs, setTabsRefs ] = React.useState<Array<React.RefObject<HTMLButtonElement>>>([]); const [ tabsRefs, setTabsRefs ] = React.useState<Array<React.RefObject<HTMLButtonElement>>>([]);
const listRef = React.useRef<HTMLDivElement>(null); const listRef = React.useRef<HTMLDivElement>(null);
const rightSlotRef = React.useRef<HTMLDivElement>(null); const rightSlotRef = React.useRef<HTMLDivElement>(null);
...@@ -52,7 +52,7 @@ export default function useAdaptiveTabs(tabs: Array<TabItem>, disabled?: boolean ...@@ -52,7 +52,7 @@ export default function useAdaptiveTabs(tabs: Array<TabItem>, disabled?: boolean
React.useEffect(() => { React.useEffect(() => {
setTabsRefs(tabs.map((_, index) => tabsRefs[index] || React.createRef())); setTabsRefs(tabs.map((_, index) => tabsRefs[index] || React.createRef()));
setTabsCut(disabled ? tabs.length : 0); setTabsCut(disabled ? tabs.length : undefined);
// update refs only when disabled prop changes // update refs only when disabled prop changes
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ disabled ]); }, [ disabled ]);
......
...@@ -180,23 +180,22 @@ const semanticTokens: ThemingConfig['semanticTokens'] = { ...@@ -180,23 +180,22 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
tabs: { tabs: {
solid: { solid: {
fg: { fg: {
DEFAULT: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.400}' } }, DEFAULT: { value: { _light: '{colors.blue.700}', _dark: '{colors.blue.100}' } },
selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } }, selected: { value: { _light: '{colors.blue.700}', _dark: '{colors.gray.50}' } },
}, },
bg: { bg: {
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } }, selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } },
}, },
}, },
secondary: { secondary: {
fg: { fg: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } }, DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
selected: { value: { _light: '{colors.blue.600}', _dark: '{colors.gray.50}' } },
}, },
bg: { bg: {
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.600}' } }, selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } },
}, },
border: { border: {
DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } }, DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } },
}, },
}, },
segmented: { segmented: {
......
...@@ -29,7 +29,6 @@ export const recipe = defineSlotRecipe({ ...@@ -29,7 +29,6 @@ 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)',
...@@ -130,6 +129,7 @@ export const recipe = defineSlotRecipe({ ...@@ -130,6 +129,7 @@ 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',
...@@ -156,6 +156,7 @@ export const recipe = defineSlotRecipe({ ...@@ -156,6 +156,7 @@ export const recipe = defineSlotRecipe({
}, },
}, },
trigger: { trigger: {
fontWeight: '500',
color: 'tabs.secondary.fg', color: 'tabs.secondary.fg',
bg: 'transparent', bg: 'transparent',
borderWidth: '2px', borderWidth: '2px',
...@@ -164,11 +165,9 @@ export const recipe = defineSlotRecipe({ ...@@ -164,11 +165,9 @@ export const recipe = defineSlotRecipe({
borderRadius: 'base', borderRadius: 'base',
_selected: { _selected: {
bg: 'tabs.secondary.bg.selected', bg: 'tabs.secondary.bg.selected',
color: 'tabs.secondary.fg.selected', borderColor: 'transparent',
borderColor: 'tabs.secondary.bg.selected',
_hover: { _hover: {
color: 'tabs.secondary.fg.selected', borderColor: 'transparent',
borderColor: 'tabs.secondary.bg.selected',
}, },
}, },
_hover: { _hover: {
......
...@@ -183,7 +183,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) => ...@@ -183,7 +183,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
size="sm" size="sm"
listProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS } listProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS }
rightSlot={ rightSlot } rightSlot={ rightSlot }
rightSlotProps={ tab === 'tokens_nfts' && !isMobile ? { display: 'flex', justifyContent: 'space-between', ml: 8 } : {} } rightSlotProps={ tab === 'tokens_nfts' && !isMobile ? { display: 'flex', justifyContent: 'space-between', ml: 8, widthAllocation: 'available' } : {} }
stickyEnabled={ !isMobile } stickyEnabled={ !isMobile }
/> />
</> </>
......
...@@ -10,7 +10,7 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead ...@@ -10,7 +10,7 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead
const TabsShowcase = () => { const TabsShowcase = () => {
const tabs = [ const tabs = [
{ id: 'tab1', title: 'Swaps', component: <div>Swaps content</div>, count: 10 }, { id: 'tab1', title: 'Swaps', component: <div>Swaps content</div>, count: 10 },
{ id: 'tab2', title: 'Bridges', component: <div>Bridges content</div>, count: 3 }, { id: 'tab2', title: 'Bridges', component: <div>Bridges content</div>, count: 0 },
{ id: 'tab3', title: 'Liquidity staking', component: <div>Liquidity staking content</div>, count: 300 }, { id: 'tab3', title: 'Liquidity staking', component: <div>Liquidity staking content</div>, count: 300 },
{ id: 'tab4', title: 'Lending', component: <div>Lending content</div>, count: 400 }, { id: 'tab4', title: 'Lending', component: <div>Lending content</div>, count: 400 },
{ id: 'tab5', title: 'Yield farming', component: <div>Yield farming content</div> }, { id: 'tab5', title: 'Yield farming', component: <div>Yield farming content</div> },
...@@ -69,8 +69,8 @@ const TabsShowcase = () => { ...@@ -69,8 +69,8 @@ const TabsShowcase = () => {
outline="1px dashed lightpink" outline="1px dashed lightpink"
leftSlot={ <Box display={{ base: 'none', lg: 'block' }}>Left element</Box> } leftSlot={ <Box display={{ base: 'none', lg: 'block' }}>Left element</Box> }
leftSlotProps={{ pr: { base: 0, lg: 4 }, color: 'text.secondary' }} leftSlotProps={{ pr: { base: 0, lg: 4 }, color: 'text.secondary' }}
rightSlot={ <Box display={{ base: 'none', lg: 'block' }}>Right element</Box> } rightSlot={ <Box display={{ base: 'none', lg: 'flex' }} justifyContent="space-between"><span>Right element</span><span>🙈</span></Box> }
rightSlotProps={{ pl: { base: 0, lg: 4 }, color: 'text.secondary' }} rightSlotProps={{ pl: { base: 0, lg: 4 }, color: 'text.secondary', widthAllocation: 'available' }}
/> />
</Sample> </Sample>
</SamplesStack> </SamplesStack>
......
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