diff --git a/packages/web/components/elements/images/LibraryGridLayoutIcon.tsx b/packages/web/components/elements/images/LibraryGridLayoutIcon.tsx
new file mode 100644
index 000000000..afa40677a
--- /dev/null
+++ b/packages/web/components/elements/images/LibraryGridLayoutIcon.tsx
@@ -0,0 +1,21 @@
+type LibraryGridLayoutIconProps = {
+ color: string
+}
+
+export function LibraryGridLayoutIcon(props: LibraryGridLayoutIconProps): JSX.Element {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/elements/images/LibraryListLayoutIcon.tsx b/packages/web/components/elements/images/LibraryListLayoutIcon.tsx
new file mode 100644
index 000000000..01e413815
--- /dev/null
+++ b/packages/web/components/elements/images/LibraryListLayoutIcon.tsx
@@ -0,0 +1,13 @@
+type LibraryListLayoutIconProps = {
+ color: string
+}
+
+export function LibraryListLayoutIcon(props: LibraryListLayoutIconProps): JSX.Element {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx
new file mode 100644
index 000000000..01b13b9f8
--- /dev/null
+++ b/packages/web/components/patterns/LibraryCards/LibraryGridCard.tsx
@@ -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 (
+ {
+ props.handleAction('showDetail')
+ }}
+ >
+ {props.item.image && (
+ {
+ ;(e.target as HTMLElement).style.display = 'none'
+ }}
+ />
+ )}
+
+
+
+ {/* {
+ // This is here to prevent menu click events from bubbling
+ // up and causing us to "click" on the link item.
+ e.stopPropagation()
+ }}
+ >
+
+ }
+ actionHandler={props.handleAction}
+ />
+ */}
+
+ {/*
+
+ {props.item.author && (
+
+ {authoredByText(props.item.author)}
+
+ )}
+
+ {props.originText}
+
+
+ */}
+
+
+
+ {props.item.description}
+
+
+
+
+ {props.item.labels?.map(({ name, color }, index) => (
+
+ ))}
+
+
+
+ )
+}
+
+type CardTitleProps = {
+ title: string
+}
+
+function CardTitle(props: CardTitleProps): JSX.Element {
+ return (
+
+ {props.title}
+
+ )
+}
diff --git a/packages/web/components/templates/library/LibraryAvatar.tsx b/packages/web/components/templates/library/LibraryAvatar.tsx
new file mode 100644
index 000000000..e653036f3
--- /dev/null
+++ b/packages/web/components/templates/library/LibraryAvatar.tsx
@@ -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 (
+
+
+
+ {props.viewer?.profile.pictureUrl
+ ?
+ : {props.viewer?.name.charAt(0) ?? ''}
+ }
+
+
+ {/* This spacer is to help align with items in the search box */}
+
+
+ )
+}
+
+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',
+})
diff --git a/packages/web/components/templates/library/LibraryContainer.tsx b/packages/web/components/templates/library/LibraryContainer.tsx
new file mode 100644
index 000000000..47d0d883b
--- /dev/null
+++ b/packages/web/components/templates/library/LibraryContainer.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/templates/library/LibraryHeadline.tsx b/packages/web/components/templates/library/LibraryHeadline.tsx
new file mode 100644
index 000000000..28c2e22c9
--- /dev/null
+++ b/packages/web/components/templates/library/LibraryHeadline.tsx
@@ -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 (
+
+ Home
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/templates/library/LibraryList.tsx b/packages/web/components/templates/library/LibraryList.tsx
new file mode 100644
index 000000000..f0b55a805
--- /dev/null
+++ b/packages/web/components/templates/library/LibraryList.tsx
@@ -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('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 (
+
+ {/* // {!isValidating && items.length == 0 ? (
+ // {
+
+ // }}
+ // />
+ // ) : ( */}
+
+ {libraryItems.map((linkedItem) => (
+ div': {
+ bg: '$libraryBackground',
+ },
+ '&:focus': {
+ '> div': {
+ bg: '$grayBgActive',
+ },
+ },
+ '&:hover': {
+ '> div': {
+ bg: '$grayBgActive',
+ },
+ },
+ }}
+ >
+ {viewerData?.me && (
+ {
+
+ }}
+ />
+ )}
+
+ ))}
+
+ {/* Extra padding at bottom to give space for scrolling */}
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/templates/library/LibraryMenu.tsx b/packages/web/components/templates/library/LibraryMenu.tsx
new file mode 100644
index 000000000..baf771a7b
--- /dev/null
+++ b/packages/web/components/templates/library/LibraryMenu.tsx
@@ -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 (
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/packages/web/components/templates/library/LibrarySearchBar.tsx b/packages/web/components/templates/library/LibrarySearchBar.tsx
new file mode 100644
index 000000000..8831e7fdf
--- /dev/null
+++ b/packages/web/components/templates/library/LibrarySearchBar.tsx
@@ -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 (
+ <>
+
+
+
+ {searchTerm && (
+
+
+
+
+
+ )}
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/packages/web/pages/library.tsx b/packages/web/pages/library.tsx
new file mode 100644
index 000000000..839ff3251
--- /dev/null
+++ b/packages/web/pages/library.tsx
@@ -0,0 +1,5 @@
+import { LibraryContainer } from '../components/templates/library/LibraryContainer'
+
+export default function Library(): JSX.Element {
+ return
+}