Add page with new library layout

This commit is contained in:
Jackson Harper 2022-08-11 17:36:09 +08:00
parent 858448e0d4
commit 695a9ce649
10 changed files with 562 additions and 0 deletions

View file

@ -0,0 +1,21 @@
type LibraryGridLayoutIconProps = {
color: string
}
export function LibraryGridLayoutIcon(props: LibraryGridLayoutIconProps): JSX.Element {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3573_89627)">
<path d="M9.5 -0.00299072H1.5C0.671573 -0.00299072 0 0.668582 0 1.49701V9.49701C0 10.3254 0.671573 10.997 1.5 10.997H9.5C10.3284 10.997 11 10.3254 11 9.49701V1.49701C11 0.668582 10.3284 -0.00299072 9.5 -0.00299072Z" fill={props.color} />
<path d="M22.5 -0.00299072H14.5C13.6716 -0.00299072 13 0.668582 13 1.49701V9.49701C13 10.3254 13.6716 10.997 14.5 10.997H22.5C23.3284 10.997 24 10.3254 24 9.49701V1.49701C24 0.668582 23.3284 -0.00299072 22.5 -0.00299072Z" fill={props.color} />
<path d="M9.5 12.997H1.5C0.671573 12.997 0 13.6686 0 14.497V22.497C0 23.3254 0.671573 23.997 1.5 23.997H9.5C10.3284 23.997 11 23.3254 11 22.497V14.497C11 13.6686 10.3284 12.997 9.5 12.997Z" fill={props.color} />
<path d="M22.5 12.997H14.5C13.6716 12.997 13 13.6686 13 14.497V22.497C13 23.3254 13.6716 23.997 14.5 23.997H22.5C23.3284 23.997 24 23.3254 24 22.497V14.497C24 13.6686 23.3284 12.997 22.5 12.997Z" fill={props.color} />
</g>
<defs>
<clipPath id="clip0_3573_89627">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>
)
}

View file

@ -0,0 +1,13 @@
type LibraryListLayoutIconProps = {
color: string
}
export function LibraryListLayoutIcon(props: LibraryListLayoutIconProps): JSX.Element {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.5 0.747009H1.5C0.671573 0.747009 0 1.41858 0 2.24701V4.74701C0 5.57544 0.671573 6.24701 1.5 6.24701H22.5C23.3284 6.24701 24 5.57544 24 4.74701V2.24701C24 1.41858 23.3284 0.747009 22.5 0.747009Z" fill={props.color} />
<path d="M22.5 9.24701H1.5C0.671573 9.24701 0 9.91858 0 10.747V13.247C0 14.0754 0.671573 14.747 1.5 14.747H22.5C23.3284 14.747 24 14.0754 24 13.247V10.747C24 9.91858 23.3284 9.24701 22.5 9.24701Z" fill={props.color} />
<path d="M22.5 17.747H1.5C0.671573 17.747 0 18.4186 0 19.247V21.747C0 22.5754 0.671573 23.247 1.5 23.247H22.5C23.3284 23.247 24 22.5754 24 21.747V19.247C24 18.4186 23.3284 17.747 22.5 17.747Z" fill={props.color} />
</svg>
)
}

View file

@ -0,0 +1,178 @@
import { Box, VStack, HStack, SpanBox } from '../../elements/LayoutPrimitives'
import { CoverImage } from '../../elements/CoverImage'
import { StyledText } from '../../elements/StyledText'
import { authoredByText } from '../ArticleSubtitle'
import { MoreOptionsIcon } from '../../elements/images/MoreOptionsIcon'
import { theme } from '../../tokens/stitches.config'
import { CardMenu } from '../CardMenu'
import { LabelChip } from '../../elements/LabelChip'
import { ProgressBar } from '../../elements/ProgressBar'
import type { LinkedItemCardProps } from './CardTypes'
export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
return (
<VStack
css={{
p: '12px',
height: '100%',
width: '100%',
maxWidth: '100%',
borderRadius: '8px',
cursor: 'pointer',
wordBreak: 'break-word',
overflow: 'clip',
border: '1px solid $border',
position: 'relative',
}}
alignment="start"
distribution="start"
onClick={() => {
props.handleAction('showDetail')
}}
>
{props.item.image && (
<CoverImage
src={props.item.image}
alt="Link Preview Image"
width="100%"
height={160}
css={{ borderRadius: '8px' }}
onError={(e) => {
;(e.target as HTMLElement).style.display = 'none'
}}
/>
)}
<VStack
distribution="start"
alignment="start"
css={{
px: '0px',
width: '100%',
pl: '$1',
}}
>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
p: '0px',
mr: '-12px',
mt: '15px',
display: 'grid',
gridTemplateColumns: '1fr 24px',
gridTemplateRows: '1fr',
}}
>
<CardTitle title={props.item.title} />
{/* <Box
css={{ alignSelf: 'end', alignItems: 'start', height: '100%' }}
onClick={(e) => {
// This is here to prevent menu click events from bubbling
// up and causing us to "click" on the link item.
e.stopPropagation()
}}
>
<CardMenu
item={props.item}
viewer={props.viewer}
triggerElement={
<MoreOptionsIcon
size={24}
strokeColor={theme.colors.grayTextContrast.toString()}
orientation="horizontal"
/>
}
actionHandler={props.handleAction}
/>
</Box> */}
</HStack>
{/* <HStack alignment="start" distribution="between">
<StyledText style="caption" css={{ my: '0', mt: '-$2' }}>
{props.item.author && (
<SpanBox css={{ mr: '8px' }}>
{authoredByText(props.item.author)}
</SpanBox>
)}
<SpanBox css={{ textDecorationLine: 'underline' }}>
{props.originText}
</SpanBox>
</StyledText>
</HStack> */}
</VStack>
<HStack
alignment="start"
distribution="between"
css={{
width: '100%',
pt: '$2',
px: '$1',
pr: '12px',
mt: '7px',
flexGrow: '1',
}}
>
<StyledText
css={{
m: 0,
py: '0px',
mr: '$2',
fontStyle: 'normal',
fontWeight: '400',
fontSize: '14px',
lineHeight: '125%',
color: '$grayTextContrast',
flexGrow: '4',
overflow: 'hidden',
display: '-webkit-box',
WebkitLineClamp: 5,
WebkitBoxOrient: 'vertical',
}}
data-testid="listDesc"
>
{props.item.description}
</StyledText>
</HStack>
<Box css={{ display: 'block', py: '12px' }}>
{props.item.labels?.map(({ name, color }, index) => (
<LabelChip key={index} text={name || ''} color={color} />
))}
</Box>
<ProgressBar
fillPercentage={props.item.readingProgressPercent}
fillColor={theme.colors.highlight.toString()}
backgroundColor={theme.colors.lightBorder.toString()}
borderRadius={
props.item.readingProgressPercent === 100 ? '0' : '0px 8px 8px 0px'
}
/>
</VStack>
)
}
type CardTitleProps = {
title: string
}
function CardTitle(props: CardTitleProps): JSX.Element {
return (
<StyledText
style="listTitle"
data-testid="listTitle"
css={{
mt: '0',
mb: '0',
fontSize: '18px',
textAlign: 'left',
lineHeight: '1.25',
// whiteSpace: 'nowrap',
// textOverflow: 'ellipsis',
width: '100%',
// overflow: 'hidden',
}}
>
{props.title}
</StyledText>
)
}

View file

@ -0,0 +1,62 @@
import { SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { styled } from '../../tokens/stitches.config'
import { Root, Image, Fallback } from '@radix-ui/react-avatar'
type AvatarProps = {
viewer?: UserBasicData
}
export function LibraryAvatar(props: AvatarProps): JSX.Element {
return (
<VStack alignment="center" distribution="start" css={{ pl: '8px', pt: '20px', width: '100%', height: '100%' }}>
<VStack css={{ height: '100%' }}>
<StyledAvatar
css={{
width: '40px',
height: '40px',
borderRadius: '6px',
}}
>
{props.viewer?.profile.pictureUrl
? <StyledImage src={props.viewer.profile.pictureUrl} />
: <StyledFallback>{props.viewer?.name.charAt(0) ?? ''}</StyledFallback>
}
</StyledAvatar>
</VStack>
{/* This spacer is to help align with items in the search box */}
<SpanBox css={{ marginTop: 'auto', height: '10px', width: '100%' }} />
</VStack>
)
}
const StyledAvatar = styled(Root, {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
verticalAlign: 'middle',
overflow: 'hidden',
userSelect: 'none',
})
const StyledImage = styled(Image, {
width: '100%',
height: '100%',
objectFit: 'cover',
'&:hover': {
opacity: '48%',
},
})
const StyledFallback = styled(Fallback, {
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
backgroundColor: '$omnivoreCtaYellow',
color: '#595959',
})

View file

@ -0,0 +1,35 @@
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { LibraryMenu } from './LibraryMenu'
import { LibraryAvatar } from './LibraryAvatar'
import { LibrarySearchBar } from './LibrarySearchBar'
import { LibraryList } from './LibraryList'
import { LibraryHeadline } from './LibraryHeadline'
export function LibraryContainer(): JSX.Element {
useGetUserPreferences()
const { viewerData } = useGetViewerQuery()
return (
<>
<VStack alignment="start" distribution="start" css={{ width: '100vw', height: '100vh', overflow: 'hidden', bg: '$libraryBackground' }}>
<HStack alignment="start" distribution="start" css={{ width: '100%' }}>
<LibrarySearchBar />
<SpanBox css={{ marginLeft: 'auto', width: '130px', height: '100%' }}>
<LibraryAvatar viewer={viewerData?.me} />
</SpanBox>
</HStack>
<HStack alignment="start" distribution="start" css={{ width: '100%', height: '100%' }}>
<LibraryMenu />
<VStack alignment="start" distribution="start" css={{ width: '100%', height: '100%', mr: '20px' }}>
<LibraryHeadline />
<LibraryList />
</VStack>
</HStack>
</VStack>
</>
)
}

View file

@ -0,0 +1,23 @@
import { HStack } from '../../elements/LayoutPrimitives'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { StyledText } from '../../elements/StyledText'
import { theme } from '../../tokens/stitches.config'
import { LibraryListLayoutIcon } from '../../elements/images/LibraryListLayoutIcon'
import { LibraryGridLayoutIcon } from '../../elements/images/LibraryGridLayoutIcon'
import { Button } from '../../elements/Button'
export function LibraryHeadline(): JSX.Element {
useGetUserPreferences()
return (
<HStack alignment="center" distribution="start" css={{ pt: '4px', pb: '8px', width: '100%', pr: '15px' }}>
<StyledText style="libraryHeader">Home</StyledText>
<HStack alignment="center" distribution="start" css={{ marginLeft: 'auto', gap: '16px' }}>
<Button style="ctaDarkYellow">Add Link</Button>
<LibraryListLayoutIcon color="#D6D6D6" />
<LibraryGridLayoutIcon color={theme.colors.omnivoreCtaYellow.toString()} />
</HStack>
</HStack>
)
}

View file

@ -0,0 +1,114 @@
import { Box } from '../../elements/LayoutPrimitives'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { useMemo, useState } from 'react'
import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LibraryGridCard } from '../../patterns/LibraryCards/LibraryGridCard'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
export function LibraryList(): JSX.Element {
useGetUserPreferences()
const [layout, setLayout] = useState<LayoutType>('GRID_LAYOUT')
const { viewerData } = useGetViewerQuery()
const defaultQuery = {
limit: 50,
sortDescending: true,
searchQuery: undefined,
}
const { itemsPages, size, setSize, isValidating, performActionOnItem } =
useGetLibraryItemsQuery(defaultQuery)
const libraryItems = useMemo(() => {
const items =
itemsPages?.flatMap((ad) => {
return ad.search.edges
}) || []
return items
}, [itemsPages, performActionOnItem])
return (
<Box css={{ overflowY: 'scroll' }}>
{/* // {!isValidating && items.length == 0 ? (
// <EmptyLibrary
// onAddLinkClicked={() => {
// }}
// />
// ) : ( */}
<Box
css={{
display: 'grid',
gridGap: layout == 'LIST_LAYOUT' ? '0' : '16px',
width: '100%',
gridAutoRows: 'auto',
borderRadius: '8px',
marginBottom: '0px',
paddingRight: '14px',
paddingTop: '0px',
marginTop: layout == 'LIST_LAYOUT' ? '21px' : '0',
paddingBottom: layout == 'LIST_LAYOUT' ? '0px' : '21px',
'@smDown': {
border: 'unset',
width: layout == 'LIST_LAYOUT' ? '100vw' : undefined,
margin: layout == 'LIST_LAYOUT' ? '16px -16px' : undefined,
borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined,
},
'@lg': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : '1fr 1fr',
},
'@xl': {
gridTemplateColumns:
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(3, 1fr)',
},
}}
>
{libraryItems.map((linkedItem) => (
<Box
className="linkedItemCard"
data-testid="linkedItemCard"
id={linkedItem.node.id}
tabIndex={0}
key={linkedItem.node.id}
css={{
width: '100%',
'&> div': {
bg: '$libraryBackground',
},
'&:focus': {
'> div': {
bg: '$grayBgActive',
},
},
'&:hover': {
'> div': {
bg: '$grayBgActive',
},
},
}}
>
{viewerData?.me && (
<LibraryGridCard
layout={layout}
item={linkedItem.node}
viewer={viewerData.me}
handleAction={(action: LinkedItemCardAction) => {
}}
/>
)}
</Box>
))}
</Box>
{/* Extra padding at bottom to give space for scrolling */}
<Box css={{ width: '100%', height: '200px' }} />
</Box>
)
}

View file

@ -0,0 +1,14 @@
import { VStack } from '../../elements/LayoutPrimitives'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { Menubar } from '../Menu'
export function LibraryMenu(): JSX.Element {
useGetUserPreferences()
return (
<VStack alignment="start" distribution="start" css={{ width: '286px', pl: '16px', height: '100%' }}>
<Menubar />
</VStack>
)
}

View file

@ -0,0 +1,97 @@
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import { useRouter } from 'next/router'
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
import { useState } from 'react'
import { FormInput } from '../../elements/FormElements'
import { Button } from '../../elements/Button'
import { X } from 'phosphor-react'
import { theme } from '../../tokens/stitches.config'
export function LibrarySearchBar(): JSX.Element {
useGetUserPreferences()
const router = useRouter()
const [searchTerm, setSearchTerm] = useState('')
const { viewerData } = useGetViewerQuery()
return (
<>
<VStack alignment="start" distribution="start" css={{ pl: '32px', pt: '20px', width: '100%', height: '110px' }}>
<HStack alignment="start" distribution="start" css={{ width: '100%' }}>
<form
style={{ width: '100%' }}
onSubmit={(event) => {
event.preventDefault()
// props.applySearchQuery(searchTerm || '')
// inputRef.current?.blur()
}}
>
<FormInput
css={{
width: '100%',
height: '80px',
fontFamily: 'Inter',
fontSize: '24px',
}}
type="text"
value={searchTerm}
placeholder="Search"
// onFocus={(event) => {
// event.target.select()
// setFocused(true)
// }}
// onBlur={() => {
// setFocused(false)
// }}
// onChange={(event) => {
// setSearchTerm(event.target.value)
// }}
/>
</form>
{searchTerm && (
<HStack alignment="center" distribution="start" css={{ height: '100%' }}>
<Button
style="plainIcon"
onClick={(event) => {
// event.preventDefault()
// setSearchTerm('')
// props.applySearchQuery('')
// inputRef.current?.blur()
}}
css={{
display: 'flex',
flexDirection: 'row',
mr: '16px',
height: '100%',
alignItems: 'center',
}}
>
<X
width={24}
height={24}
color={theme.colors.grayTextContrast.toString()}
/>
</Button>
<Button
style="ctaDarkYellow"
onClick={(event) => {
// event.preventDefault()
// setSearchTerm('')
// props.applySearchQuery('')
// inputRef.current?.blur()
}}
>
Search
</Button>
</HStack>
)}
</HStack>
<SpanBox css={{ width: '100%', height: '1px', bg: '$grayBorder' }} />
</VStack>
</>
)
}

View file

@ -0,0 +1,5 @@
import { LibraryContainer } from '../components/templates/library/LibraryContainer'
export default function Library(): JSX.Element {
return <LibraryContainer />
}