import React from 'react' ; import styled from '@emotion/styled' ; import { Stack , StackProps , Avatar , Text , Image , Box , BoxProps , Flex , FlexProps , Grid , GridProps , useColorModeValue } from '@chakra-ui/react' ; import { OutgoingLink , IconButtonOutgoingLink } from '@47ng/chakra-next' ; import { FiHeart , FiRepeat , FiMessageSquare , FiTwitter } from 'react-icons/fi' ; import { H5 } from 'src/components/primitives/Typography' ; import { formatDate , formatTime } from 'src/ui/format' ; import { safeContentID } from 'src/data/services/twitter' ; export interface TweetPhotoData { type : 'photo' ; src : string ; url : string ; alt ? : string ; } export interface TweetVideoData { type : 'video' ; src : string ; url : string ; alt ? : string ; sources : { url : string ; type : string ; } [ ] ; } export interface TweetData { author : { avatarURL : string ; displayName : string ; handle : string ; } ; largeText : boolean ; body : string ; quotedTweet ? : TweetData ; media : ( TweetPhotoData | TweetVideoData ) [ ] ; meta : { url : string ; date : number ; likes : number ; retweets : number ; } ; } export interface TweetProps extends StackProps , TweetData { } export const Tweet = ( { author , meta , body , media , largeText , quotedTweet , ... props } ) => { const baseProps = { mx : 'auto' , rounded : 'lg' , borderWidth : 1 , borderColor : useColorModeValue ( 'gray.300' , 'gray.700' ) , p : 4 } ; return ( < Stack mb = { 8 } maxW = "600px" { ... baseProps } { ... props } > < TweetHeader author = { author } url = { meta . url } mb = { 4 } / > < TweetBody body = { body } largeText = { largeText } / > { media . length > 0 && < TweetMedia media = { media } />} { ! ! quotedTweet && ( < Stack { ... baseProps } maxW = "100%" my = { 4 } > < CompactTweetHeader author = { quotedTweet . author } meta = { quotedTweet . meta } / > < TweetBody my = { 0 } body = { quotedTweet . body } largeText = { false } wordBreak = "break-word" / > < /Stack>)} < TweetFooter { ... meta } mb = { - 2 } /> < /Stack>); } ; const AuthorName = styled ( H5 ) `
  a:hover & {
    text-decoration: underline;
  }
` ; interface TweetHeaderProps extends FlexProps { author : TweetData [ 'author' ] ; url : string ; } const TweetHeader = ( { author , url , ... props } ) => ( < Flex as = "header" justifyContent = "space-between" { ... props } > < OutgoingLink href = { `https://twitter.com/${ author . handle }` } _hover = { { textDecoration : 'none' } } > < Stack as = "header" isInline alignItems = "center" > < Avatar name = { author . displayName } src = { author . avatarURL } backgroundColor = "transparent" / > < Box > < AuthorName my = { 0 } > { author . displayName } < /AuthorName> < Text fontSize = "sm" color = "gray.600" > @ { author . handle } < /Text> < /Box> < /Stack> < /OutgoingLink> < IconButtonOutgoingLink href = { url } aria - label = "Open on Twitter" icon = { < FiTwitter / > } color = "gray.600" rounded = "full" variant = "ghost" mr = { - 2 } mt = { - 2 } /> < /Flex>); interface CompactTweetHeaderProps extends StackProps { author : TweetData [ 'author' ] ; meta : TweetData [ 'meta' ] ; } const CompactTweetHeader = ( { author , meta , ... props } ) => ( < Stack as = "header" isInline { ... props } > < OutgoingLink href = { `https://twitter.com/${ author . handle }` } _hover = { { textDecoration : 'none' } } > < Stack as = "header" isInline alignItems = "center" > < Avatar size = "2xs" name = { author . displayName } src = { author . avatarURL } backgroundColor = "transparent" / > < AuthorName as = "h6" my = { 0 } > { author . displayName } < /AuthorName> < Text fontSize = "sm" color = "gray.600" > @ { author . handle } < /Text> < /Stack> < /OutgoingLink> < Text fontSize = "sm" color = "gray.600" > • < /Text> < OutgoingLink href = { meta . url } fontSize = "sm" color = "gray.600" > { formatDate ( meta . date ) } < /OutgoingLink> < Box ml = "auto" > < IconButtonOutgoingLink href = { meta . url } aria - label = "Open on Twitter" icon = { < FiTwitter / > } color = "gray.600" rounded = "full" variant = "ghost" mt = { - 2 } mr = { - 2 } /> < /Box> < /Stack>); type TweetContentProps = Pick < TweetData , 'body' | 'largeText' > & BoxProps ; const StyledBody = styled ( Box ) `
  img.emoji {
    display: inline-block;
    height: 1.1em;
    width: 1.1em;
    margin: 0 0 0 0.1em;
    vertical-align: -0.1em;
  }
` ; const TweetBody = ( { body , largeText , ... props } ) => ( < StyledBody mb = { 4 } fontSize = { largeText ? 'lg' : 'md' } fontWeight = "medium" dangerouslySetInnerHTML = { { __html : body } } { ... props } />); const TweetFooter = ( { url , date , likes , retweets , ... props } ) => { const id = safeContentID ( url ) ; return ( < Flex as = "footer" justifyContent = "space-between" alignItems = "center" color = "gray.600" { ... props } > < OutgoingLink href = { url } fontSize = { [ 'xs' , 'sm' ] } > { formatTime ( date ) } • { formatDate ( date ) } < /OutgoingLink> < Stack isInline justifyContent = "space-evenly" fontSize = { [ 'xs' , 'sm' ] } spacing = { [ 1 , 2 ] } > < Flex alignItems = "center" > < IconButtonOutgoingLink icon = { < FiMessageSquare />} color="currentcolor" href={`https:/ / twitter . com / intent / tweet ? in_reply_to = $ { id } `} aria-label="Reply" variant="ghost" rounded="full" fontSize={['sm', 'md']}/>
          <Text>Reply</Text>
        </Flex>
        <Flex alignItems="center">
          <IconButtonOutgoingLink icon={<FiRepeat />} color="currentcolor" href={` https : < Text > { retweets } < /Text> < /Flex> < Flex alignItems = "center" > < IconButtonOutgoingLink icon = { < FiHeart />} color="currentcolor" href={`https:/ / twitter . com / intent / like ? tweet_id = $ { id } `} aria-label="Like" variant="ghost" rounded="full" fontSize={['sm', 'md']}/>
          <Text>{likes}</Text>
        </Flex>
      </Stack>
    </Flex>);
};
// --
interface TweetMediaProps extends GridProps {
    media: TweetData['media'];
}
const TweetMedia = ({ media, ...props }) => (<Grid templateColumns={media.length === 1 ? '1fr' : '1fr 1fr'} templateRows={media.length > 2 ? '1fr 1fr' : '1fr'} rounded="md" overflow="hidden" gridGap={1} maxH="350px" my={2} {...props}>
    {media.map((img, i) => img.type === 'video' ? (<video key={img.url} autoPlay={false} controls poster={img.src} style={{ margin: '0 auto' }}>
          {img.sources.map(src => (<source key={src.url} src={src.url} type={src.type}/>))}
        </video>) : (<Image src={img.src} alt={img.alt} key={img.url} maxH="568px" w="100%" h="100%" objectFit="cover" objectPosition="center" gridRow={i === 0 && media.length === 3 ? '1/3' : undefined}/>))}
  </Grid>);
