Start adding allotment cleaning up navigation

This commit is contained in:
Jackson Harper
2024-06-06 17:10:55 +08:00
parent 2123b248c9
commit 4295e8228d
17 changed files with 1971 additions and 127 deletions

View File

@ -64,7 +64,7 @@ export function LibraryGridCard(props: LinkedItemCardProps): JSX.Element {
css={{
pl: '0px',
padding: '0px',
width: '293px',
width: '100%',
height: '100%',
minHeight: '270px',
background: 'white',

View File

@ -68,19 +68,19 @@ export function LibraryListCard(props: LinkedItemCardProps): JSX.Element {
borderStyle: 'none',
borderBottom: 'none',
borderRadius: '6px',
width: '100vw',
'@media (min-width: 768px)': {
width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`,
},
'@media (min-width: 930px)': {
width: '580px',
},
'@media (min-width: 1280px)': {
width: '890px',
},
'@media (min-width: 1600px)': {
width: '1200px',
},
width: '100%',
// '@media (min-width: 768px)': {
// width: `calc(100vw - ${LIBRARY_LEFT_MENU_WIDTH})`,
// },
// '@media (min-width: 930px)': {
// width: '580px',
// },
// '@media (min-width: 1280px)': {
// width: '890px',
// },
// '@media (min-width: 1600px)': {
// width: '1200px',
// },
'@media (max-width: 930px)': {
borderRadius: '0px',
},

View File

@ -1,17 +1,17 @@
import { useCallback, useRef, useState } from 'react'
import * as Progress from '@radix-ui/react-progress'
import { File, Info } from 'phosphor-react'
import { locale, timeZone } from '../../../lib/dateFormatting'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
import { Button } from '../../elements/Button'
import { FormInput } from '../../elements/FormElements'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { locale, timeZone } from '../../lib/dateFormatting'
import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers'
import { Button } from '../elements/Button'
import { FormInput } from '../elements/FormElements'
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
import {
ModalContent,
ModalOverlay,
ModalRoot,
} from '../../elements/ModalPrimitives'
import { CloseButton } from '../../elements/CloseButton'
} from '../elements/ModalPrimitives'
import { CloseButton } from '../elements/CloseButton'
import { styled } from '@stitches/react'
import Dropzone, {
Accept,
@ -20,17 +20,17 @@ import Dropzone, {
FileRejection,
} from 'react-dropzone'
import { v4 as uuidv4 } from 'uuid'
import { validateCsvFile } from '../../../utils/csvValidator'
import { validateCsvFile } from '../../utils/csvValidator'
import {
uploadImportFileRequestMutation,
UploadImportFileType,
} from '../../../lib/networking/mutations/uploadImportFileMutation'
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
} from '../../lib/networking/mutations/uploadImportFileMutation'
import { uploadFileRequestMutation } from '../../lib/networking/mutations/uploadFileMutation'
import axios from 'axios'
import { theme } from '../../tokens/stitches.config'
import { formatMessage } from '../../../locales/en/messages'
import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation'
import { SubscriptionType } from '../../../lib/networking/queries/useGetSubscriptionsQuery'
import { theme } from '../tokens/stitches.config'
import { formatMessage } from '../../locales/en/messages'
import { subscribeMutation } from '../../lib/networking/mutations/subscribeMutation'
import { SubscriptionType } from '../../lib/networking/queries/useGetSubscriptionsQuery'
type TabName = 'link' | 'feed' | 'opml' | 'pdf' | 'import'

View File

@ -1,5 +1,5 @@
import { PageMetaData, PageMetaDataProps } from '../patterns/PageMetaData'
import { Box, VStack } from '../elements/LayoutPrimitives'
import { Box, HStack, VStack } from '../elements/LayoutPrimitives'
import { ReactNode, useEffect, useState, useCallback } from 'react'
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
import { navigationCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
@ -19,6 +19,10 @@ import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer'
import { Button } from '../elements/Button'
import { List } from 'phosphor-react'
import { usePersistedState } from '../../lib/hooks/usePersistedState'
import { Allotment } from 'allotment'
import 'allotment/dist/style.css'
import { LibrarySideBar } from './library/LibrarySideBar'
import NoSsr from './NoSsr'
export type NavigationSection =
| 'justread'
@ -29,6 +33,7 @@ export type NavigationSection =
type NavigationLayoutProps = {
children: ReactNode
rightPane?: ReactNode
section: NavigationSection
pageMetaDataProps?: PageMetaDataProps
}
@ -41,7 +46,8 @@ export function NavigationLayout(props: NavigationLayoutProps): JSX.Element {
const [showLogoutConfirmation, setShowLogoutConfirmation] = useState(false)
const [showKeyboardCommandsModal, setShowKeyboardCommandsModal] =
useState(false)
const [showLeftMenu, setShowLeftMenu] = usePersistedState<boolean>({
const [showNavMenu, setShowNavMenu] = usePersistedState<boolean>({
key: 'nav-show-menu',
isSessionStorage: false,
initialValue: true,
@ -109,51 +115,37 @@ export function NavigationLayout(props: NavigationLayoutProps): JSX.Element {
}, [showLogout])
return (
<>
<HStack css={{ width: '100vw', height: '100vh' }}>
{props.pageMetaDataProps ? (
<PageMetaData {...props.pageMetaDataProps} />
) : null}
<Box
css={{
height: '100%',
width: '100vw',
bg: '$thBackground2',
<Header
toggleMenu={() => {
setShowNavMenu(!showNavMenu)
}}
>
<Header
toggleMenu={() => {
setShowLeftMenu(!showLeftMenu)
}}
/>
<NavigationMenu
section={props.section}
setShowAddLinkModal={() => {}}
searchTerm=""
applySearchQuery={() => {}}
showFilterMenu={showNavMenu}
setShowFilterMenu={setShowNavMenu}
/>
{props.children}
{showLogoutConfirmation ? (
<ConfirmationModal
message={'Are you sure you want to log out?'}
onAccept={logout}
onOpenChange={() => setShowLogoutConfirmation(false)}
/>
{showLeftMenu && (
<NavigationMenu
section={props.section}
// eslint-disable-next-line @typescript-eslint/no-empty-function
setShowAddLinkModal={() => {}}
searchTerm={''}
// eslint-disable-next-line @typescript-eslint/no-empty-function
applySearchQuery={(searchQuery: string) => {}}
showFilterMenu={showLeftMenu}
setShowFilterMenu={(show) => {
setShowLeftMenu(show)
}}
/>
)}
{props.children}
{showLogoutConfirmation ? (
<ConfirmationModal
message={'Are you sure you want to log out?'}
onAccept={logout}
onOpenChange={() => setShowLogoutConfirmation(false)}
/>
) : null}
{showKeyboardCommandsModal ? (
<KeyboardShortcutListModal
onOpenChange={() => setShowKeyboardCommandsModal(false)}
/>
) : null}
</Box>
</>
) : null}
{showKeyboardCommandsModal ? (
<KeyboardShortcutListModal
onOpenChange={() => setShowKeyboardCommandsModal(false)}
/>
) : null}
</HStack>
)
}

View File

@ -2,7 +2,7 @@ import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
import { LibraryFilterMenu } from '../navMenu/LibraryMenu'
import { DiscoverHeader } from './DiscoverHeader/DiscoverHeader'
import { useRouter } from 'next/router'
import React, { useCallback, useEffect, useState } from "react"
import React, { useCallback, useEffect, useState } from 'react'
import { DiscoverItemFeed } from './DiscoverFeed/DiscoverFeed'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import toast from 'react-hot-toast'
@ -10,13 +10,13 @@ import { Button } from '../../elements/Button'
import { showErrorToast } from '../../../lib/toastHelpers'
import {
saveDiscoverArticleMutation,
SaveDiscoverArticleOutput
} from "../../../lib/networking/mutations/saveDiscoverArticle"
import { saveUrlMutation } from "../../../lib/networking/mutations/saveUrlMutation"
import { useFetchMore } from "../../../lib/hooks/useFetchMoreScroll"
import { AddLinkModal } from "../homeFeed/AddLinkModal"
import { useGetDiscoverFeedItems } from "../../../lib/networking/queries/useGetDiscoverFeedItems"
import { useGetDiscoverFeeds } from "../../../lib/networking/queries/useGetDiscoverFeeds"
SaveDiscoverArticleOutput,
} from '../../../lib/networking/mutations/saveDiscoverArticle'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
import { AddLinkModal } from '../AddLinkModal'
import { useGetDiscoverFeedItems } from '../../../lib/networking/queries/useGetDiscoverFeedItems'
import { useGetDiscoverFeeds } from '../../../lib/networking/queries/useGetDiscoverFeeds'
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
@ -27,8 +27,8 @@ export function DiscoverContainer(): JSX.Element {
const viewer = useGetViewerQuery()
const [showFilterMenu, setShowFilterMenu] = useState(false)
const [layoutType, setLayoutType] = useState<LayoutType>('GRID_LAYOUT')
const [showAddLinkModal, setShowAddLinkModal] = useState(false);
const {feeds, revalidate, isValidating} = useGetDiscoverFeeds()
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
const { feeds, revalidate, isValidating } = useGetDiscoverFeeds()
const topics = [
{
title: 'Popular',
@ -73,8 +73,16 @@ export function DiscoverContainer(): JSX.Element {
},
]
const [selectedFeed, setSelectedFeed] = useState("All Feeds");
const { discoverItems, setTopic, activeTopic, isLoading, hasMore, setPage, page } = useGetDiscoverFeedItems(topics[1], selectedFeed)
const [selectedFeed, setSelectedFeed] = useState('All Feeds')
const {
discoverItems,
setTopic,
activeTopic,
isLoading,
hasMore,
setPage,
page,
} = useGetDiscoverFeedItems(topics[1], selectedFeed)
const handleFetchMore = useCallback(() => {
if (isLoading || !hasMore) {
return
@ -88,7 +96,11 @@ export function DiscoverContainer(): JSX.Element {
timezone: string,
locale: string
): Promise<SaveDiscoverArticleOutput | undefined> => {
const result = await saveDiscoverArticleMutation({discoverArticleId, timezone, locale})
const result = await saveDiscoverArticleMutation({
discoverArticleId,
timezone,
locale,
})
if (result?.saveDiscoverArticle) {
toast(
() => (
@ -160,8 +172,8 @@ export function DiscoverContainer(): JSX.Element {
}, [])
const setTopicAndReturnToTop = (topic: TopicTabData) => {
window.scroll(0,0);
setTopic(topic);
window.scroll(0, 0)
setTopic(topic)
}
return (
@ -204,12 +216,12 @@ export function DiscoverContainer(): JSX.Element {
items={discoverItems ?? []}
viewer={viewer.viewerData?.me}
/>
{ showAddLinkModal &&
{showAddLinkModal && (
<AddLinkModal
handleLinkSubmission={handleLinkSave}
onOpenChange={() => setShowAddLinkModal(false)}
/>
}
)}
</HStack>
</VStack>
)

View File

@ -32,13 +32,13 @@ import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
import { AddLinkModal } from './AddLinkModal'
import { AddLinkModal } from '../AddLinkModal'
import { EditLibraryItemModal } from './EditItemModals'
import { EmptyLibrary } from './EmptyLibrary'
import { HighlightItemsLayout } from './HighlightsLayout'
import { LibraryFilterMenu } from '../navMenu/LibraryMenu'
import { LibraryLegacyMenu } from '../navMenu/LibraryLegacyMenu'
import { LibraryHeader, MultiSelectMode } from './LibraryHeader'
import { LegacyLibraryHeader, MultiSelectMode } from './LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
@ -975,7 +975,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
}}
>
{props.mode != 'highlights' && (
<LibraryHeader
<LegacyLibraryHeader
layout={layout}
viewer={viewerData?.me}
updateLayout={updateLayout}

View File

@ -63,7 +63,7 @@ export const headerControlWidths = (
}
}
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
export function LegacyLibraryHeader(props: LibraryHeaderProps): JSX.Element {
const [small, setSmall] = useState(false)
useEffect(() => {

View File

@ -9,12 +9,29 @@ import { TrashIcon } from '../../elements/icons/TrashIcon'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { AddBulkLabelsModal } from '../article/AddBulkLabelsModal'
import { X } from 'phosphor-react'
import { LibraryHeaderProps } from './LibraryHeader'
import { MultiSelectMode } from './LibraryHeader'
import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon'
import { Label } from '../../../lib/networking/fragments/labelFragment'
import { MarkAsReadIcon } from '../../elements/icons/MarkAsReadIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => {
export type MultiSelectProps = {
viewer: UserBasicData | undefined
searchTerm: string | undefined
applySearchQuery: (searchQuery: string) => void
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
numItemsSelected: number
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
}
export const MultiSelectControls = (props: MultiSelectProps): JSX.Element => {
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
const [showLabelsModal, setShowLabelsModal] = useState(false)
// Don't change on immediate hover, the button has to be blurred at least once
@ -146,7 +163,7 @@ export const MultiSelectControls = (props: LibraryHeaderProps): JSX.Element => {
)
}
export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => {
export const CheckBoxButton = (props: MultiSelectProps): JSX.Element => {
return (
<Button
title="Select multiple"
@ -171,7 +188,7 @@ export const CheckBoxButton = (props: LibraryHeaderProps): JSX.Element => {
)
}
export const ArchiveButton = (props: LibraryHeaderProps): JSX.Element => {
export const ArchiveButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
@ -206,7 +223,7 @@ export const ArchiveButton = (props: LibraryHeaderProps): JSX.Element => {
)
}
export const MarkAsReadButton = (props: LibraryHeaderProps): JSX.Element => {
export const MarkAsReadButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
@ -321,7 +338,7 @@ export const RemoveItemsButton = (
)
}
export const CancelButton = (props: LibraryHeaderProps): JSX.Element => {
export const CancelButton = (props: MultiSelectProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,418 @@
import { useEffect, useRef, useState } from 'react'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { theme } from '../../tokens/stitches.config'
import { FormInput } from '../../elements/FormElements'
import { searchBarCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import { Button, IconButton } from '../../elements/Button'
import { FunnelSimple, X } from 'phosphor-react'
import { LayoutType, LibraryMode } from '../homeFeed/HomeFeedContainer'
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from '../homeFeed/HeaderSpacer'
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon'
import { HeaderToggleTLDRIcon } from '../../elements/icons/HeaderToggleTLDRIcon'
import { UserBasicData } from '../../../lib/networking/queries/useGetViewerQuery'
import { userHasFeature } from '../../../lib/featureFlag'
import {
MultiSelectControls,
CheckBoxButton,
} from '../homeFeed/MultiSelectControls'
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
export type LibraryHeaderProps = {
viewer: UserBasicData | undefined
layout: LayoutType
updateLayout: (layout: LayoutType) => void
searchTerm: string | undefined
applySearchQuery: (searchQuery: string) => void
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
numItemsSelected: number
multiSelectMode: MultiSelectMode
setMultiSelectMode: (mode: MultiSelectMode) => void
performMultiSelectAction: (action: BulkAction, labelIds?: string[]) => void
}
export const headerControlWidths = (
layout: LayoutType,
multiSelectMode: MultiSelectMode
) => {
return {
width: '95%',
'@mdDown': {
width: '100%',
},
'@media (min-width: 930px)': {
width: '620px',
},
'@media (min-width: 1280px)': {
width: '940px',
},
'@media (min-width: 1600px)': {
width: '1232px',
},
}
}
export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
const [small, setSmall] = useState(false)
useEffect(() => {
const handleScroll = () => {
setSmall(window.scrollY > 40)
}
if (typeof window !== 'undefined') {
window.addEventListener('scroll', handleScroll)
}
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
return (
<>
<VStack
alignment="start"
distribution="start"
css={{
width: '100%',
px: '70px',
bg: '$thLibraryBackground',
// position: 'sticky',
left: LIBRARY_LEFT_MENU_WIDTH,
height: small ? '60px' : DEFAULT_HEADER_HEIGHT,
transition: 'height 0.5s',
'@lgDown': { px: '20px' },
'@mdDown': {
px: '10px',
left: '0px',
right: '0',
},
}}
>
<LargeHeaderLayout {...props} />
</VStack>
{/* This spacer is put in to push library content down
below the fixed header height. */}
<HeaderSpacer />
</>
)
}
function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
return (
<HStack
alignment="center"
distribution="start"
css={{
gap: '10px',
width: '100%',
height: '100%',
}}
>
{props.multiSelectMode !== 'off' ? (
<>
<MultiSelectControls {...props} />
</>
) : (
<HeaderControls {...props} />
)}
</HStack>
)
}
const HeaderControls = (props: LibraryHeaderProps): JSX.Element => {
const [searchBoxFocused, setSearchBoxFocused] = useState(false)
return (
<>
{!searchBoxFocused && (
<SpanBox
css={{
display: 'none',
'@mdDown': { display: 'flex' },
}}
>
<MenuHeaderButton {...props} />
</SpanBox>
)}
<SearchBox
{...props}
searchBoxFocused={searchBoxFocused}
setSearchBoxFocused={setSearchBoxFocused}
/>
<SpanBox css={{ display: 'flex', ml: 'auto', gap: '10px' }}>
{/* {userHasFeature(props.viewer, 'ai-summaries') && (
<Button
title="TLDR Summaries"
style="plainIcon"
css={{
display: 'flex',
marginLeft: 'auto',
'&:hover': { opacity: '1.0' },
}}
onClick={(e) => {
if (props.mode == 'reads') {
props.setMode('tldr')
} else {
props.setMode('reads')
}
e.preventDefault()
}}
>
<HeaderToggleTLDRIcon />
</Button>
)} */}
<Button
title={
props.layout == 'GRID_LAYOUT'
? 'Switch to list layout'
: 'Switch to grid layout'
}
style="plainIcon"
css={{
display: 'flex',
marginLeft: 'auto',
'&:hover': { opacity: '1.0' },
}}
onClick={(e) => {
props.updateLayout(
props.layout == 'GRID_LAYOUT' ? 'LIST_LAYOUT' : 'GRID_LAYOUT'
)
e.preventDefault()
}}
>
{props.layout == 'LIST_LAYOUT' ? (
<HeaderToggleGridIcon />
) : (
<HeaderToggleListIcon />
)}
</Button>
</SpanBox>
</>
)
}
type MenuHeaderButtonProps = {
showFilterMenu: boolean
setShowFilterMenu: (show: boolean) => void
}
export function MenuHeaderButton(props: MenuHeaderButtonProps): JSX.Element {
return (
<HStack
css={{
width: '67px',
height: '40px',
bg: props.showFilterMenu ? '$thTextContrast2' : '$thBackground2',
borderRadius: '5px',
px: '5px',
cursor: 'pointer',
}}
alignment="center"
distribution="around"
onClick={() => {
props.setShowFilterMenu(!props.showFilterMenu)
}}
>
<OmnivoreSmallLogo
size={20}
strokeColor={
props.showFilterMenu
? theme.colors.thBackground.toString()
: theme.colors.thTextContrast2.toString()
}
/>
<FunnelSimple
size={20}
color={
props.showFilterMenu
? theme.colors.thBackground.toString()
: theme.colors.thTextContrast2.toString()
}
/>
</HStack>
)
}
type SearchBoxProps = LibraryHeaderProps & {
searchBoxFocused: boolean
setSearchBoxFocused: (show: boolean) => void
}
export function SearchBox(props: SearchBoxProps): JSX.Element {
const inputRef = useRef<HTMLInputElement | null>(null)
const [searchTerm, setSearchTerm] = useState(props.searchTerm ?? '')
useEffect(() => {
setSearchTerm(props.searchTerm ?? '')
}, [props.searchTerm])
useKeyboardShortcuts(
searchBarCommands((action) => {
if (action === 'focusSearchBar' && inputRef.current) {
inputRef.current.select()
}
if (action == 'clearSearch' && inputRef.current) {
setSearchTerm('')
props.applySearchQuery('')
}
})
)
return (
<Box
css={{
height: '38px',
width: '100%',
maxWidth: '521px',
bg: '$thLibrarySearchbox',
borderRadius: '6px',
boxShadow: props.searchBoxFocused
? 'none'
: '0 1px 3px 0 rgba(0, 0, 0, 0.1),0 1px 2px 0 rgba(0, 0, 0, 0.06);',
}}
>
<HStack
alignment="center"
distribution="start"
css={{ width: '100%', height: '100%' }}
>
<HStack
alignment="center"
distribution="center"
css={{
width: '53px',
height: '100%',
display: 'flex',
bg: props.multiSelectMode !== 'off' ? '$ctaBlue' : 'transparent',
borderTopLeftRadius: '6px',
borderBottomLeftRadius: '6px',
'--checkbox-color': 'var(--colors-thLibraryMultiselectCheckbox)',
'&:hover': {
bg: '$thLibraryMultiselectHover',
'--checkbox-color':
'var(--colors-thLibraryMultiselectCheckboxHover)',
},
}}
>
<CheckBoxButton {...props} />
</HStack>
<HStack
alignment="center"
distribution="start"
css={{
border: props.searchBoxFocused
? '2px solid $searchActiveOutline'
: '2px solid transparent',
borderTopRightRadius: '6px',
borderBottomRightRadius: '6px',
width: '100%',
height: '100%',
}}
>
<form
onSubmit={async (event) => {
event.preventDefault()
props.applySearchQuery(searchTerm || '')
inputRef.current?.blur()
}}
style={{ width: '100%' }}
>
<FormInput
ref={inputRef}
type="text"
value={searchTerm}
autoFocus={false}
placeholder="Search keywords or labels"
onFocus={(event) => {
event.target.select()
props.setSearchBoxFocused(true)
}}
onBlur={() => {
props.setSearchBoxFocused(false)
}}
onChange={(event) => {
setSearchTerm(event.target.value)
}}
onKeyDown={(event) => {
const key = event.key.toLowerCase()
if (key == 'escape') {
event.currentTarget.blur()
}
}}
/>
</form>
<HStack
alignment="center"
css={{
py: '15px',
mr: '10px',
marginLeft: 'auto',
}}
>
<CancelSearchButton
onClick={() => {
setSearchTerm('in:inbox')
props.applySearchQuery('')
inputRef.current?.blur()
}}
/>
</HStack>
</HStack>
</HStack>
</Box>
)
}
type CancelSearchButtonProps = {
onClick: () => void
}
const CancelSearchButton = (props: CancelSearchButtonProps): JSX.Element => {
const [color, setColor] = useState<string>(
theme.colors.thTextContrast2.toString()
)
return (
<Button
title="Cancel"
style="plainIcon"
css={{
p: '5px',
display: 'flex',
'&:hover': {
bg: '$ctaBlue',
borderRadius: '100px',
opacity: 1.0,
},
}}
onMouseEnter={(event) => {
setColor('white')
event.preventDefault()
}}
onMouseLeave={(event) => {
setColor(theme.colors.thTextContrast2.toString())
event.preventDefault()
}}
onClick={(event) => {
event.preventDefault()
props.onClick()
}}
>
<X width={19} height={19} color={color} />
</Button>
)
}

View File

@ -0,0 +1,23 @@
import { Allotment } from 'allotment'
import 'allotment/dist/style.css'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import { useRouter } from 'next/router'
import { useKBar } from 'kbar'
import { useState } from 'react'
import { LibraryContainer } from './LibraryContainer'
import { LibrarySideBar } from './LibrarySideBar'
export function LibraryItemsContainer(): JSX.Element {
const router = useRouter()
return (
<Allotment>
<Allotment.Pane minSize={200}>
<LibraryContainer />
</Allotment.Pane>
<Allotment.Pane snap maxSize={230}>
<LibrarySideBar />
</Allotment.Pane>
</Allotment>
)
}

View File

@ -0,0 +1,62 @@
import { Action, createAction, useKBar, useRegisterActions } from 'kbar'
import debounce from 'lodash/debounce'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Toaster } from 'react-hot-toast'
import TopBarProgress from 'react-topbar-progress-indicator'
import { useFetchMore } from '../../../lib/hooks/useFetchMoreScroll'
import { usePersistedState } from '../../../lib/hooks/usePersistedState'
import { libraryListCommands } from '../../../lib/keyboardShortcuts/navigationShortcuts'
import { useKeyboardShortcuts } from '../../../lib/keyboardShortcuts/useKeyboardShortcuts'
import {
PageType,
State,
} from '../../../lib/networking/fragments/articleFragment'
import {
SearchItem,
TypeaheadSearchItemsData,
typeaheadSearchQuery,
} from '../../../lib/networking/queries/typeaheadSearch'
import type {
LibraryItem,
LibraryItemsQueryInput,
} from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
import {
useGetViewerQuery,
UserBasicData,
} from '../../../lib/networking/queries/useGetViewerQuery'
import { Button } from '../../elements/Button'
import { StyledText } from '../../elements/StyledText'
import { ConfirmationModal } from '../../patterns/ConfirmationModal'
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
import { LinkedItemCard } from '../../patterns/LibraryCards/LinkedItemCard'
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
import { AddLinkModal } from '../AddLinkModal'
import { EditLibraryItemModal } from '../homeFeed/EditItemModals'
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
import { LegacyLibraryHeader, MultiSelectMode } from '../homeFeed/LibraryHeader'
import { UploadModal } from '../UploadModal'
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
import { bulkActionMutation } from '../../../lib/networking/mutations/bulkActionMutation'
import {
showErrorToast,
showSuccessToast,
showSuccessToastWithAction,
} from '../../../lib/toastHelpers'
import { SetPageLabelsModalPresenter } from '../article/SetLabelsModalPresenter'
import { NotebookPresenter } from '../article/NotebookPresenter'
import { saveUrlMutation } from '../../../lib/networking/mutations/saveUrlMutation'
import { articleQuery } from '../../../lib/networking/queries/useGetArticleQuery'
import { PinnedButtons } from '../homeFeed/PinnedButtons'
import { PinnedSearch } from '../../../pages/settings/pinned-searches'
import { FetchItemsError } from '../homeFeed/FetchItemsError'
import { LibraryHeader } from './LibraryHeader'
type LibrarySideBarProps = {
text: string
}
export function LibrarySideBar(props: LibrarySideBarProps): JSX.Element {
return <VStack css={{ width: '100%', height: '100%' }}>{props.text}</VStack>
}

View File

@ -105,9 +105,9 @@ export function NavigationMenu(props: LibraryFilterMenuProps): JSX.Element {
left: '0px',
top: '0px',
position: 'fixed',
bg: '$thLeftMenuBackground',
height: '100%',
width: LIBRARY_LEFT_MENU_WIDTH,
bg: '$thLeftMenuBackground',
overflowY: 'auto',
overflowX: 'hidden',
'&::-webkit-scrollbar': {
@ -200,7 +200,7 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
gap: '5px',
width: '100%',
borderBottom: '1px solid $thBorderColor',
px: '15px',
px: '0px',
pb: '25px',
}}
alignment="start"
@ -213,13 +213,6 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
isSelected={props.section == 'home'}
icon={<HomeIcon color={theme.colors.thHomeIcon.toString()} />}
/>
<NavButton
{...props}
text="Subscriptions"
section="subscriptions"
isSelected={props.section == 'subscriptions'}
icon={<FollowingIcon color="#F59932" />}
/>
<NavButton
{...props}
text="Library"
@ -227,6 +220,13 @@ const LibraryNav = (props: LibraryFilterMenuProps): JSX.Element => {
isSelected={props.section == 'library'}
icon={<LibraryIcon color={theme.colors.ctaBlue.toString()} />}
/>
<NavButton
{...props}
text="Subscriptions"
section="subscriptions"
isSelected={props.section == 'subscriptions'}
icon={<FollowingIcon color="#F59932" />}
/>
<NavButton
{...props}
text="Highlights"
@ -303,7 +303,7 @@ const Shortcuts = (props: LibraryFilterMenuProps): JSX.Element => {
m: '0px',
gap: '8px',
width: '100%',
px: '15px',
px: '0px',
pb: '25px',
}}
alignment="start"
@ -815,6 +815,7 @@ function NavButton(props: NavButtonProps): JSX.Element {
width: '100%',
maxWidth: '100%',
height: '34px',
px: '15px',
backgroundColor: props.isSelected
? '$thLibrarySelectionColor'

View File

@ -31,6 +31,7 @@
"@radix-ui/react-switch": "^1.0.1",
"@sentry/nextjs": "^7.42.0",
"@stitches/react": "^1.2.5",
"allotment": "^1.20.2",
"antd": "4.24.3",
"axios": "^1.2.0",
"cookie": "^0.5.0",

View File

@ -1,7 +1,7 @@
import * as HoverCard from '@radix-ui/react-hover-card'
import { styled } from '@stitches/react'
import { useRouter } from 'next/router'
import { useCallback, useMemo, useState } from 'react'
import { useMemo, useState } from 'react'
import { Button } from '../../components/elements/Button'
import { AddToLibraryActionIcon } from '../../components/elements/icons/home/AddToLibraryActionIcon'
import { ArchiveActionIcon } from '../../components/elements/icons/home/ArchiveActionIcon'

View File

@ -1,13 +1,14 @@
import { NavigationLayout } from '../../components/templates/NavigationLayout'
import { PrimaryLayout } from '../../components/templates/PrimaryLayout'
import { HomeFeedContainer } from '../../components/templates/homeFeed/HomeFeedContainer'
import { VStack } from '../../components/elements/LayoutPrimitives'
import { Box, VStack } from '../../components/elements/LayoutPrimitives'
import { LibraryContainer } from '../../components/templates/library/LibraryContainer'
import { LibraryItemsContainer } from '../../components/templates/library/LibraryItemsContainer'
import { LibrarySideBar } from '../../components/templates/library/LibrarySideBar'
import { Allotment, LayoutPriority } from 'allotment'
import 'allotment/dist/style.css'
export default function Home(): JSX.Element {
return <LoadedContent />
}
function LoadedContent(): JSX.Element {
export default function Library(): JSX.Element {
return (
<NavigationLayout
section="library"
@ -16,18 +17,16 @@ function LoadedContent(): JSX.Element {
path: '/library',
}}
>
<VStack
alignment="start"
distribution="center"
css={{
px: '70px',
backgroundColor: '$thLibraryBackground',
'@lgDown': { px: '20px' },
'@mdDown': { px: '10px' },
}}
>
<HomeFeedContainer />
</VStack>
{/* <Allotment>
<Allotment.Pane priority={LayoutPriority.High}> */}
<Box css={{ width: '100%', height: '100%', overflowY: 'auto' }}>
<LibraryContainer />
</Box>
{/* </Allotment.Pane>
<Allotment.Pane maxSize={480}>
<LibrarySideBar text="SIDEBAR" />
</Allotment.Pane>
</Allotment> */}
</NavigationLayout>
)
}

View File

@ -3793,6 +3793,11 @@
dependencies:
lodash "^4.17.21"
"@juggle/resize-observer@^3.3.1":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
"@langchain/anthropic@^0.1.16":
version "0.1.16"
resolved "https://registry.yarnpkg.com/@langchain/anthropic/-/anthropic-0.1.16.tgz#c2a9d3dd4e02df7118dd97cf2503c9bd1a4de5ad"
@ -9649,6 +9654,18 @@ ajv@^8.11.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
allotment@^1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/allotment/-/allotment-1.20.2.tgz#5ea3a630b3265479debb69156658244711f83843"
integrity sha512-TaCuHfYNcsJS9EPk04M7TlG5Rl3vbAdHeAyrTE9D5vbpzV+wxnRoUrulDbfnzaQcPIZKpHJNixDOoZNuzliKEA==
dependencies:
classnames "^2.3.0"
eventemitter3 "^5.0.0"
lodash.clamp "^4.0.0"
lodash.debounce "^4.0.0"
lodash.isequal "^4.5.0"
use-resize-observer "^9.0.0"
ansi-align@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb"
@ -12147,6 +12164,11 @@ classnames@^2.2.6:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
classnames@^2.3.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
cld@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/cld/-/cld-2.9.1.tgz#0c6685672d9f4612dfeb75eabfdd17bf282a87a6"
@ -15330,6 +15352,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
eventemitter3@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
eventid@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/eventid/-/eventid-2.0.1.tgz#574e860149457a79a2efe788c459f0c3062d02ec"
@ -21076,12 +21103,17 @@ lodash.capitalize@^4.2.1:
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
integrity sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==
lodash.clamp@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa"
integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.debounce@^4.0.8:
lodash.debounce@^4.0.0, lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
@ -21126,6 +21158,11 @@ lodash.isboolean@^3.0.3:
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.isfunction@^3.0.9:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
@ -31088,6 +31125,13 @@ use-latest@^1.0.0:
dependencies:
use-isomorphic-layout-effect "^1.0.0"
use-resize-observer@^9.0.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c"
integrity sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==
dependencies:
"@juggle/resize-observer" "^3.3.1"
use-sidecar@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"