Merge pull request #133 from acid-info/refactor-components

Refactor components
This commit is contained in:
jeangovil 2023-08-29 19:19:43 +03:30 committed by GitHub
commit e300dc926f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 391 additions and 59 deletions

View File

@ -43,9 +43,12 @@ export const RenderArticleBlock = ({
return block.embed && isIframe ? (
block.labels.includes('youtube_embed') ? (
<ReactPlayer url={block.embed.src} />
<IframeContainer isSimplecast={false}>
<ReactPlayer url={block.embed.src} />
</IframeContainer>
) : (
<IframeContainer
isSimplecast={true}
dangerouslySetInnerHTML={{
__html: block.embed.html,
}}
@ -91,9 +94,9 @@ const Paragraph = styled(Typography)`
}
`
const IframeContainer = styled.div`
const IframeContainer = styled.div<{ isSimplecast?: boolean }>`
position: relative;
padding-bottom: 56.25%;
padding-bottom: ${({ isSimplecast }) => (isSimplecast ? '30%' : '60%')};
padding-top: 30px;
height: 0;
overflow: hidden;
@ -107,4 +110,10 @@ const IframeContainer = styled.div`
width: 100%;
height: 100%;
}
& > div {
width: 100% !important;
height: unset !important;
aspect-ratio: 16 / 9;
}
`

View File

@ -9,6 +9,7 @@ import { LPE } from '../../../types/lpe.types'
import { ArticleImageBlockWrapper } from '../Article.ImageBlockWrapper'
import ArticleStats from '../Article.Stats'
import ArticleSummary from './Article.Summary'
import { TagsAndSocial } from '@/components/TagsAndSocial'
export type ArticleHeaderProps = LPE.Article.Data
@ -53,7 +54,7 @@ const ArticleHeader = ({
{subtitle}
</ArticleSubtitle>
)}
<Tags tags={tags} className={'articleTags'} />
<TagsAndSocial tags={tags} className={'articleTags'} />
<AuthorsContainer>
<Authors authors={authors} email={true} gap={12} />
</AuthorsContainer>

View File

@ -7,6 +7,8 @@ import EpisodeStats from '../Episode.Stats'
import EpisodePlayer from './Episode.Player'
import Image from 'next/image'
import Link from 'next/link'
import { ShareButton } from '@/components/ShareButton'
import { TagsAndSocial } from '@/components/TagsAndSocial'
export type EpisodeHeaderProps = LPE.Podcast.Document & {
channel: LPE.Podcast.Channel
@ -51,7 +53,7 @@ const EpisodeHeader = ({
</Show>
</CustomLink>
)}
{tags && <Tags tags={tags} />}
<TagsAndSocial tags={tags} />
{channels && <EpisodeChannels channels={channels} />}
{description && (
<EpisodeSubtitle
@ -106,6 +108,7 @@ const Show = styled.div`
const CustomLink = styled(Link)`
text-decoration: none;
width: fit-content;
`
export default EpisodeHeader

View File

@ -7,6 +7,7 @@ import { episodeState } from '@/components/GlobalAudioPlayer/episode.state'
import SimplecastPlayer from './Episode.SimplecastPlayer'
import { LPE } from '@/types/lpe.types'
import { useRouter } from 'next/router'
import { ResponsiveImage } from '@/components/ResponsiveImage/ResponsiveImage'
export type EpisodePlayerProps = {
channel: LPE.Podcast.Channel
@ -29,6 +30,9 @@ const EpisodePlayer = ({
const playerContainerRef = useRef<HTMLDivElement>(null)
const playerRef = useRef<ReactPlayer>(null)
const [volume, setVolume] = useState(state.value.volume)
const [loading, setLoading] = useState(true)
const isSimplecast = channel?.name === LPE.Podcast.ChannelNames.Simplecast
const url =
@ -68,12 +72,11 @@ const EpisodePlayer = ({
}))
}
} else {
if (state.value.playing) {
state.set((prev) => ({
...prev,
isEnabled: true,
}))
}
state.set((prev) => ({
...prev,
isEnabled: true,
volume: volume,
}))
}
})
observer.observe(playerContainerRef.current as any)
@ -81,7 +84,7 @@ const EpisodePlayer = ({
return () => {
observer.disconnect()
}
}, [keepGlobalPlay])
}, [keepGlobalPlay, volume])
useEffect(() => {
const handleLeave = () => {
@ -110,22 +113,46 @@ const EpisodePlayer = ({
useEffect(() => {
if (!state.value.isEnabled) {
playerRef.current?.seekTo(state.value.played)
const offset = state.value.played === 0 ? 0 : 1 / state.value.duration // 1 second in %
playerRef.current?.seekTo(state.value.played + offset)
}
}, [state.value.isEnabled])
useEffect(() => {
if (channel?.name === LPE.Podcast.ChannelNames.Youtube) {
window.addEventListener('message', function (event) {
if (event.origin == 'https://www.youtube.com') {
const data = JSON.parse(event?.data)
const volume = data?.info?.volume
const listener = (event: any) => {
if (event.origin == 'https://www.youtube.com') {
const data = JSON.parse(event?.data)
if (typeof volume !== 'undefined') {
state.set((prev) => ({ ...prev, volume: volume / 100 }))
if (data?.info?.hasOwnProperty('muted') && !state.value.isEnabled) {
const isMuted = data.info.muted === true
const muteChanged = state.value.muted !== isMuted
const newVolume = data.info?.volume / 100 || state.value.volume
if (isMuted) {
if (muteChanged && !state.value.muted) {
// TODO : handle mute
}
} else if (!isMuted) {
if (muteChanged) {
state.set((prev) => ({
...prev,
muted: false,
volume: newVolume,
}))
}
setVolume(newVolume)
}
}
})
}
}
if (channel?.name === LPE.Podcast.ChannelNames.Youtube) {
window.addEventListener('message', listener)
}
return () => {
window.removeEventListener('message', listener)
}
}, [])
@ -166,7 +193,12 @@ const EpisodePlayer = ({
return (
<>
{isSimplecast && (
{loading && coverImage && (
<PlaceholderImage>
<ResponsiveImage data={coverImage} />
</PlaceholderImage>
)}
{!loading && isSimplecast && (
<SimplecastPlayer
playing={keepGlobalPlay ? false : state.value.playing}
playedSeconds={keepGlobalPlay ? 0 : state.value.playedSeconds}
@ -194,6 +226,7 @@ const EpisodePlayer = ({
onPlay={handlePlay}
onPause={handlePause}
onDuration={handleDuration}
onReady={() => setLoading(false)}
config={{
youtube: {
playerVars: { enablejsapi: 1 },
@ -228,4 +261,11 @@ const PlayerContainer = styled.div<{ isAudio: boolean }>`
}
`
const PlaceholderImage = styled.div`
position: absolute;
z-index: 1;
width: 100%;
aspect-ratio: 16/9;
`
export default EpisodePlayer

View File

@ -96,12 +96,6 @@ const SimplecastPlayer = ({
const Container = styled.div`
position: relative;
width: 100%;
padding-top: 56%;
background: red;
> * {
position: absolute;
}
`
const ImageContainer = styled.div`

View File

@ -50,5 +50,5 @@ const Links = styled(FooterSection)`
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 72px;
margin-bottom: 36px;
`

View File

@ -1,6 +1,6 @@
import ReactPlayer from 'react-player'
import styled from '@emotion/styled'
import React, { useEffect, useRef, useState } from 'react'
import React, { useEffect, useRef } from 'react'
import { CloseIcon, Typography } from '@acid-info/lsd-react'
import Image from 'next/image'
import { playerState } from './globalAudioPlayer.state'
@ -61,11 +61,9 @@ export default function GlobalAudioPlayer() {
globalPlayerRef.current?.seekTo(v)
}
const handleSeekMouseUp = () =>
// e: React.MouseEvent<HTMLInputElement, MouseEvent>,
{
state.set((prev) => ({ ...prev, seeking: false }))
}
const handleSeekMouseUp = () => {
state.set((prev) => ({ ...prev, seeking: false }))
}
const handleDuration = (duration: number) => {
state.set((prev) => ({ ...prev, duration }))
@ -94,7 +92,8 @@ export default function GlobalAudioPlayer() {
useEffect(() => {
if (state.value.isEnabled) {
globalPlayerRef.current?.seekTo(state.value.played)
const offset = 1 / state.value.duration // 1 second in %
globalPlayerRef.current?.seekTo(state.value.played + offset)
}
}, [state.value.isEnabled])
@ -171,7 +170,13 @@ export default function GlobalAudioPlayer() {
<CloseIcon
width={16}
height={16}
onClick={() => state.set((prev) => ({ ...prev, isEnabled: false }))}
onClick={() =>
state.set((prev) => ({
...prev,
isEnabled: false,
playing: false,
}))
}
/>
</CloseIconContainer>
</RightMenu>

View File

@ -0,0 +1,20 @@
import { LsdIcon } from '@acid-info/lsd-react'
export const CopyIcon = LsdIcon(
(props) => (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M9.33268 0.583313H2.33268C1.69102 0.583313 1.16602 1.10831 1.16602 1.74998V9.91665H2.33268V1.74998H9.33268V0.583313ZM8.74935 2.91665H4.66602C4.02435 2.91665 3.50518 3.44165 3.50518 4.08331L3.49935 12.25C3.49935 12.8916 4.01852 13.4166 4.66018 13.4166H11.0827C11.7243 13.4166 12.2493 12.8916 12.2493 12.25V6.41665L8.74935 2.91665ZM4.66602 12.25V4.08331H8.16602V6.99998H11.0827V12.25H4.66602Z"
fill="black"
/>
</svg>
),
{ filled: true },
)

View File

@ -0,0 +1 @@
export * from './CopyIcon'

View File

@ -0,0 +1,20 @@
import { LsdIcon } from '@acid-info/lsd-react'
export const ShareIcon = LsdIcon(
(props) => (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M10.5 9.37999C10.0567 9.37999 9.66 9.55499 9.35667 9.82916L5.1975 7.40832C5.22667 7.27416 5.25 7.13999 5.25 6.99999C5.25 6.85999 5.22667 6.72582 5.1975 6.59166L9.31 4.19416C9.625 4.48582 10.0392 4.66666 10.5 4.66666C11.4683 4.66666 12.25 3.88499 12.25 2.91666C12.25 1.94832 11.4683 1.16666 10.5 1.16666C9.53167 1.16666 8.75 1.94832 8.75 2.91666C8.75 3.05666 8.77333 3.19082 8.8025 3.32499L4.69 5.72249C4.375 5.43082 3.96083 5.24999 3.5 5.24999C2.53167 5.24999 1.75 6.03166 1.75 6.99999C1.75 7.96832 2.53167 8.74999 3.5 8.74999C3.96083 8.74999 4.375 8.56916 4.69 8.27749L8.84333 10.7042C8.81417 10.8267 8.79667 10.955 8.79667 11.0833C8.79667 12.0225 9.56083 12.7867 10.5 12.7867C11.4392 12.7867 12.2033 12.0225 12.2033 11.0833C12.2033 10.1442 11.4392 9.37999 10.5 9.37999ZM10.5 2.33332C10.8208 2.33332 11.0833 2.59582 11.0833 2.91666C11.0833 3.23749 10.8208 3.49999 10.5 3.49999C10.1792 3.49999 9.91667 3.23749 9.91667 2.91666C9.91667 2.59582 10.1792 2.33332 10.5 2.33332ZM3.5 7.58332C3.17917 7.58332 2.91667 7.32082 2.91667 6.99999C2.91667 6.67916 3.17917 6.41666 3.5 6.41666C3.82083 6.41666 4.08333 6.67916 4.08333 6.99999C4.08333 7.32082 3.82083 7.58332 3.5 7.58332ZM10.5 11.6783C10.1792 11.6783 9.91667 11.4158 9.91667 11.095C9.91667 10.7742 10.1792 10.5117 10.5 10.5117C10.8208 10.5117 11.0833 10.7742 11.0833 11.095C11.0833 11.4158 10.8208 11.6783 10.5 11.6783Z"
fill="black"
/>
</svg>
),
{ filled: true },
)

View File

@ -0,0 +1 @@
export * from './ShareIcon'

View File

@ -0,0 +1,20 @@
import { LsdIcon } from '@acid-info/lsd-react'
export const UnfilledPlayIcon = LsdIcon(
(props) => (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M5.83329 5.03996L8.90746 6.99996L5.83329 8.95996V5.03996ZM4.66663 2.91663V11.0833L11.0833 6.99996L4.66663 2.91663Z"
fill="black"
/>
</svg>
),
{ filled: true },
)

View File

@ -0,0 +1 @@
export * from './UnfilledPlayIcon'

View File

@ -94,7 +94,9 @@ export default function NavBar({ defaultState }: NavBarProps) {
<NavbarLinks links={NavLinksItems} />
</NavLinksContainer>
<ControlsContainer>
<SocialMediaKit />
<SocialMediaKitContainer>
<SocialMediaKit />
</SocialMediaKitContainer>
{buttons}
</ControlsContainer>
{showMobileMenu && <NavbarMobileMenu />}
@ -113,6 +115,12 @@ const PressLogoType = styled(Typography)<{ display: boolean }>`
`}
`
const SocialMediaKitContainer = styled.div`
@media (max-width: ${({ theme }) => theme.breakpoints.sm.width}px) {
display: none;
}
`
const Container = styled.header<{
bordered?: boolean
}>`

View File

@ -6,6 +6,7 @@ import { FooterOrgPanel } from '@/components/Footer/Footer.OrgPanel'
import { useThemeState } from '@/states/themeState'
import { ThemeSwitchWithLabel } from '@/components/ThemeSwitch/ThemeSwitch'
import { useEffect } from 'react'
import { SocialMediaKit } from './Navbar.SocialMediaKit'
interface Props {}
@ -24,13 +25,14 @@ export const NavbarMobileMenu = (props: Props) => {
<NavbarMobileMenuContainer>
<InnerContainer>
<NavbarLinks links={NavLinksItems} />
<FooterOrgPanel />
<ThemeSwitchContainer>
<SocialAndThemeSwitchContainer>
<SocialMediaKit />
<ThemeSwitchWithLabel
toggle={themeState.toggleMode}
mode={themeState.get().mode}
/>
</ThemeSwitchContainer>
</SocialAndThemeSwitchContainer>
<FooterOrgPanel />
</InnerContainer>
</NavbarMobileMenuContainer>
)
@ -70,3 +72,10 @@ const ThemeSwitchContainer = styled.div`
margin-top: 0;
padding-bottom: 16px;
`
const SocialAndThemeSwitchContainer = styled.div`
display: flex;
justify-content: space-between;
padding-top: 16px;
border-top: 1px solid rgb(var(--lsd-border-primary));
`

View File

@ -9,6 +9,7 @@ import { LPEFooterGroup } from '@/types/ui.types'
const socialLinks = FooterLinksItems.about.find(
(item) => item.key === 'social',
) as LPEFooterGroup
export const SocialMediaKit = () => {
return (
<Container>
@ -29,14 +30,18 @@ export const SocialMediaKit = () => {
Icon = null
}
return (
<Link
href={link.href}
key={`sm-link-${index}`}
title={`Join us on ${link.label}`}
target={'_blank'}
>
{Icon && <Icon />}
</Link>
Icon && (
<LinkContainer>
<Link
href={link.href}
key={`sm-link-${index}`}
title={`Join us on ${link.label}`}
target={'_blank'}
>
<Icon />
</Link>
</LinkContainer>
)
)
})}
</Container>
@ -46,8 +51,24 @@ export const SocialMediaKit = () => {
const Container = styled.div`
display: flex;
align-items: center;
gap: 8px;
gap: 16px;
a {
display: flex;
}
`
const LinkContainer = styled.div`
@media (max-width: ${({ theme }) => theme.breakpoints.sm.width}px) {
width: fit-content;
display: flex;
&:not(:last-child) {
&:after {
content: '';
margin-left: 16px;
border-right: 1px solid rgb(var(--lsd-border-primary));
}
}
}
`

View File

@ -3,8 +3,12 @@ import {
ResponsiveImageProps,
} from '@/components/ResponsiveImage/ResponsiveImage'
import { LPE } from '@/types/lpe.types'
import { IconButton } from '@acid-info/lsd-react'
import Link from 'next/link'
import { FC } from 'react'
import { PlayIcon } from '../Icons/PlayIcon'
import styled from '@emotion/styled'
import { UnfilledPlayIcon } from '../Icons/UnfilledPlayIcon'
export type PostCardCoverProps = React.ComponentProps<typeof Link> & {
imageProps: ResponsiveImageProps
@ -18,8 +22,28 @@ export const PostCardCover: FC<PostCardCoverProps> = ({
...props
}) => {
return (
<Link {...props} className={`post-card__cover-image ${props.className}`}>
<CustomLink
{...props}
className={`post-card__cover-image ${props.className}`}
>
<ResponsiveImage {...imageProps} data={imageData} />
</Link>
{playIcon && (
<Icon size="small">
<UnfilledPlayIcon />
</Icon>
)}
</CustomLink>
)
}
const CustomLink = styled(Link)`
position: relative;
`
const Icon = styled(IconButton)`
position: absolute;
bottom: 8px;
right: 8px;
background: white;
border: none;
`

View File

@ -4,12 +4,11 @@ import {
PostCardShowDetailsProps,
} from '@/components/PostCard/PostCard.ShowDetails'
import { Tags } from '@/components/Tags'
import { Theme, Typography } from '@acid-info/lsd-react'
import { Theme } from '@acid-info/lsd-react'
import { CommonProps } from '@acid-info/lsd-react/dist/utils/useCommonProps'
import { css } from '@emotion/react'
import styled from '@emotion/styled'
import clsx from 'clsx'
import Link from 'next/link'
import React from 'react'
import { LPE } from '../../types/lpe.types'
import { lsdUtils } from '../../utils/lsd.utils'
@ -73,7 +72,12 @@ export const PostCard = (_props: PostCardProps) => {
: `/podcasts/${podcastShowDetails?.slug}/${slug}`
const coverImageElement = coverImage && (
<PostCardCover href={link} imageProps={imageProps} imageData={coverImage} />
<PostCardCover
href={link}
imageProps={imageProps}
imageData={coverImage}
playIcon={contentType === LPE.PostTypes.Podcast}
/>
)
const labelElement = (

View File

@ -0,0 +1,118 @@
import { Tag, Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { ShareIcon } from '../Icons/ShareIcon'
import { useRef, useState } from 'react'
import { CopyIcon } from '../Icons/CopyIcon'
import { XIcon } from '../Icons/XIcon'
import Link from 'next/link'
import { useClickAway } from 'react-use'
type Props = {
url: string
}
export default function ShareButton({ url }: Props) {
const [showOptions, setShowOptions] = useState(false)
const [copied, setCopied] = useState(false)
const ref = useRef(null)
useClickAway(ref, () => {
setShowOptions(false)
})
const handleCopyClipBoard = async (text: string) => {
await navigator.clipboard.writeText(text)
setCopied(true)
// TODO : Temporary solution
setTimeout(() => {
setCopied(false)
}, 2000)
}
return (
<Container ref={ref}>
<CustomTag
onClick={() => setShowOptions(!showOptions)}
icon={<ShareIcon width={14} height={14} />}
iconDirection="left"
showOptions={showOptions}
>
<Typography variant="body3">Share</Typography>
</CustomTag>
{showOptions && (
<Options>
<Label>
<Typography variant="body3">Share Options</Typography>
</Label>
<ShareOption onClick={() => handleCopyClipBoard(url)}>
<CopyIcon width={14} height={14} />
<Typography variant="body2">
{copied ? 'Copied' : 'Copy link'}
</Typography>
</ShareOption>
<CustomLink href={`http://www.twitter.com/share?url=${url}`}>
<ShareOption>
<XIcon />
<Typography variant="body2">X</Typography>
</ShareOption>
</CustomLink>
</Options>
)}
</Container>
)
}
const HEIGHT = 24
const Container = styled.div`
position: relative;
`
const CustomTag = styled(Tag)<{ showOptions: boolean }>`
width: 69px;
height: ${HEIGHT}px;
padding: 0 8px;
border-bottom: ${(props) =>
props.showOptions
? '1px solid transparent'
: '1px solid rgb(var(--lsd-border-primary))'};
`
const Options = styled.div`
top: ${HEIGHT - 1}px;
left: 0;
width: 169px;
border: 1px solid rgb(var(--lsd-border-primary));
position: absolute;
background-color: rgb(var(--lsd-surface-primary));
padding-bottom: 8px;
z-index: 1;
`
const Label = styled.div`
padding: 12px;
`
const ShareOption = styled.div`
cursor: pointer;
padding: 6px 12px 6px 14px;
display: flex;
gap: 14px;
align-items: center;
&:hover {
text-decoration: underline;
}
`
const CustomLink = styled(Link)`
text-decoration: none;
display: flex;
align-items: center;
gap: 14px;
&:hover {
text-decoration: underline;
}
`

View File

@ -0,0 +1 @@
export { default as ShareButton } from './ShareButton'

View File

@ -0,0 +1,34 @@
import styled from '@emotion/styled'
import { ShareButton } from '../ShareButton'
import { Tags } from '../Tags'
export type TagsProps = {
tags: string[]
className?: string
}
const TagsAndSocial: React.FC<TagsProps> = ({ tags, className }) => {
const currentUrl = typeof window !== 'undefined' ? window.location.href : ''
return (
<Container>
{tags && <Tags tags={tags} className={className} />}
<VerticalLine />
<ShareButton url={currentUrl} />
</Container>
)
}
const Container = styled.div`
display: flex;
align-items: center;
gap: 16px;
width: fit-content;
`
const VerticalLine = styled.div`
height: 12px;
border-left: 1px solid rgb(var(--lsd-border-primary));
`
export default TagsAndSocial

View File

@ -0,0 +1 @@
export { default as TagsAndSocial } from './TagsAndSocial'

View File

@ -1,7 +1,4 @@
import { LPEFooterGroup } from '@/types/ui.types'
import { DiscordIcon } from '@/components/Icons/DiscordIcon'
import { YoutubeIcon } from '@/components/Icons/YTIcon'
import { XIcon } from '@/components/Icons/XIcon'
export const ArticleBlocksOrders = {
title: 0,
@ -61,12 +58,12 @@ export const FooterLinksItems: {
title: null,
key: 'social',
links: [
{ label: 'X', href: 'https://twitter.com/Logos_state', key: 'x' },
{
label: 'Discord',
href: 'https://discord.gg/logos-state',
key: 'discord',
},
{ label: 'X', href: 'https://twitter.com/Logos_state', key: 'x' },
{
label: 'Github',
href: 'https://github.com/acid-info',