@ -20,7 +20,7 @@ export const Button = styled('button', {
|
||||
},
|
||||
ctaDarkYellow: {
|
||||
border: 0,
|
||||
fontSize: '16px',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
fontStyle: 'normal',
|
||||
fontFamily: 'Inter',
|
||||
@ -28,7 +28,7 @@ export const Button = styled('button', {
|
||||
cursor: 'pointer',
|
||||
color: '$omnivoreGray',
|
||||
bg: '$omnivoreCtaYellow',
|
||||
p: '10px 12px',
|
||||
p: '10px 13px',
|
||||
},
|
||||
ctaOutlineYellow: {
|
||||
boxSizing: 'border-box',
|
||||
|
||||
@ -15,6 +15,7 @@ export function ProgressBar(props: ProgressBarProps): JSX.Element {
|
||||
width: '100%',
|
||||
borderRadius: '$1',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: props.backgroundColor,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
|
||||
@ -152,6 +152,15 @@ const textVariants = {
|
||||
fontWeight: '500',
|
||||
lineHeight: 'unset',
|
||||
},
|
||||
libraryHeader: {
|
||||
pt: '0px',
|
||||
m: '0px',
|
||||
fontSize: 24,
|
||||
fontFamily: 'inter',
|
||||
lineHeight: 'unset',
|
||||
fontWeight: 'medium',
|
||||
color: '$textSubtle',
|
||||
},
|
||||
error: {
|
||||
color: '$error',
|
||||
fontSize: '$2',
|
||||
|
||||
@ -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 clipPath="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>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
@ -46,7 +46,7 @@ export function GridLinkedItemCard(props: LinkedItemCardProps): JSX.Element {
|
||||
<ProgressBar
|
||||
fillPercentage={props.item.readingProgressPercent}
|
||||
fillColor={theme.colors.highlight.toString()}
|
||||
backgroundColor={theme.colors.grayTextContrast.toString()}
|
||||
backgroundColor="transparent"
|
||||
borderRadius={
|
||||
props.item.readingProgressPercent === 100 ? '0' : '0px 8px 8px 0px'
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
174
packages/web/components/templates/Menu.tsx
Normal file
174
packages/web/components/templates/Menu.tsx
Normal file
@ -0,0 +1,174 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { useGetLabelsQuery } from '../../lib/networking/queries/useGetLabelsQuery'
|
||||
import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery'
|
||||
|
||||
import { ProSidebar, Menu, MenuItem, SubMenu } from 'react-pro-sidebar'
|
||||
import { Tag } from 'phosphor-react'
|
||||
|
||||
// styles
|
||||
const proSideBarStyles = {
|
||||
display: 'inline-block',
|
||||
}
|
||||
|
||||
// Types
|
||||
type MenuItems = {
|
||||
label: string
|
||||
query: string
|
||||
active?: boolean
|
||||
icon: string | JSX.Element | null
|
||||
href: string
|
||||
}
|
||||
|
||||
type DynamicMenuItems = {
|
||||
labels?: MenuItems[]
|
||||
subscriptions?: MenuItems[]
|
||||
}
|
||||
|
||||
// Functions
|
||||
const calculateTodayMenuItem = () => {
|
||||
const timeZoneHourDiff = -new Date().getTimezoneOffset() / 60
|
||||
const hrefStr = `?q=in%3Ainbox+saved%3A${
|
||||
new Date(new Date().getTime() - 24 * 3600000).toISOString().split('T')[0]
|
||||
}Z%${timeZoneHourDiff}B2..*`
|
||||
return hrefStr
|
||||
}
|
||||
|
||||
const createDynamicMenuItems = (
|
||||
labels: Array<any>,
|
||||
subscriptions: Array<any>
|
||||
) => {
|
||||
const labelsList: Array<MenuItems> = []
|
||||
const subscriptionsList: Array<MenuItems> = []
|
||||
// Create labels list
|
||||
if (labels.length) {
|
||||
labels.map((l) =>
|
||||
labelsList.push({
|
||||
label: l.name,
|
||||
query: `label:"${l.name}"`,
|
||||
icon: <Tag size={18} weight="light" />,
|
||||
href: `?q=label:"${l.name}"`,
|
||||
})
|
||||
)
|
||||
}
|
||||
// create subscriptions list
|
||||
if (subscriptions.length) {
|
||||
subscriptions.map((s) =>
|
||||
subscriptionsList.push({
|
||||
label: s.name,
|
||||
query: `subscription:"${s.subscription}"`,
|
||||
icon: 'subscription',
|
||||
href: `?q=subscription:"${s.subscription}"`,
|
||||
})
|
||||
)
|
||||
}
|
||||
return { labels: [...labelsList], subscriptions: [...subscriptionsList] }
|
||||
}
|
||||
|
||||
// Component
|
||||
export const Menubar = () => {
|
||||
const { labels } = useGetLabelsQuery()
|
||||
const { subscriptions } = useGetSubscriptionsQuery()
|
||||
|
||||
const [menuList, setMenuList] = useState<Array<MenuItems>>([])
|
||||
const [dynamicMenuItems, setDynamicMenuItems] = useState<DynamicMenuItems>({})
|
||||
|
||||
useEffect(() => {
|
||||
if (labels || subscriptions) {
|
||||
setDynamicMenuItems(createDynamicMenuItems(labels, subscriptions))
|
||||
}
|
||||
setMenuList([
|
||||
{
|
||||
label: 'Home',
|
||||
query: 'in:inbox',
|
||||
icon: null,
|
||||
href: '/home',
|
||||
active: true,
|
||||
},
|
||||
{
|
||||
label: 'Today',
|
||||
query: 'in:inbox-label:Newsletter',
|
||||
icon: null,
|
||||
href: calculateTodayMenuItem(),
|
||||
},
|
||||
{
|
||||
label: 'Read Later',
|
||||
query: 'in:inbox-label:Newsletter',
|
||||
icon: null,
|
||||
href: `?q=in:inbox+-label:Newsletter`,
|
||||
},
|
||||
{
|
||||
label: 'HighLights',
|
||||
query: 'type:highlights',
|
||||
icon: null,
|
||||
href: `?q=type:highlights`,
|
||||
},
|
||||
{
|
||||
label: 'Newsletters',
|
||||
query: 'in:inbox label:Newsletter',
|
||||
icon: null,
|
||||
href: `?q=in:inbox+label:Newsletter`,
|
||||
},
|
||||
])
|
||||
},[labels, subscriptions])
|
||||
|
||||
return (
|
||||
<ProSidebar style={proSideBarStyles} breakPoint={'sm'}>
|
||||
<Menu>
|
||||
{menuList.length > 0 &&
|
||||
menuList.map((item) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
icon={item.icon}
|
||||
active={item.active ?? false}
|
||||
>
|
||||
<Link passHref href={item.href}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
{dynamicMenuItems.labels &&
|
||||
<SubMenu
|
||||
key="labels-list"
|
||||
title="Labels"
|
||||
>
|
||||
{dynamicMenuItems.labels.map((item: MenuItems) => {
|
||||
return (<MenuItem
|
||||
key={item.label}
|
||||
icon={item.icon}
|
||||
active={item.active ?? false}
|
||||
>
|
||||
<Link passHref href={item.href}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</MenuItem>)
|
||||
})}
|
||||
</SubMenu>
|
||||
}
|
||||
{dynamicMenuItems.subscriptions &&
|
||||
<SubMenu
|
||||
key="subscriptions-list"
|
||||
title="Subscriptions"
|
||||
>
|
||||
{dynamicMenuItems.subscriptions.map((item: MenuItems) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
icon={item.icon}
|
||||
active={item.active ?? false}
|
||||
>
|
||||
<Link passHref href={item.href}>
|
||||
{item.label}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
)
|
||||
})}
|
||||
</SubMenu>
|
||||
}
|
||||
</Menu>
|
||||
</ProSidebar>
|
||||
)
|
||||
}
|
||||
62
packages/web/components/templates/library/LibraryAvatar.tsx
Normal file
62
packages/web/components/templates/library/LibraryAvatar.tsx
Normal 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',
|
||||
})
|
||||
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
114
packages/web/components/templates/library/LibraryList.tsx
Normal file
114
packages/web/components/templates/library/LibraryList.tsx
Normal 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) => {
|
||||
console.log('card clicked')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{/* Extra padding at bottom to give space for scrolling */}
|
||||
<Box css={{ width: '100%', height: '200px' }} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
14
packages/web/components/templates/library/LibraryMenu.tsx
Normal file
14
packages/web/components/templates/library/LibraryMenu.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -112,6 +112,7 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
||||
grayBg: '#FFFFFF',
|
||||
grayBgActive: '#e6e6e6',
|
||||
grayBorder: '#F0F0F0',
|
||||
lightBorder: '#F0F0F0',
|
||||
grayTextContrast: '#3A3939',
|
||||
graySolid: '#9C9B9A',
|
||||
textDefault: 'rgba(255, 255, 255, 0.8)',
|
||||
@ -155,6 +156,12 @@ export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
||||
labelButtonsBg: '#F5F5F4',
|
||||
tooltipIcons: '#FDFAEC',
|
||||
|
||||
textSubtle: '#605F5D',
|
||||
libraryActive: '#F8F8F8',
|
||||
libraryBackground: '#FFFFFF',
|
||||
border: '#F0F0F0',
|
||||
|
||||
|
||||
//utility
|
||||
textNonEssential: 'rgba(10, 8, 6, 0.4)',
|
||||
overlay: 'rgba(63, 62, 60, 0.2)',
|
||||
@ -207,6 +214,11 @@ const darkThemeSpec = {
|
||||
avatarBg: '#000000',
|
||||
avatarFont: 'rgba(255, 255, 255, 0.8)',
|
||||
|
||||
textSubtle: '#AAAAAA',
|
||||
libraryActive: '#3B3938',
|
||||
libraryBackground: '#252525',
|
||||
border: '#323232',
|
||||
|
||||
//utility
|
||||
utilityTextSubtle: 'rgba(255, 255, 255, 0.65)',
|
||||
textNonEssential: 'rgba(10, 8, 6, 0.4)',
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-pro-sidebar": "^0.7.1",
|
||||
"react-super-responsive-table": "^5.2.1",
|
||||
"react-topbar-progress-indicator": "^4.1.1",
|
||||
"react-twitter-widgets": "^1.10.0",
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import '../styles/globals.css'
|
||||
import '../styles/articleInnerStyling.css'
|
||||
import 'react-pro-sidebar/dist/css/styles.css'
|
||||
import '../styles/menu.css'
|
||||
|
||||
import type { AppProps } from 'next/app'
|
||||
import { IdProvider } from '@radix-ui/react-id'
|
||||
import { NextRouter, useRouter } from 'next/router'
|
||||
|
||||
5
packages/web/pages/library.tsx
Normal file
5
packages/web/pages/library.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { LibraryContainer } from '../components/templates/library/LibraryContainer'
|
||||
|
||||
export default function Library(): JSX.Element {
|
||||
return <LibraryContainer />
|
||||
}
|
||||
16
packages/web/stories/Menu.stories.tsx
Normal file
16
packages/web/stories/Menu.stories.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||
//import { updateThemeLocally } from '../lib/themeUpdater'
|
||||
//import { ThemeId } from '../components/tokens/stitches.config'
|
||||
import { Menubar } from '../components/templates/Menu'
|
||||
|
||||
export default {
|
||||
title: 'Components/Menu',
|
||||
component: Menubar,
|
||||
} as ComponentMeta<typeof Menubar>
|
||||
|
||||
const Template: ComponentStory<typeof Menubar> = () => (
|
||||
<Menubar/>
|
||||
)
|
||||
|
||||
export const MenuStory = Template.bind({})
|
||||
|
||||
30
packages/web/styles/menu.css
Normal file
30
packages/web/styles/menu.css
Normal file
@ -0,0 +1,30 @@
|
||||
/* Menu Override styles */
|
||||
|
||||
.pro-sidebar > .pro-sidebar-inner, .pro-sidebar .pro-menu a {
|
||||
background-color: var(--colors-libraryBackground);
|
||||
color: var(--colors-utilityTextDefault);
|
||||
}
|
||||
|
||||
.pro-sidebar .pro-menu > ul > .pro-sub-menu > .pro-inner-list-item {
|
||||
color: var(--colors-utilityTextDefault);
|
||||
background-color: var(--colors-libraryBackground);
|
||||
}
|
||||
|
||||
.pro-sidebar .pro-menu a:hover {
|
||||
color: var(--colors-utilityTextDefault);
|
||||
}
|
||||
|
||||
.pro-sidebar .pro-menu .pro-menu-item > .pro-inner-item {
|
||||
padding: 8px 20px 3px 20px;
|
||||
}
|
||||
|
||||
/* .pro-sidebar .pro-menu .active > .pro-inner-item {
|
||||
background-color: #F8F8F8;
|
||||
border-radius: 8px;
|
||||
} */
|
||||
|
||||
.pro-sidebar .pro-menu .pro-menu-item > .pro-inner-item > .pro-icon-wrapper {
|
||||
width: 20px;
|
||||
min-width: 0;
|
||||
height: 25px;
|
||||
}
|
||||
29
yarn.lock
29
yarn.lock
@ -4531,7 +4531,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
|
||||
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
|
||||
|
||||
"@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0":
|
||||
"@popperjs/core@^2.4.0", "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0":
|
||||
version "2.11.5"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64"
|
||||
integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==
|
||||
@ -10641,6 +10641,11 @@ class-utils@^0.3.5:
|
||||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2.6:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
|
||||
clean-css@^4.2.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178"
|
||||
@ -21134,6 +21139,16 @@ react-popper@^2.2.4:
|
||||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-pro-sidebar@^0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/react-pro-sidebar/-/react-pro-sidebar-0.7.1.tgz#0b5edca0809ff6a23bda188c5db370f880690ee7"
|
||||
integrity sha512-Iy1X8ce4t5Vqz4CsyzjwokGUE3/IObgmYzS0ins7/2eWKle0SMUPaWdgMKFIVjtVrMr5vmjPbRicq8FxnVaf8A==
|
||||
dependencies:
|
||||
"@popperjs/core" "^2.4.0"
|
||||
classnames "^2.2.6"
|
||||
react-slidedown "^2.4.5"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
|
||||
react-refresh@^0.11.0:
|
||||
version "0.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
@ -21183,6 +21198,13 @@ react-sizeme@^3.0.1:
|
||||
shallowequal "^1.1.0"
|
||||
throttle-debounce "^3.0.1"
|
||||
|
||||
react-slidedown@^2.4.5:
|
||||
version "2.4.7"
|
||||
resolved "https://registry.yarnpkg.com/react-slidedown/-/react-slidedown-2.4.7.tgz#c09e72bba8aac25018fd644ece041da771854589"
|
||||
integrity sha512-HGDfrqo70r1WVE0DwrySPdCT27/2wcZaJYh5kOnmuPSCtjDDJrNkDdn4Ep/cma2VVfwupeAGhbc2pbrGThU6VQ==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-style-singleton@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
|
||||
@ -21763,6 +21785,11 @@ requires-port@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-cwd@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||
|
||||
Reference in New Issue
Block a user