diff --git a/frontend/src/components/explore-page/pinned-notes/caret.module.scss b/frontend/src/components/explore-page/pinned-notes/caret.module.scss new file mode 100644 index 000000000..1d26b5306 --- /dev/null +++ b/frontend/src/components/explore-page/pinned-notes/caret.module.scss @@ -0,0 +1,10 @@ +/*! + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.active:hover { + cursor: pointer; + color: var(--bs-emphasis-color); +} diff --git a/frontend/src/components/explore-page/pinned-notes/caret.tsx b/frontend/src/components/explore-page/pinned-notes/caret.tsx new file mode 100644 index 000000000..906a889f3 --- /dev/null +++ b/frontend/src/components/explore-page/pinned-notes/caret.tsx @@ -0,0 +1,32 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React, { useMemo } from 'react' +import { + CaretLeft as IconCaretLeftEmpty, + CaretLeftFill as IconCaretLeft, + CaretRight as IconCaretRightEmpty, + CaretRightFill as IconCaretRight +} from 'react-bootstrap-icons' +import styles from './caret.module.scss' +import { UiIcon } from '../../common/icons/ui-icon' + +interface CaretProps { + left: boolean + active: boolean + onClick?: () => void +} + +export const Caret: React.FC = ({ active, left, onClick }) => { + const activeIcon = useMemo(() => (left ? IconCaretLeft : IconCaretRight), [left]) + const inactiveIcon = useMemo(() => (left ? IconCaretLeftEmpty : IconCaretRightEmpty), [left]) + + return ( +
+ +
+ ) +} diff --git a/frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.css b/frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.scss similarity index 80% rename from frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.css rename to frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.scss index 49f91b968..fd7e17866 100644 --- a/frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.css +++ b/frontend/src/components/explore-page/pinned-notes/pinned-note-card.module.scss @@ -1,20 +1,29 @@ -/* +/*! * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ .card { + width: 25rem; min-width: 25rem; height: 8rem; position: relative; text-decoration: none; + scroll-snap-align: start; + scroll-snap-stop: always; } .card:hover { background-color: var(--bs-card-border-color); } +.cardBody { + display: flex; + flex-direction: column; + justify-content: space-between; +} + .bookmark { position: absolute; top: -2px; diff --git a/frontend/src/components/explore-page/pinned-notes/pinned-note-card.tsx b/frontend/src/components/explore-page/pinned-notes/pinned-note-card.tsx index 8b01b699e..24bb75ecd 100644 --- a/frontend/src/components/explore-page/pinned-notes/pinned-note-card.tsx +++ b/frontend/src/components/explore-page/pinned-notes/pinned-note-card.tsx @@ -7,7 +7,7 @@ import React, { type MouseEvent, useMemo } from 'react' import { Badge, Card } from 'react-bootstrap' import { DateTime } from 'luxon' import { BookmarkStarFill as IconPinned } from 'react-bootstrap-icons' -import styles from './pinned-note-card.module.css' +import styles from './pinned-note-card.module.scss' import { useCallback } from 'react' import { NoteTypeIcon } from '../../common/note-type-icon/note-type-icon' import type { NoteType } from '@hedgedoc/commons' @@ -60,23 +60,21 @@ export const PinnedNoteCard: React.FC = ({ title, id, lastVisited }, [tags, onClickTag, labelTag]) return ( -
  • - - -
    - -
    -
    - - - - {title} - - - {lastVisitedString} - {tagsChips} - - -
  • + + +
    + +
    +
    + + + + {title} + + + {lastVisitedString} +
    {tagsChips}
    + + ) } diff --git a/frontend/src/components/explore-page/pinned-notes/pinned-notes.module.css b/frontend/src/components/explore-page/pinned-notes/pinned-notes.module.css new file mode 100644 index 000000000..d9cec5e59 --- /dev/null +++ b/frontend/src/components/explore-page/pinned-notes/pinned-notes.module.css @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.scrollbox { + display: flex; + flex-flow: row; + overflow-x: scroll; + gap: 0.5rem; + scroll-snap-type: x mandatory; + scrollbar-width: none; +} diff --git a/frontend/src/components/explore-page/pinned-notes/pinned-notes.tsx b/frontend/src/components/explore-page/pinned-notes/pinned-notes.tsx index e1750c1bb..6db857320 100644 --- a/frontend/src/components/explore-page/pinned-notes/pinned-notes.tsx +++ b/frontend/src/components/explore-page/pinned-notes/pinned-notes.tsx @@ -3,11 +3,13 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Fragment, useMemo } from 'react' +import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react' import type { NoteCardProps } from './pinned-note-card' import { PinnedNoteCard } from './pinned-note-card' import { Trans, useTranslation } from 'react-i18next' import { NoteType } from '@hedgedoc/commons' +import { Caret } from './caret' +import styles from './pinned-notes.module.css' const mockListPinnedNotes: NoteCardProps[] = [ { @@ -74,9 +76,47 @@ const mockListPinnedNotes: NoteCardProps[] = [ export const PinnedNotes: React.FC = () => { useTranslation() + const scrollboxRef = useRef(null) + const [enableScrollLeft, setEnableScrollLeft] = useState(false) + const [enableScrollRight, setEnableScrollRight] = useState(true) - const cards = useMemo(() => { - return mockListPinnedNotes.map((note: NoteCardProps) => ) + const pinnedNotes = useMemo(() => { + return mockListPinnedNotes + }, []) + + const leftClick = useCallback(() => { + if (!scrollboxRef.current) { + return + } + scrollboxRef.current.scrollBy({ + left: -400, + behavior: 'smooth' + }) + }, []) + const rightClick = useCallback(() => { + if (!scrollboxRef.current) { + return + } + scrollboxRef.current.scrollBy({ + left: 400, + behavior: 'smooth' + }) + }, []) + + useEffect(() => { + if (!scrollboxRef.current) { + return + } + const scrollbox = scrollboxRef.current + const scrollHandler = () => { + setEnableScrollLeft(scrollbox.scrollLeft > 0) + setEnableScrollRight(Math.ceil(scrollbox.scrollLeft + scrollbox.clientWidth) < scrollbox.scrollWidth) + } + scrollbox.addEventListener('scroll', scrollHandler) + scrollHandler() + return () => { + scrollbox.removeEventListener('scroll', scrollHandler) + } }, []) return ( @@ -84,7 +124,15 @@ export const PinnedNotes: React.FC = () => {

    -
      {cards}
    +
    + +
    + {pinnedNotes.map((note: NoteCardProps) => ( + + ))} +
    + +
    ) }