Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
8600aeba
Commit
8600aeba
authored
Feb 12, 2025
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix image loading state
parent
d053f9da
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
21 additions
and
697 deletions
+21
-697
image.tsx
toolkit/chakra/image.tsx
+7
-5
select.tsx
toolkit/chakra/select.tsx
+1
-1
NativeTokenIcon.tsx
ui/shared/NativeTokenIcon.tsx
+1
-1
AdaptiveTabsList.tsx
ui/shared/Tabs/AdaptiveTabsList.tsx
+0
-147
RoutedTabs.tsx
ui/shared/Tabs/RoutedTabs.tsx
+0
-90
TabCounter.tsx
ui/shared/Tabs/TabCounter.tsx
+0
-26
TabsMenu.tsx
ui/shared/Tabs/TabsMenu.tsx
+0
-77
TabsWithScroll.pw.tsx
ui/shared/Tabs/TabsWithScroll.pw.tsx
+0
-33
TabsWithScroll.tsx
ui/shared/Tabs/TabsWithScroll.tsx
+0
-125
TabsWithScroll.pw.tsx_default_with-counters-1.png
...shots__/TabsWithScroll.pw.tsx_default_with-counters-1.png
+0
-0
types.ts
ui/shared/Tabs/types.ts
+0
-21
useAdaptiveTabs.tsx
ui/shared/Tabs/useAdaptiveTabs.tsx
+0
-75
useScrollToActiveTab.tsx
ui/shared/Tabs/useScrollToActiveTab.tsx
+0
-48
useTabIndexFromQuery.tsx
ui/shared/Tabs/useTabIndexFromQuery.tsx
+0
-28
utils.ts
ui/shared/Tabs/utils.ts
+0
-11
CoinzillaTextAd.tsx
ui/shared/ad/CoinzillaTextAd.tsx
+9
-3
AddressEntity.tsx
ui/shared/entities/address/AddressEntity.tsx
+2
-2
TokenEntity.tsx
ui/shared/entities/token/TokenEntity.tsx
+1
-4
No files found.
toolkit/chakra/image.tsx
View file @
8600aeba
...
@@ -6,12 +6,11 @@ import { Skeleton } from './skeleton';
...
@@ -6,12 +6,11 @@ import { Skeleton } from './skeleton';
export
interface
ImageProps
extends
ChakraImageProps
{
export
interface
ImageProps
extends
ChakraImageProps
{
fallback
?:
React
.
ReactNode
;
fallback
?:
React
.
ReactNode
;
containerProps
?:
BoxProps
;
}
}
export
const
Image
=
React
.
forwardRef
<
HTMLImageElement
,
ImageProps
>
(
export
const
Image
=
React
.
forwardRef
<
HTMLImageElement
,
ImageProps
>
(
function
Image
(
props
,
ref
)
{
function
Image
(
props
,
ref
)
{
const
{
fallback
,
src
,
containerProps
,
...
rest
}
=
props
;
const
{
fallback
,
src
,
...
rest
}
=
props
;
const
[
loading
,
setLoading
]
=
React
.
useState
(
true
);
const
[
loading
,
setLoading
]
=
React
.
useState
(
true
);
const
[
error
,
setError
]
=
React
.
useState
(
false
);
const
[
error
,
setError
]
=
React
.
useState
(
false
);
...
@@ -34,14 +33,17 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
...
@@ -34,14 +33,17 @@ export const Image = React.forwardRef<HTMLImageElement, ImageProps>(
}
}
return
(
return
(
<
Skeleton
loading=
{
loading
}
{
...
containerProps
}
>
<>
{
loading
&&
<
Skeleton
loading
{
...
rest
as
BoxProps
}
/>
}
<
ChakraImage
<
ChakraImage
ref=
{
ref
}
ref=
{
ref
}
src=
{
src
}
src=
{
src
}
onError=
{
handleLoadError
}
onError=
{
handleLoadError
}
onLoad=
{
handleLoadSuccess
}
onLoad=
{
handleLoadSuccess
}
{
...
rest
}
{
...
rest
}
display=
{
loading
?
'
none
'
:
rest
.
display
||
'
inline-block
'
}
/>
/>
</
Skeleton
>
</>
);
);
});
},
);
toolkit/chakra/select.tsx
View file @
8600aeba
'
use client
'
;
'
use client
'
;
import
type
{
CollectionItem
}
from
'
@chakra-ui/react
'
;
import
type
{
CollectionItem
}
from
'
@chakra-ui/react
'
;
import
{
Select
as
ChakraSelect
,
Portal
,
useSelect
,
useSelect
Context
}
from
'
@chakra-ui/react
'
;
import
{
Select
as
ChakraSelect
,
Portal
,
useSelectContext
}
from
'
@chakra-ui/react
'
;
import
*
as
React
from
'
react
'
;
import
*
as
React
from
'
react
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
...
...
ui/shared/NativeTokenIcon.tsx
View file @
8600aeba
...
@@ -31,8 +31,8 @@ const NativeTokenIcon = ({ isLoading, className, type }: Props) => {
...
@@ -31,8 +31,8 @@ const NativeTokenIcon = ({ isLoading, className, type }: Props) => {
return
(
return
(
<
Image
<
Image
className=
{
className
}
borderRadius=
"base"
borderRadius=
"base"
containerProps=
{
{
className
}
}
src=
{
src
||
undefined
}
src=
{
src
||
undefined
}
alt=
{
`${ config.chain.currency.symbol } logo`
}
alt=
{
`${ config.chain.currency.symbol } logo`
}
fallback=
{
<
TokenLogoPlaceholder
borderRadius=
"base"
className=
{
className
}
/>
}
fallback=
{
<
TokenLogoPlaceholder
borderRadius=
"base"
className=
{
className
}
/>
}
...
...
ui/shared/Tabs/AdaptiveTabsList.tsx
deleted
100644 → 0
View file @
d053f9da
import
type
{
StyleProps
,
ThemingProps
}
from
'
@chakra-ui/react
'
;
import
{
Box
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
{
useScrollDirection
}
from
'
lib/contexts/scrollDirection
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
useIsSticky
from
'
lib/hooks/useIsSticky
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
TabsList
,
TabsTrigger
}
from
'
toolkit/chakra/tabs
'
;
import
TabCounter
from
'
./TabCounter
'
;
import
TabsMenu
from
'
./TabsMenu
'
;
import
type
{
Props
as
TabsProps
}
from
'
./TabsWithScroll
'
;
import
useAdaptiveTabs
from
'
./useAdaptiveTabs
'
;
import
useScrollToActiveTab
from
'
./useScrollToActiveTab
'
;
import
{
getTabValue
,
menuButton
}
from
'
./utils
'
;
const
hiddenItemStyles
:
StyleProps
=
{
position
:
'
absolute
'
,
top
:
'
-9999px
'
,
left
:
'
-9999px
'
,
visibility
:
'
hidden
'
,
};
interface
Props
extends
TabsProps
{
activeTab
:
string
;
onItemClick
:
(
index
:
number
)
=>
void
;
themeProps
:
ThemingProps
<
'
Tabs
'
>
;
isLoading
?:
boolean
;
}
const
AdaptiveTabsList
=
(
props
:
Props
)
=>
{
const
scrollDirection
=
useScrollDirection
();
const
isMobile
=
useIsMobile
();
const
tabsList
=
React
.
useMemo
(()
=>
{
return
[
...
props
.
tabs
,
menuButton
];
},
[
props
.
tabs
]);
// TODO @tom2drum remove isMobile || true
const
{
tabsCut
,
tabsRefs
,
listRef
,
rightSlotRef
,
leftSlotRef
}
=
useAdaptiveTabs
(
tabsList
,
isMobile
||
true
);
const
isSticky
=
useIsSticky
(
listRef
,
5
,
props
.
stickyEnabled
);
const
activeTabIndex
=
tabsList
.
findIndex
((
tab
)
=>
getTabValue
(
tab
)
===
props
.
activeTab
)
??
0
;
useScrollToActiveTab
({
activeTabIndex
,
listRef
,
tabsRefs
,
isMobile
,
isLoading
:
props
.
isLoading
});
return
(
<
TabsList
marginBottom=
{
6
}
mx=
{
{
base
:
'
-12px
'
,
lg
:
'
unset
'
}
}
px=
{
{
base
:
'
12px
'
,
lg
:
'
unset
'
}
}
flexWrap=
"nowrap"
alignItems=
"center"
whiteSpace=
"nowrap"
ref=
{
listRef
}
overflowX=
{
{
base
:
'
auto
'
,
lg
:
'
initial
'
}
}
overscrollBehaviorX=
"contain"
css=
{
{
'
scroll-snap-type
'
:
'
x mandatory
'
,
'
scroll-padding-inline
'
:
'
12px
'
,
// mobile page padding
// hide scrollbar
'
&::-webkit-scrollbar
'
:
{
/* Chromiums */
display
:
'
none
'
,
},
'
-ms-overflow-style
'
:
'
none
'
,
/* IE and Edge */
'
scrollbar-width
'
:
'
none
'
,
/* Firefox */
}
}
bgColor=
{
{
_light
:
'
white
'
,
_dark
:
'
black
'
}
}
transitionProperty=
"top,box-shadow,background-color,color"
transitionDuration=
"normal"
transitionTimingFunction=
"ease"
{
...
(
props
.
stickyEnabled
?
{
position
:
'
sticky
',
boxShadow
:
{
base
:
isSticky
?
'
md
'
:
'
none
',
lg
:
'
none
'
},
top
:
{
base
:
scrollDirection
===
'
down
'
?
`0
px
`
:
`106
px
`,
lg
:
0
},
zIndex
:
{
base
:
'
sticky2
',
lg
:
'
docked
'
},
}
:
{
})
}
{
...
(
typeof
props
.
tabListProps
===
'
function
'
?
props
.
tabListProps
({
isSticky
,
activeTab
:
props
.
activeTab
})
:
props
.
tabListProps
)
}
>
{
props
.
leftSlot
&&
<
Box
ref=
{
leftSlotRef
}
{
...
props
.
leftSlotProps
}
>
{
props
.
leftSlot
}
</
Box
>
}
{
tabsList
.
slice
(
0
,
props
.
isLoading
?
5
:
Infinity
).
map
((
tab
,
index
)
=>
{
const
value
=
getTabValue
(
tab
);
if
(
tab
.
id
===
'
menu
'
)
{
if
(
props
.
isLoading
)
{
return
null
;
}
return
(
<
TabsMenu
key=
"menu"
tabs=
{
props
.
tabs
}
activeTab=
{
props
.
tabs
[
activeTabIndex
]
}
tabsCut=
{
tabsCut
}
isActive=
{
activeTabIndex
>=
tabsCut
}
styles=
{
tabsCut
<
props
.
tabs
.
length
?
// initially our cut is 0 and we don't want to show the menu button too
// but we want to keep it in the tabs row so it won't collapse
// that's why we only change opacity but not the position itself
{
opacity
:
tabsCut
===
0
?
0
:
1
}
:
hiddenItemStyles
}
onItemClick=
{
props
.
onItemClick
}
buttonRef=
{
tabsRefs
[
index
]
}
size=
{
props
.
themeProps
.
size
||
'
md
'
}
/>
);
}
return
(
<
TabsTrigger
key=
{
value
}
value=
{
value
}
ref=
{
tabsRefs
[
index
]
}
{
...
(
index
<
tabsCut
?
{}
:
hiddenItemStyles
)
}
scrollSnapAlign=
"start"
flexShrink=
{
0
}
sx=
{
{
'
&:hover span
'
:
{
color
:
'
inherit
'
,
},
}
}
{
...
(
value
===
props
.
activeTab
?
{
'
data
-
selected
':
true
}
:
{})
}
>
<
Skeleton
loading=
{
props
.
isLoading
}
>
{
typeof
tab
.
title
===
'
function
'
?
tab
.
title
()
:
tab
.
title
}
<
TabCounter
count=
{
tab
.
count
}
/>
</
Skeleton
>
</
TabsTrigger
>
);
})
}
{
props
.
rightSlot
&&
tabsCut
>
0
?
<
Box
ref=
{
rightSlotRef
}
ml=
"auto"
{
...
props
.
rightSlotProps
}
>
{
props
.
rightSlot
}
</
Box
>
:
null
}
</
TabsList
>
);
};
export
default
React
.
memo
(
AdaptiveTabsList
);
ui/shared/Tabs/RoutedTabs.tsx
deleted
100644 → 0
View file @
d053f9da
import
type
{
ChakraProps
,
ThemingProps
}
from
'
@chakra-ui/react
'
;
import
{
chakra
}
from
'
@chakra-ui/react
'
;
import
{
pickBy
}
from
'
es-toolkit
'
;
import
{
useRouter
}
from
'
next/router
'
;
import
React
,
{
useEffect
,
useRef
}
from
'
react
'
;
import
type
{
RoutedTab
}
from
'
./types
'
;
import
TabsWithScroll
from
'
./TabsWithScroll
'
;
import
useTabIndexFromQuery
from
'
./useTabIndexFromQuery
'
;
interface
Props
extends
ThemingProps
<
'
Tabs
'
>
{
tabs
:
Array
<
RoutedTab
>
;
tabListProps
?:
ChakraProps
|
(({
isSticky
,
activeTabIndex
}:
{
isSticky
:
boolean
;
activeTabIndex
:
number
})
=>
ChakraProps
);
rightSlot
?:
React
.
ReactNode
;
rightSlotProps
?:
ChakraProps
;
leftSlot
?:
React
.
ReactNode
;
leftSlotProps
?:
ChakraProps
;
stickyEnabled
?:
boolean
;
className
?:
string
;
onTabChange
?:
(
index
:
number
)
=>
void
;
isLoading
?:
boolean
;
}
const
RoutedTabs
=
({
tabs
,
tabListProps
,
rightSlot
,
rightSlotProps
,
leftSlot
,
leftSlotProps
,
stickyEnabled
,
className
,
onTabChange
,
isLoading
,
...
themeProps
}:
Props
)
=>
{
const
router
=
useRouter
();
const
tabIndex
=
useTabIndexFromQuery
(
tabs
);
const
tabsRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
handleTabChange
=
React
.
useCallback
((
index
:
number
)
=>
{
const
nextTab
=
tabs
[
index
];
const
queryForPathname
=
pickBy
(
router
.
query
,
(
value
,
key
)
=>
router
.
pathname
.
includes
(
`[
${
key
}
]`
));
const
tabId
=
Array
.
isArray
(
nextTab
.
id
)
?
nextTab
.
id
[
0
]
:
nextTab
.
id
;
router
.
push
(
{
pathname
:
router
.
pathname
,
query
:
{
...
queryForPathname
,
tab
:
tabId
}
},
undefined
,
{
shallow
:
true
},
);
onTabChange
?.(
index
);
},
[
tabs
,
router
,
onTabChange
]);
useEffect
(()
=>
{
if
(
router
.
query
.
scroll_to_tabs
)
{
tabsRef
?.
current
?.
scrollIntoView
(
true
);
delete
router
.
query
.
scroll_to_tabs
;
router
.
push
(
{
pathname
:
router
.
pathname
,
query
:
router
.
query
,
},
undefined
,
{
shallow
:
true
},
);
}
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[]);
return
(
<
TabsWithScroll
tabs=
{
tabs
}
tabListProps=
{
tabListProps
}
leftSlot=
{
leftSlot
}
leftSlotProps=
{
leftSlotProps
}
rightSlot=
{
rightSlot
}
rightSlotProps=
{
rightSlotProps
}
stickyEnabled=
{
stickyEnabled
}
onTabChange=
{
handleTabChange
}
defaultTabIndex=
{
tabIndex
}
isLoading=
{
isLoading
}
{
...
themeProps
}
/>
);
};
export
default
React
.
memo
(
chakra
(
RoutedTabs
));
ui/shared/Tabs/TabCounter.tsx
deleted
100644 → 0
View file @
d053f9da
import
{
chakra
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
const
COUNTER_OVERLOAD
=
50
;
type
Props
=
{
count
?:
number
|
null
;
};
// TODO @tom2drum remove this
const
TabCounter
=
({
count
}:
Props
)
=>
{
if
(
count
===
undefined
||
count
===
null
)
{
return
null
;
}
return
(
<
chakra
.
span
color=
{
count
>
0
?
'
text.secondary
'
:
{
_light
:
'
blackAlpha.400
'
,
_dark
:
'
whiteAlpha.400
'
}
}
ml=
{
1
}
>
{
count
>
COUNTER_OVERLOAD
?
`${ COUNTER_OVERLOAD }+`
:
count
}
</
chakra
.
span
>
);
};
export
default
TabCounter
;
ui/shared/Tabs/TabsMenu.tsx
deleted
100644 → 0
View file @
d053f9da
import
type
{
StyleProps
}
from
'
@chakra-ui/styled-system
'
;
import
React
from
'
react
'
;
import
type
{
MenuButton
,
TabItem
}
from
'
./types
'
;
import
type
{
ButtonProps
}
from
'
toolkit/chakra/button
'
;
import
{
Button
}
from
'
toolkit/chakra/button
'
;
import
{
PopoverBody
,
PopoverContent
,
PopoverRoot
,
PopoverTrigger
}
from
'
toolkit/chakra/popover
'
;
import
{
useDisclosure
}
from
'
toolkit/hooks/useDisclosure
'
;
import
TabCounter
from
'
./TabCounter
'
;
import
{
menuButton
}
from
'
./utils
'
;
interface
Props
{
tabs
:
Array
<
TabItem
|
MenuButton
>
;
activeTab
?:
TabItem
;
tabsCut
:
number
;
isActive
:
boolean
;
styles
?:
StyleProps
;
onItemClick
:
(
index
:
number
)
=>
void
;
buttonRef
:
React
.
RefObject
<
HTMLButtonElement
>
;
size
:
ButtonProps
[
'
size
'
];
}
const
TabsMenu
=
({
tabs
,
tabsCut
,
isActive
,
styles
,
onItemClick
,
buttonRef
,
activeTab
,
size
}:
Props
)
=>
{
// const { isOpen, onClose, onOpen } = useDisclosure();
const
handleItemClick
=
React
.
useCallback
((
event
:
React
.
MouseEvent
<
HTMLButtonElement
>
)
=>
{
// onClose();
const
tabIndex
=
event
.
currentTarget
.
getAttribute
(
'
data-index
'
);
if
(
tabIndex
)
{
onItemClick
(
tabsCut
+
Number
(
tabIndex
));
}
},
[
onItemClick
,
tabsCut
]);
return
(
<
PopoverRoot
positioning=
{
{
placement
:
'
bottom-end
'
}
}
>
<
PopoverTrigger
>
<
Button
as=
"div"
role=
"button"
variant=
"ghost"
// isActive={ isOpen || isActive }
ref=
{
buttonRef
}
size=
{
size
}
{
...
styles
}
>
{
menuButton
.
title
}
</
Button
>
</
PopoverTrigger
>
<
PopoverContent
w=
"auto"
>
<
PopoverBody
display=
"flex"
flexDir=
"column"
>
{
tabs
.
slice
(
tabsCut
).
map
((
tab
,
index
)
=>
(
<
Button
key=
{
tab
.
id
?.
toString
()
}
variant=
"ghost"
onClick=
{
handleItemClick
}
active=
{
activeTab
?
activeTab
.
id
===
tab
.
id
:
false
}
justifyContent=
"left"
data
-
index=
{
index
}
css=
{
{
'
&:hover span
'
:
{
color
:
'
inherit
'
,
},
}
}
>
{
typeof
tab
.
title
===
'
function
'
?
tab
.
title
()
:
tab
.
title
}
<
TabCounter
count=
{
tab
.
count
}
/>
</
Button
>
))
}
</
PopoverBody
>
</
PopoverContent
>
</
PopoverRoot
>
);
};
export
default
React
.
memo
(
TabsMenu
);
ui/shared/Tabs/TabsWithScroll.pw.tsx
deleted
100644 → 0
View file @
d053f9da
import
React
from
'
react
'
;
import
type
{
TabItem
}
from
'
./types
'
;
import
{
test
,
expect
}
from
'
playwright/lib
'
;
import
TabsWithScroll
from
'
./TabsWithScroll
'
;
test
(
'
with counters
'
,
async
({
render
})
=>
{
const
tabs
:
Array
<
TabItem
>
=
[
{
id
:
'
tab1
'
,
title
:
'
First tab
'
,
count
:
11
,
component
:
null
,
},
{
id
:
'
tab2
'
,
title
:
'
Second tab
'
,
count
:
0
,
component
:
null
,
},
{
id
:
'
tab3
'
,
title
:
'
Third tab
'
,
count
:
51
,
component
:
null
,
},
];
const
component
=
await
render
(<
TabsWithScroll
tabs=
{
tabs
}
/>);
await
component
.
getByText
(
'
Third tab
'
).
hover
();
await
expect
(
component
).
toHaveScreenshot
();
});
ui/shared/Tabs/TabsWithScroll.tsx
deleted
100644 → 0
View file @
d053f9da
import
{
chakra
}
from
'
@chakra-ui/react
'
;
import
{
debounce
}
from
'
es-toolkit
'
;
import
React
,
{
useEffect
,
useRef
,
useState
}
from
'
react
'
;
import
type
{
TabItem
}
from
'
./types
'
;
import
isBrowser
from
'
lib/isBrowser
'
;
import
type
{
TabsProps
}
from
'
toolkit/chakra/tabs
'
;
import
{
TabsContent
,
TabsRoot
}
from
'
toolkit/chakra/tabs
'
;
import
AdaptiveTabsList
from
'
./AdaptiveTabsList
'
;
import
{
getTabValue
,
menuButton
}
from
'
./utils
'
;
export
interface
Props
extends
TabsProps
{
tabs
:
Array
<
TabItem
>
;
lazyBehavior
?:
LazyMode
;
tabListProps
?:
ChakraProps
|
(({
isSticky
,
activeTabIndex
}:
{
isSticky
:
boolean
;
activeTabIndex
:
number
})
=>
ChakraProps
);
rightSlot
?:
React
.
ReactNode
;
rightSlotProps
?:
ChakraProps
;
leftSlot
?:
React
.
ReactNode
;
leftSlotProps
?:
ChakraProps
;
stickyEnabled
?:
boolean
;
onTabChange
?:
(
value
:
string
)
=>
void
;
defaultTab
?:
string
;
isLoading
?:
boolean
;
className
?:
string
;
}
// TODO @tom2drum remove this component
const
TabsWithScroll
=
({
tabs
,
lazyBehavior
,
tabListProps
,
rightSlot
,
rightSlotProps
,
leftSlot
,
leftSlotProps
,
stickyEnabled
,
onTabChange
,
defaultTab
,
isLoading
,
className
,
...
themeProps
}:
Props
)
=>
{
const
[
activeTab
,
setActiveTab
]
=
useState
<
string
>
(
defaultTab
||
getTabValue
(
tabs
[
0
]));
const
[
screenWidth
,
setScreenWidth
]
=
React
.
useState
(
isBrowser
()
?
window
.
innerWidth
:
0
);
const
tabsRef
=
useRef
<
HTMLDivElement
>
(
null
);
const
tabsList
=
React
.
useMemo
(()
=>
{
return
[
...
tabs
,
menuButton
];
},
[
tabs
]);
const
handleTabChange
=
React
.
useCallback
(({
value
}:
{
value
:
string
})
=>
{
if
(
isLoading
)
{
return
;
}
onTabChange
?
onTabChange
(
value
)
:
setActiveTab
(
value
);
},
[
isLoading
,
onTabChange
]);
useEffect
(()
=>
{
if
(
defaultTab
!==
undefined
)
{
setActiveTab
(
defaultTab
);
}
},
[
defaultTab
]);
React
.
useEffect
(()
=>
{
const
resizeHandler
=
debounce
(()
=>
{
setScreenWidth
(
window
.
innerWidth
);
},
100
);
const
resizeObserver
=
new
ResizeObserver
(
resizeHandler
);
resizeObserver
.
observe
(
document
.
body
);
return
function
cleanup
()
{
resizeObserver
.
unobserve
(
document
.
body
);
};
},
[]);
if
(
tabs
.
length
===
1
)
{
return
<
div
>
{
tabs
[
0
].
component
}
</
div
>;
}
return
(
<
TabsRoot
className=
{
className
}
variant=
{
themeProps
.
variant
}
// colorScheme={ themeProps.colorScheme || 'blue' }
lazyMount
unmountOnExit
onValueChange=
{
handleTabChange
}
value=
{
activeTab
}
position=
"relative"
size=
{
themeProps
.
size
}
ref=
{
tabsRef
}
>
<
AdaptiveTabsList
// the easiest and most readable way to achieve correct tab's cut recalculation when
// - screen is resized or
// - tabs list is changed when API data is loaded
// is to do full re-render of the tabs list
// so we use screenWidth + tabIds as a key for the TabsList component
key=
{
isLoading
+
'
_
'
+
screenWidth
+
'
_
'
+
tabsList
.
map
((
tab
)
=>
tab
.
id
).
join
(
'
:
'
)
}
tabs=
{
tabs
}
tabListProps=
{
tabListProps
}
leftSlot=
{
leftSlot
}
leftSlotProps=
{
leftSlotProps
}
rightSlot=
{
rightSlot
}
rightSlotProps=
{
rightSlotProps
}
stickyEnabled=
{
stickyEnabled
}
activeTab=
{
activeTab
}
onItemClick=
{
handleTabChange
}
themeProps=
{
themeProps
}
isLoading=
{
isLoading
}
/>
{
tabsList
.
map
((
tab
)
=>
(
<
TabsContent
padding=
{
0
}
key=
{
getTabValue
(
tab
)
}
value=
{
getTabValue
(
tab
)
}
>
{
tab
.
component
}
</
TabsContent
>
))
}
</
TabsRoot
>
);
};
export
default
React
.
memo
(
chakra
(
TabsWithScroll
));
ui/shared/Tabs/__screenshots__/TabsWithScroll.pw.tsx_default_with-counters-1.png
deleted
100644 → 0
View file @
d053f9da
5.29 KB
ui/shared/Tabs/types.ts
deleted
100644 → 0
View file @
d053f9da
import
type
React
from
'
react
'
;
export
interface
TabItem
{
// NOTE, in case of array of ids, when switching tabs, the first id will be used
// switching between other ids should be handled in the underlying component
id
:
string
|
Array
<
string
>
;
title
:
string
|
(()
=>
React
.
ReactNode
);
count
?:
number
|
null
;
component
:
React
.
ReactNode
;
}
export
type
RoutedTab
=
TabItem
&
{
subTabs
?:
Array
<
string
>
};
export
type
RoutedSubTab
=
Omit
<
TabItem
,
'
subTabs
'
>
;
export
interface
MenuButton
{
id
:
'
menu
'
;
title
:
string
;
count
?:
never
;
component
:
null
;
}
ui/shared/Tabs/useAdaptiveTabs.tsx
deleted
100644 → 0
View file @
d053f9da
import
React
from
'
react
'
;
import
type
{
MenuButton
,
RoutedTab
}
from
'
./types
'
;
export
default
function
useAdaptiveTabs
(
tabs
:
Array
<
RoutedTab
|
MenuButton
>
,
disabled
?:
boolean
)
{
// to avoid flickering we set initial value to 0
// so there will be no displayed tabs initially
const
[
tabsCut
,
setTabsCut
]
=
React
.
useState
(
disabled
?
tabs
.
length
:
0
);
const
[
tabsRefs
,
setTabsRefs
]
=
React
.
useState
<
Array
<
React
.
RefObject
<
HTMLButtonElement
>>>
([]);
const
listRef
=
React
.
useRef
<
HTMLDivElement
>
(
null
);
const
rightSlotRef
=
React
.
useRef
<
HTMLDivElement
>
(
null
);
const
leftSlotRef
=
React
.
useRef
<
HTMLDivElement
>
(
null
);
const
calculateCut
=
React
.
useCallback
(()
=>
{
const
listWidth
=
listRef
.
current
?.
getBoundingClientRect
().
width
;
const
rightSlotWidth
=
rightSlotRef
.
current
?.
getBoundingClientRect
().
width
||
0
;
const
leftSlotWidth
=
leftSlotRef
.
current
?.
getBoundingClientRect
().
width
||
0
;
const
tabWidths
=
tabsRefs
.
map
((
tab
)
=>
tab
.
current
?.
getBoundingClientRect
().
width
);
const
menuWidth
=
tabWidths
[
tabWidths
.
length
-
1
];
if
(
!
listWidth
||
!
menuWidth
)
{
return
tabs
.
length
;
}
const
{
visibleNum
}
=
tabWidths
.
slice
(
0
,
-
1
).
reduce
((
result
,
item
,
index
,
array
)
=>
{
if
(
!
item
)
{
return
result
;
}
if
(
result
.
visibleNum
<
index
)
{
// means that we haven't increased visibleNum on the previous iteration, so there is no space left
// we skip now till the end of the loop
return
result
;
}
if
(
index
===
array
.
length
-
1
)
{
// last element
if
(
result
.
accWidth
+
item
<
listWidth
-
rightSlotWidth
-
leftSlotWidth
)
{
return
{
visibleNum
:
result
.
visibleNum
+
1
,
accWidth
:
result
.
accWidth
+
item
};
}
}
else
{
if
(
result
.
accWidth
+
item
+
menuWidth
<
listWidth
-
rightSlotWidth
-
leftSlotWidth
)
{
return
{
visibleNum
:
result
.
visibleNum
+
1
,
accWidth
:
result
.
accWidth
+
item
};
}
}
return
result
;
},
{
visibleNum
:
0
,
accWidth
:
0
});
return
visibleNum
;
},
[
tabs
.
length
,
tabsRefs
]);
React
.
useEffect
(()
=>
{
setTabsRefs
(
tabs
.
map
((
_
,
index
)
=>
tabsRefs
[
index
]
||
React
.
createRef
()));
setTabsCut
(
disabled
?
tabs
.
length
:
0
);
// update refs only when disabled prop changes
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
disabled
]);
React
.
useEffect
(()
=>
{
if
(
tabsRefs
.
length
>
0
&&
!
disabled
)
{
setTabsCut
(
calculateCut
());
}
},
[
calculateCut
,
disabled
,
tabsRefs
]);
return
React
.
useMemo
(()
=>
{
return
{
tabsCut
,
tabsRefs
,
listRef
,
rightSlotRef
,
leftSlotRef
,
};
},
[
tabsCut
,
tabsRefs
]);
}
ui/shared/Tabs/useScrollToActiveTab.tsx
deleted
100644 → 0
View file @
d053f9da
import
React
from
'
react
'
;
interface
Props
{
activeTabIndex
:
number
;
tabsRefs
:
Array
<
React
.
RefObject
<
HTMLButtonElement
>>
;
listRef
:
React
.
RefObject
<
HTMLDivElement
>
;
isMobile
?:
boolean
;
isLoading
?:
boolean
;
}
export
default
function
useScrollToActiveTab
({
activeTabIndex
,
tabsRefs
,
listRef
,
isMobile
,
isLoading
}:
Props
)
{
React
.
useEffect
(()
=>
{
if
(
isLoading
)
{
return
;
}
if
(
activeTabIndex
<
tabsRefs
.
length
&&
isMobile
)
{
window
.
setTimeout
(()
=>
{
const
activeTabRef
=
tabsRefs
[
activeTabIndex
];
if
(
activeTabRef
.
current
&&
listRef
.
current
)
{
const
containerWidth
=
listRef
.
current
.
getBoundingClientRect
().
width
;
const
activeTabWidth
=
activeTabRef
.
current
.
getBoundingClientRect
().
width
;
const
left
=
tabsRefs
.
slice
(
0
,
activeTabIndex
)
.
map
((
tab
)
=>
tab
.
current
?.
getBoundingClientRect
())
.
filter
(
Boolean
)
.
map
((
rect
)
=>
rect
.
width
)
.
reduce
((
result
,
item
)
=>
result
+
item
,
0
);
const
isWithinFirstPage
=
containerWidth
>
left
+
activeTabWidth
;
if
(
isWithinFirstPage
)
{
return
;
}
listRef
.
current
.
scrollTo
({
left
,
behavior
:
'
smooth
'
,
});
}
// have to wait until DOM is updated and all styles to tabs is applied
},
300
);
}
// run only when tab index or device type is changed
// eslint-disable-next-line react-hooks/exhaustive-deps
},
[
activeTabIndex
,
isMobile
,
isLoading
]);
}
ui/shared/Tabs/useTabIndexFromQuery.tsx
deleted
100644 → 0
View file @
d053f9da
import
{
useRouter
}
from
'
next/router
'
;
import
type
{
RoutedTab
}
from
'
./types
'
;
import
getQueryParamString
from
'
lib/router/getQueryParamString
'
;
export
default
function
useTabIndexFromQuery
(
tabs
:
Array
<
RoutedTab
>
)
{
const
router
=
useRouter
();
const
tabFromQuery
=
getQueryParamString
(
router
.
query
.
tab
);
if
(
!
tabFromQuery
)
{
return
0
;
}
const
tabIndex
=
tabs
.
findIndex
(({
id
,
subTabs
})
=>
{
if
(
Array
.
isArray
(
id
))
{
return
id
.
includes
(
tabFromQuery
);
}
return
id
===
tabFromQuery
||
subTabs
?.
some
((
id
)
=>
id
===
tabFromQuery
);
});
if
(
tabIndex
<
0
)
{
return
0
;
}
return
tabIndex
;
}
ui/shared/Tabs/utils.ts
deleted
100644 → 0
View file @
d053f9da
import
type
{
MenuButton
,
TabItem
}
from
'
./types
'
;
import
{
middot
}
from
'
lib/html-entities
'
;
export
const
menuButton
:
MenuButton
=
{
id
:
'
menu
'
,
title
:
`
${
middot
}${
middot
}${
middot
}
`
,
component
:
null
,
};
export
const
getTabValue
=
(
tab
:
MenuButton
|
TabItem
):
string
=>
tab
.
id
.
toString
();
ui/shared/ad/CoinzillaTextAd.tsx
View file @
8600aeba
import
{
Box
,
Link
,
Text
,
chakra
}
from
'
@chakra-ui/react
'
;
import
{
Box
,
Text
,
chakra
}
from
'
@chakra-ui/react
'
;
import
React
,
{
useEffect
}
from
'
react
'
;
import
React
,
{
useEffect
}
from
'
react
'
;
import
{
ndash
}
from
'
lib/html-entities
'
;
import
{
ndash
}
from
'
lib/html-entities
'
;
import
isBrowser
from
'
lib/isBrowser
'
;
import
isBrowser
from
'
lib/isBrowser
'
;
import
{
Image
}
from
'
toolkit/chakra/image
'
;
import
{
Image
}
from
'
toolkit/chakra/image
'
;
import
{
Link
}
from
'
toolkit/chakra/link
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
import
{
Skeleton
}
from
'
toolkit/chakra/skeleton
'
;
type
AdData
=
{
type
AdData
=
{
...
@@ -52,6 +53,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
...
@@ -52,6 +53,7 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
if
(
isLoading
)
{
if
(
isLoading
)
{
return
(
return
(
<
Skeleton
<
Skeleton
loading
className=
{
className
}
className=
{
className
}
h=
{
{
base
:
12
,
lg
:
6
}
}
h=
{
{
base
:
12
,
lg
:
6
}
}
w=
"100%"
w=
"100%"
...
@@ -83,12 +85,16 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
...
@@ -83,12 +85,16 @@ const CoinzillaTextAd = ({ className }: { className?: string }) => {
<
Text
as=
"span"
mr=
{
1
}
>
🎨
</
Text
>
:
(
<
Text
as=
"span"
mr=
{
1
}
>
🎨
</
Text
>
:
(
<
Image
<
Image
src=
{
adData
.
ad
.
thumbnail
}
src=
{
adData
.
ad
.
thumbnail
}
containerProps=
{
{
width
:
'
20px
'
,
height
:
'
20px
'
,
mb
:
'
-4px
'
,
mr
:
1
,
display
:
'
inline-block
'
}
}
width=
"20px"
height=
"20px"
mb=
"-4px"
mr=
{
1
}
display=
"inline-block"
alt=
""
alt=
""
/>
/>
)
}
)
}
<
Text
as=
"span"
whiteSpace=
"pre-wrap"
>
{
`${ adData.ad.name } ${ ndash } ${ adData.ad.description_short } `
}
</
Text
>
<
Text
as=
"span"
whiteSpace=
"pre-wrap"
>
{
`${ adData.ad.name } ${ ndash } ${ adData.ad.description_short } `
}
</
Text
>
<
Link
href=
{
adData
.
ad
.
url
}
>
{
adData
.
ad
.
cta_button
}
</
Link
>
<
Link
href=
{
adData
.
ad
.
url
}
external
noIcon
>
{
adData
.
ad
.
cta_button
}
</
Link
>
</
Box
>
</
Box
>
);
);
};
};
...
...
ui/shared/entities/address/AddressEntity.tsx
View file @
8600aeba
...
@@ -154,7 +154,7 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
...
@@ -154,7 +154,7 @@ export interface EntityProps extends EntityBase.EntityBaseProps {
noAltHash
?:
boolean
;
noAltHash
?:
boolean
;
}
}
const
AddressEnt
r
y
=
(
props
:
EntityProps
)
=>
{
const
AddressEnt
it
y
=
(
props
:
EntityProps
)
=>
{
const
partsProps
=
distributeEntityProps
(
props
);
const
partsProps
=
distributeEntityProps
(
props
);
const
highlightContext
=
useAddressHighlightContext
(
props
.
noHighlight
);
const
highlightContext
=
useAddressHighlightContext
(
props
.
noHighlight
);
const
settingsContext
=
useSettingsContext
();
const
settingsContext
=
useSettingsContext
();
...
@@ -180,7 +180,7 @@ const AddressEntry = (props: EntityProps) => {
...
@@ -180,7 +180,7 @@ const AddressEntry = (props: EntityProps) => {
);
);
};
};
export
default
React
.
memo
(
chakra
(
AddressEnt
r
y
));
export
default
React
.
memo
(
chakra
(
AddressEnt
it
y
));
export
{
export
{
Container
,
Container
,
...
...
ui/shared/entities/token/TokenEntity.tsx
View file @
8600aeba
...
@@ -49,10 +49,7 @@ const Icon = (props: IconProps) => {
...
@@ -49,10 +49,7 @@ const Icon = (props: IconProps) => {
return
(
return
(
<
Image
<
Image
{
...
styles
}
{
...
styles
}
containerProps=
{
{
className=
{
props
.
className
}
className
:
props
.
className
,
...
styles
,
}
}
src=
{
props
.
token
.
icon_url
??
undefined
}
src=
{
props
.
token
.
icon_url
??
undefined
}
alt=
{
`${ props.token.name || 'token' } logo`
}
alt=
{
`${ props.token.name || 'token' } logo`
}
fallback=
{
<
TokenLogoPlaceholder
{
...
styles
}
/>
}
fallback=
{
<
TokenLogoPlaceholder
{
...
styles
}
/>
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment