-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4916 from cowprotocol/release/26-09-2024
Release/26-09-2024
- Loading branch information
Showing
233 changed files
with
10,983 additions
and
1,512 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import React from 'react' | ||
import { clickOnKnowledgeBase } from 'modules/analytics' | ||
import { LinkItem, LinkColumn } from '@/styles/styled' | ||
import { Article } from 'services/cms' | ||
|
||
interface ArticlesListProps { | ||
articles: Article[] | ||
} | ||
|
||
const ARTICLES_PATH = '/learn/articles/' | ||
|
||
export const ArticlesList: React.FC<ArticlesListProps> = ({ articles }) => ( | ||
<LinkColumn> | ||
{articles.map((article) => { | ||
if (!article.attributes) return null | ||
|
||
const { slug, title } = article.attributes | ||
|
||
return ( | ||
<LinkItem | ||
key={article.id} | ||
href={`${ARTICLES_PATH}${slug}`} | ||
onClick={() => clickOnKnowledgeBase(`click-article-${title}`)} | ||
> | ||
{title} | ||
<span>→</span> | ||
</LinkItem> | ||
) | ||
})} | ||
</LinkColumn> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import React from 'react' | ||
import styled from 'styled-components/macro' | ||
import { clickOnKnowledgeBase } from 'modules/analytics' | ||
import { Color, Media } from '@cowprotocol/ui' | ||
interface Category { | ||
name: string | ||
slug: string | ||
} | ||
|
||
interface CategoryLinksProps { | ||
allCategories: Category[] | ||
noDivider?: boolean | ||
} | ||
|
||
const CategoryLinksWrapper = styled.ul<{ noDivider?: boolean }>` | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
gap: 32px; | ||
padding: 0; | ||
margin: 0; | ||
list-style: none; | ||
font-size: 16px; | ||
font-weight: 500; | ||
color: ${Color.neutral50}; | ||
width: 100%; | ||
scrollbar-width: thin; | ||
scrollbar-color: ${Color.neutral70} ${Color.neutral90}; | ||
-webkit-overflow-scrolling: touch; | ||
&::-webkit-scrollbar { | ||
width: 8px; | ||
} | ||
&::-webkit-scrollbar-track { | ||
background: ${Color.neutral90}; | ||
border-radius: 10px; | ||
} | ||
&::-webkit-scrollbar-thumb { | ||
background: ${Color.neutral70}; | ||
border-radius: 10px; | ||
} | ||
&::-webkit-scrollbar-thumb:hover { | ||
background: ${Color.neutral50}; | ||
} | ||
${Media.upToMedium()} { | ||
overflow-x: auto; | ||
overflow-y: hidden; | ||
flex-flow: row nowrap; | ||
justify-content: flex-start; | ||
gap: 24px; | ||
padding: 16px 34px 16px 16px; | ||
} | ||
li { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
&:first-child { | ||
margin-right: ${({ noDivider }) => (noDivider ? '0' : '-32px')}; | ||
} | ||
&:first-child::after { | ||
content: ${({ noDivider }) => (noDivider ? 'none' : "'|'")}; | ||
margin: 0 16px; | ||
display: flex; | ||
height: 100%; | ||
width: 16px; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
} | ||
a { | ||
color: ${Color.neutral40}; | ||
text-decoration: none; | ||
transition: color 0.2s ease-in-out; | ||
white-space: nowrap; | ||
line-height: 1; | ||
&:hover { | ||
color: ${Color.neutral0}; | ||
} | ||
} | ||
` | ||
|
||
export const CategoryLinks: React.FC<CategoryLinksProps> = ({ allCategories, noDivider }) => ( | ||
<CategoryLinksWrapper noDivider={noDivider}> | ||
<li> | ||
<a href="/learn" onClick={() => clickOnKnowledgeBase('click-categories-home')}> | ||
Knowledge Base | ||
</a> | ||
</li> | ||
{allCategories.map((category) => ( | ||
<li key={category.slug}> | ||
<a | ||
href={`/learn/topic/${category.slug}`} | ||
onClick={() => clickOnKnowledgeBase(`click-categories-${category.name}`)} | ||
> | ||
{category.name} | ||
</a> | ||
</li> | ||
))} | ||
</CategoryLinksWrapper> | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import React, { useEffect, useRef, useState } from 'react' | ||
import SVG, { Props as SVGProps } from 'react-inlinesvg' | ||
|
||
interface LazySVGProps extends Omit<SVGProps, 'loader'> { | ||
src: string | ||
loader?: React.ReactNode | ||
rootMargin?: string | ||
} | ||
|
||
const LazySVG: React.FC<LazySVGProps> = ({ | ||
src, | ||
loader = <div>Loading SVG...</div>, | ||
rootMargin = '100px', | ||
width, | ||
height, | ||
...props | ||
}) => { | ||
const [isInView, setIsInView] = useState(false) | ||
const wrapperRef = useRef<HTMLSpanElement>(null) | ||
|
||
useEffect(() => { | ||
if (!wrapperRef.current) { | ||
return | ||
} | ||
|
||
const observer = new IntersectionObserver( | ||
(entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting) { | ||
setIsInView(true) | ||
observer.disconnect() | ||
} | ||
}) | ||
}, | ||
{ | ||
root: null, | ||
rootMargin: rootMargin, | ||
threshold: 0, | ||
}, | ||
) | ||
|
||
observer.observe(wrapperRef.current) | ||
|
||
return () => { | ||
observer.disconnect() | ||
} | ||
}, [rootMargin, src]) | ||
|
||
return ( | ||
<span ref={wrapperRef}> | ||
{isInView ? ( | ||
<SVG | ||
src={src} | ||
{...props} | ||
loader={loader} | ||
width={typeof width === 'number' ? width : undefined} | ||
height={typeof height === 'number' ? height : undefined} | ||
/> | ||
) : ( | ||
loader | ||
)} | ||
</span> | ||
) | ||
} | ||
|
||
export default LazySVG |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import React, { useEffect, useRef, useCallback, FC, ImgHTMLAttributes, memo } from 'react' | ||
|
||
let observer: IntersectionObserver | null = null | ||
const callbacks: ((entry: IntersectionObserverEntry) => void)[] = [] | ||
|
||
const LAZY_LOADING_CONFIG = { | ||
rootMargin: '25px', | ||
placeholderColor: '#f0f0f0', | ||
fadeInDuration: '0.3s', | ||
minHeight: '100px', | ||
} | ||
|
||
// Utility function to generate placeholder src | ||
const getPlaceholderSrc = (placeholderColor: string): string => { | ||
const encodedColor = encodeURIComponent(placeholderColor) | ||
return `data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' viewBox='0 0 1 1' preserveAspectRatio='none'><rect width='1' height='1' fill='${encodedColor}' /></svg>` | ||
} | ||
|
||
const PLACEHOLDER_SRC = getPlaceholderSrc(LAZY_LOADING_CONFIG.placeholderColor) | ||
|
||
const initObserver = () => { | ||
if (observer) return | ||
|
||
observer = new IntersectionObserver( | ||
(entries) => { | ||
entries.forEach((entry) => { | ||
callbacks.forEach((cb) => cb(entry)) | ||
}) | ||
}, | ||
{ rootMargin: LAZY_LOADING_CONFIG.rootMargin }, | ||
) | ||
} | ||
|
||
const addCallback = (cb: (entry: IntersectionObserverEntry) => void) => { | ||
callbacks.push(cb) | ||
initObserver() | ||
} | ||
|
||
const removeCallback = (cb: (entry: IntersectionObserverEntry) => void) => { | ||
const index = callbacks.indexOf(cb) | ||
if (index > -1) { | ||
callbacks.splice(index, 1) | ||
} | ||
// If no callbacks remain, disconnect the observer | ||
if (callbacks.length === 0 && observer) { | ||
observer.disconnect() | ||
observer = null | ||
} | ||
} | ||
|
||
export function useLazyLoadImages() { | ||
const replaceImageUrls = useCallback((html: string): string => { | ||
return html.replace(/<img([^>]*)src="([^"]*)"([^>]*)>/g, (_, before, src, after) => { | ||
return `<img${before}data-src="${src}" src="${PLACEHOLDER_SRC}"${after}>` | ||
}) | ||
}, []) | ||
|
||
const LazyImage: FC<ImgHTMLAttributes<HTMLImageElement>> = ({ src, alt = '', width, height, style, ...props }) => { | ||
const imgRef = useRef<HTMLImageElement>(null) | ||
|
||
useEffect(() => { | ||
if (!imgRef.current || !src) return | ||
|
||
const handleIntersect = (entry: IntersectionObserverEntry) => { | ||
const img = entry.target as HTMLImageElement | ||
if (entry.isIntersecting && img.dataset.src) { | ||
img.src = img.dataset.src | ||
img.style.opacity = '1' | ||
observer?.unobserve(img) | ||
} | ||
} | ||
|
||
addCallback(handleIntersect) | ||
|
||
observer?.observe(imgRef.current) | ||
|
||
return () => { | ||
if (imgRef.current) { | ||
observer?.unobserve(imgRef.current) | ||
} | ||
|
||
removeCallback(handleIntersect) | ||
} | ||
}, [src]) | ||
|
||
return ( | ||
<img | ||
ref={imgRef} | ||
src={PLACEHOLDER_SRC} | ||
data-src={src} | ||
alt={alt} | ||
width={width} | ||
height={height} | ||
{...props} | ||
style={{ | ||
minHeight: LAZY_LOADING_CONFIG.minHeight, | ||
opacity: 0, | ||
transition: `opacity ${LAZY_LOADING_CONFIG.fadeInDuration}`, | ||
...style, | ||
}} | ||
/> | ||
) | ||
} | ||
|
||
const MemoizedLazyImage = memo(LazyImage) | ||
|
||
return { replaceImageUrls, LazyImage: MemoizedLazyImage } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.