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';
import useScrollToActiveTab from './useScrollToActiveTab';
import { menuButton, getTabValue } from './utils';
interface SlotProps extends HTMLChakraProps<'div'> {
widthAllocation?: 'available' | 'fixed';
}
export interface BaseProps {
tabs: Array<TabItemRegular>;
listProps?: HTMLChakraProps<'div'> | (({ isSticky, activeTab }: { isSticky: boolean; activeTab: string }) => HTMLChakraProps<'div'>);
rightSlot?: React.ReactNode;
rightSlotProps?: HTMLChakraProps<'div'>;
rightSlotProps?: SlotProps;
leftSlot?: React.ReactNode;
leftSlotProps?: HTMLChakraProps<'div'>;
leftSlotProps?: SlotProps;
stickyEnabled?: boolean;
isLoading?: boolean;
}
......@@ -37,6 +41,24 @@ const HIDDEN_ITEM_STYLES: HTMLChakraProps<'button'> = {
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 {
......@@ -101,7 +123,16 @@ const AdaptiveTabsList = (props: Props) => {
...(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) => {
const value = getTabValue(tab);
const ref = tabsRefs[index];
......@@ -116,9 +147,9 @@ const AdaptiveTabsList = (props: Props) => {
key="menu"
ref={ ref }
tabs={ tabs }
tabsCut={ tabsCut }
isActive={ activeTabIndex > 0 && tabsCut > 0 && activeTabIndex >= tabsCut }
{ ...(tabsCut >= tabs.length ? HIDDEN_ITEM_STYLES : {}) }
tabsCut={ tabsCut ?? 0 }
isActive={ activeTabIndex > 0 && tabsCut !== undefined && tabsCut > 0 && activeTabIndex >= tabsCut }
{ ...getMenuStyles(tabs.length, tabsCut) }
/>
);
}
......@@ -130,7 +161,7 @@ const AdaptiveTabsList = (props: Props) => {
ref={ ref }
scrollSnapAlign="start"
flexShrink={ 0 }
{ ...(index < tabsCut ? {} : HIDDEN_ITEM_STYLES as never) }
{ ...getItemStyles(index, tabsCut) }
>
<Skeleton loading={ isLoading }>
{ typeof tab.title === 'function' ? tab.title() : tab.title }
......@@ -140,8 +171,16 @@ const AdaptiveTabsList = (props: Props) => {
);
}) }
{
rightSlot ?
<Box ref={ rightSlotRef } ml="auto" { ...rightSlotProps }> { rightSlot } </Box> :
rightSlot ? (
<Box
ref={ rightSlotRef }
ml="auto"
{ ...rightSlotProps }
flexGrow={ rightSlotProps?.widthAllocation === 'available' && tabsCut !== undefined ? 1 : undefined }
>
{ rightSlot }
</Box>
) :
null
}
</TabsList>
......
......@@ -40,7 +40,7 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverBody display="flex" flexDir="column">
<PopoverBody display="flex" flexDir="column" rowGap={ 2 } px={ 0 }>
{ tabs.slice(tabsCut).map((tab) => {
const value = getTabValue(tab);
......@@ -48,7 +48,12 @@ const AdaptiveTabsMenu = ({ tabs, tabsCut, isActive, ...props }: Props, ref: Rea
<TabsTrigger
key={ 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 }
<TabsCounter count={ tab.count }/>
......
......@@ -3,9 +3,9 @@ import React from 'react';
import type { TabItem } from './types';
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
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 listRef = React.useRef<HTMLDivElement>(null);
const rightSlotRef = React.useRef<HTMLDivElement>(null);
......@@ -52,7 +52,7 @@ export default function useAdaptiveTabs(tabs: Array<TabItem>, disabled?: boolean
React.useEffect(() => {
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
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ disabled ]);
......
......@@ -180,23 +180,22 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
tabs: {
solid: {
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}' } },
},
bg: {
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.800}' } },
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } },
},
},
secondary: {
fg: {
DEFAULT: { value: { _light: '{colors.blackAlpha.800}', _dark: '{colors.whiteAlpha.800}' } },
selected: { value: { _light: '{colors.blue.600}', _dark: '{colors.gray.50}' } },
},
bg: {
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.gray.600}' } },
selected: { value: { _light: '{colors.blue.50}', _dark: '{colors.whiteAlpha.100}' } },
},
border: {
DEFAULT: { value: { _light: '{colors.gray.200}', _dark: '{colors.gray.600}' } },
DEFAULT: { value: { _light: '{colors.gray.300}', _dark: '{colors.gray.600}' } },
},
},
segmented: {
......
......@@ -29,7 +29,6 @@ export const recipe = defineSlotRecipe({
},
},
trigger: {
fontWeight: '600',
outline: '0',
minW: 'var(--tabs-height)',
height: 'var(--tabs-height)',
......@@ -130,6 +129,7 @@ export const recipe = defineSlotRecipe({
variant: {
solid: {
trigger: {
fontWeight: '600',
borderRadius: 'base',
color: 'tabs.solid.fg',
bg: 'transparent',
......@@ -156,6 +156,7 @@ export const recipe = defineSlotRecipe({
},
},
trigger: {
fontWeight: '500',
color: 'tabs.secondary.fg',
bg: 'transparent',
borderWidth: '2px',
......@@ -164,11 +165,9 @@ export const recipe = defineSlotRecipe({
borderRadius: 'base',
_selected: {
bg: 'tabs.secondary.bg.selected',
color: 'tabs.secondary.fg.selected',
borderColor: 'tabs.secondary.bg.selected',
borderColor: 'transparent',
_hover: {
color: 'tabs.secondary.fg.selected',
borderColor: 'tabs.secondary.bg.selected',
borderColor: 'transparent',
},
},
_hover: {
......
......@@ -183,7 +183,7 @@ const AddressTokens = ({ shouldRender = true, isQueryEnabled = true }: Props) =>
size="sm"
listProps={ isMobile ? TAB_LIST_PROPS_MOBILE : TAB_LIST_PROPS }
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 }
/>
</>
......
......@@ -10,7 +10,7 @@ import { Section, Container, SectionHeader, SamplesStack, Sample, SectionSubHead
const TabsShowcase = () => {
const tabs = [
{ 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: 'tab4', title: 'Lending', component: <div>Lending content</div>, count: 400 },
{ id: 'tab5', title: 'Yield farming', component: <div>Yield farming content</div> },
......@@ -69,8 +69,8 @@ const TabsShowcase = () => {
outline="1px dashed lightpink"
leftSlot={ <Box display={{ base: 'none', lg: 'block' }}>Left element</Box> }
leftSlotProps={{ pr: { base: 0, lg: 4 }, color: 'text.secondary' }}
rightSlot={ <Box display={{ base: 'none', lg: 'block' }}>Right element</Box> }
rightSlotProps={{ pl: { base: 0, lg: 4 }, color: 'text.secondary' }}
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', widthAllocation: 'available' }}
/>
</Sample>
</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