From 925297bc3aae5821d1bbd08db06f7c2256cd3799 Mon Sep 17 00:00:00 2001 From: plebeius Date: Mon, 23 Feb 2026 20:50:30 +0800 Subject: [PATCH] refactor footer --- .../board-pagination/board-pagination.tsx | 2 +- .../catalog-footer-first-row.tsx | 20 ---- .../catalog-footer-first-row/index.ts | 1 - .../footer-first-row.module.css | 27 ----- src/components/footer/footer.module.css | 94 ++++++++++++++++++ src/components/footer/footer.tsx | 99 +++++++++++++++++++ src/components/footer/index.ts | 2 + src/components/page-footer-desktop/index.ts | 2 - .../page-footer-desktop.module.css | 38 ------- .../page-footer-desktop.tsx | 25 ----- .../thread-footer-first-row/index.ts | 2 - .../thread-footer-first-row.module.css | 31 ------ .../thread-footer-first-row.tsx | 52 ---------- src/views/board/board.tsx | 2 +- src/views/catalog/catalog.tsx | 3 +- src/views/post/post.tsx | 3 +- 16 files changed, 199 insertions(+), 204 deletions(-) delete mode 100644 src/components/catalog-footer-first-row/catalog-footer-first-row.tsx delete mode 100644 src/components/catalog-footer-first-row/index.ts delete mode 100644 src/components/footer-first-row/footer-first-row.module.css create mode 100644 src/components/footer/footer.module.css create mode 100644 src/components/footer/footer.tsx create mode 100644 src/components/footer/index.ts delete mode 100644 src/components/page-footer-desktop/index.ts delete mode 100644 src/components/page-footer-desktop/page-footer-desktop.module.css delete mode 100644 src/components/page-footer-desktop/page-footer-desktop.tsx delete mode 100644 src/components/thread-footer-first-row/index.ts delete mode 100644 src/components/thread-footer-first-row/thread-footer-first-row.module.css delete mode 100644 src/components/thread-footer-first-row/thread-footer-first-row.tsx diff --git a/src/components/board-pagination/board-pagination.tsx b/src/components/board-pagination/board-pagination.tsx index 6f26764b..1cad3230 100644 --- a/src/components/board-pagination/board-pagination.tsx +++ b/src/components/board-pagination/board-pagination.tsx @@ -1,7 +1,7 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import StyleSelector from '../style-selector/style-selector'; -import footerStyles from '../footer-first-row/footer-first-row.module.css'; +import footerStyles from '../footer/footer.module.css'; import styles from './board-pagination.module.css'; export interface BoardPaginationProps { diff --git a/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx b/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx deleted file mode 100644 index 9df6f278..00000000 --- a/src/components/catalog-footer-first-row/catalog-footer-first-row.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import StyleSelector from '../style-selector/style-selector'; -import styles from '../footer-first-row/footer-first-row.module.css'; - -/** Catalog footer first row: Style selector on right only (no pagination; catalog shows all pages at once). */ -const CatalogFooterFirstRow = () => { - const { t } = useTranslation(); - - return ( -
-
-
- {t('style')}: - -
-
- ); -}; - -export default CatalogFooterFirstRow; diff --git a/src/components/catalog-footer-first-row/index.ts b/src/components/catalog-footer-first-row/index.ts deleted file mode 100644 index 46fb1a18..00000000 --- a/src/components/catalog-footer-first-row/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './catalog-footer-first-row'; diff --git a/src/components/footer-first-row/footer-first-row.module.css b/src/components/footer-first-row/footer-first-row.module.css deleted file mode 100644 index 7724873e..00000000 --- a/src/components/footer-first-row/footer-first-row.module.css +++ /dev/null @@ -1,27 +0,0 @@ -/* Shared footer first-row layout styles (catalog footer, board pagination footer mode) */ -.footerRow { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - width: 100%; -} - -.footerLeft { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.footerRight { - display: flex; - align-items: center; - gap: 6px; -} - -.styleLabel { - font-size: 12px; - text-transform: capitalize; -} diff --git a/src/components/footer/footer.module.css b/src/components/footer/footer.module.css new file mode 100644 index 00000000..d301e91f --- /dev/null +++ b/src/components/footer/footer.module.css @@ -0,0 +1,94 @@ +/* Page footer (desktop) */ +.footer { + margin-top: 16px; + padding-bottom: 24px; +} + +.firstRow { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + padding: 8px 0; + text-align: center; +} + +.boardsBarRow { + padding: 8px 0; +} + +.legalMeta { + padding: 12px 0 0; + font-size: 11px; + color: var(--body-font-color); + text-align: center; +} + +/* Shared footer first-row layout (catalog, board pagination footer mode) */ +.footerRow { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + width: 100%; +} + +.footerLeft { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.footerRight { + display: flex; + align-items: center; + gap: 6px; +} + +.styleLabel { + font-size: 12px; + text-transform: capitalize; +} + +/* Thread footer first row */ +.threadRow { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + width: 100%; + text-transform: capitalize; +} + +.threadLeft { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.threadCenter { + display: flex; + align-items: center; + justify-content: center; +} + +.threadRight { + display: flex; + align-items: center; +} + +.threadRow .button, +.button { + text-transform: capitalize; +} + +@media (max-width: 640px) { + .footer { + display: none; + } +} diff --git a/src/components/footer/footer.tsx b/src/components/footer/footer.tsx new file mode 100644 index 00000000..19c7c47e --- /dev/null +++ b/src/components/footer/footer.tsx @@ -0,0 +1,99 @@ +import { useTranslation } from 'react-i18next'; +import { useLocation, useParams } from 'react-router-dom'; +import BoardsBar from '../boardsbar'; +import SiteLegalMeta from '../site-legal-meta'; +import StyleSelector from '../style-selector/style-selector'; +import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats } from '../board-buttons/board-buttons'; +import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils'; +import useReplyModalStore from '../../stores/use-reply-modal-store'; +import styles from './footer.module.css'; + +/* ----------------------------------------------------------------------------- + * PageFooterDesktop + * Main page footer wrapper: hr, first row slot, BoardsBar, SiteLegalMeta. + * -------------------------------------------------------------------------- */ + +export interface PageFooterDesktopProps { + /** Mode-specific first row content (e.g. board pagination or thread controls) */ + firstRow: React.ReactNode; +} + +export const PageFooterDesktop = ({ firstRow }: PageFooterDesktopProps) => ( +
+
+
{firstRow}
+
+ +
+
+ +
+
+); + +/* ----------------------------------------------------------------------------- + * CatalogFooterFirstRow + * Catalog footer first row: Style selector on right only (no pagination). + * -------------------------------------------------------------------------- */ + +export const CatalogFooterFirstRow = () => { + const { t } = useTranslation(); + return ( +
+
+
+ {t('style')}: + +
+
+ ); +}; + +/* ----------------------------------------------------------------------------- + * ThreadFooterFirstRow + * Thread page footer first row: Return, Catalog, Top, Update, Auto, Post a Reply, stats. + * -------------------------------------------------------------------------- */ + +export interface ThreadFooterFirstRowProps { + postCid: string; + threadNumber: number | undefined; + subplebbitAddress: string; + /** Thread closed - disable Post a Reply */ + isThreadClosed?: boolean; +} + +export const ThreadFooterFirstRow = ({ postCid, threadNumber, subplebbitAddress, isThreadClosed = false }: ThreadFooterFirstRowProps) => { + const { t } = useTranslation(); + const location = useLocation(); + const params = useParams(); + const { openReplyModalEmpty } = useReplyModalStore(); + + const isInAllView = isAllView(location.pathname); + const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); + const isInModView = isModView(location.pathname); + + const handlePostReplyClick = () => { + if (isThreadClosed) return; + openReplyModalEmpty(postCid, threadNumber, subplebbitAddress); + }; + + return ( +
+
+ [] [ + ] [ + ] [] [] +
+
+ [ + + ] +
+
+ +
+
+ ); +}; diff --git a/src/components/footer/index.ts b/src/components/footer/index.ts new file mode 100644 index 00000000..70454ba3 --- /dev/null +++ b/src/components/footer/index.ts @@ -0,0 +1,2 @@ +export { PageFooterDesktop, CatalogFooterFirstRow, ThreadFooterFirstRow } from './footer'; +export type { PageFooterDesktopProps, ThreadFooterFirstRowProps } from './footer'; diff --git a/src/components/page-footer-desktop/index.ts b/src/components/page-footer-desktop/index.ts deleted file mode 100644 index 56b45e60..00000000 --- a/src/components/page-footer-desktop/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './page-footer-desktop'; -export type { PageFooterDesktopProps } from './page-footer-desktop'; diff --git a/src/components/page-footer-desktop/page-footer-desktop.module.css b/src/components/page-footer-desktop/page-footer-desktop.module.css deleted file mode 100644 index 158841cd..00000000 --- a/src/components/page-footer-desktop/page-footer-desktop.module.css +++ /dev/null @@ -1,38 +0,0 @@ -.footer { - margin-top: 16px; - padding-bottom: 24px; -} - -.footer hr { - width: 90%; - margin: 16px auto; - border: none; - border-top: 1px solid var(--border-color, #ccc); -} - -.firstRow { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 8px; - padding: 8px 0; - text-align: center; -} - -.boardsBarRow { - padding: 8px 0; -} - -.legalMeta { - padding: 12px 0 0; - font-size: 11px; - color: var(--body-font-color); - text-align: center; -} - -@media (max-width: 640px) { - .footer { - display: none; - } -} diff --git a/src/components/page-footer-desktop/page-footer-desktop.tsx b/src/components/page-footer-desktop/page-footer-desktop.tsx deleted file mode 100644 index 789af537..00000000 --- a/src/components/page-footer-desktop/page-footer-desktop.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import BoardsBar from '../boardsbar'; -import SiteLegalMeta from '../site-legal-meta'; -import styles from './page-footer-desktop.module.css'; - -export interface PageFooterDesktopProps { - /** Mode-specific first row content (e.g. board pagination or thread controls) */ - firstRow: React.ReactNode; -} - -const PageFooterDesktop = ({ firstRow }: PageFooterDesktopProps) => { - return ( -
-
-
{firstRow}
-
- -
-
- -
-
- ); -}; - -export default PageFooterDesktop; diff --git a/src/components/thread-footer-first-row/index.ts b/src/components/thread-footer-first-row/index.ts deleted file mode 100644 index 33f1aea1..00000000 --- a/src/components/thread-footer-first-row/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './thread-footer-first-row'; -export type { ThreadFooterFirstRowProps } from './thread-footer-first-row'; diff --git a/src/components/thread-footer-first-row/thread-footer-first-row.module.css b/src/components/thread-footer-first-row/thread-footer-first-row.module.css deleted file mode 100644 index e0d5a376..00000000 --- a/src/components/thread-footer-first-row/thread-footer-first-row.module.css +++ /dev/null @@ -1,31 +0,0 @@ -.row { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 12px; - width: 100%; - text-transform: capitalize; -} - -.left { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.center { - display: flex; - align-items: center; - justify-content: center; -} - -.right { - display: flex; - align-items: center; -} - -.row .button { - text-transform: capitalize; -} diff --git a/src/components/thread-footer-first-row/thread-footer-first-row.tsx b/src/components/thread-footer-first-row/thread-footer-first-row.tsx deleted file mode 100644 index 0970bd00..00000000 --- a/src/components/thread-footer-first-row/thread-footer-first-row.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useLocation, useParams } from 'react-router-dom'; -import { isAllView, isSubscriptionsView, isModView } from '../../lib/utils/view-utils'; -import useReplyModalStore from '../../stores/use-reply-modal-store'; -import { ReturnButton, CatalogButton, TopButton, UpdateButton, AutoButton, PostPageStats } from '../board-buttons/board-buttons'; -import styles from './thread-footer-first-row.module.css'; - -export interface ThreadFooterFirstRowProps { - postCid: string; - threadNumber: number | undefined; - subplebbitAddress: string; - /** Thread closed - disable Post a Reply */ - isThreadClosed?: boolean; -} - -const ThreadFooterFirstRow = ({ postCid, threadNumber, subplebbitAddress, isThreadClosed = false }: ThreadFooterFirstRowProps) => { - const { t } = useTranslation(); - const location = useLocation(); - const params = useParams(); - const { openReplyModalEmpty } = useReplyModalStore(); - - const isInAllView = isAllView(location.pathname); - const isInSubscriptionsView = isSubscriptionsView(location.pathname, params); - const isInModView = isModView(location.pathname); - - const handlePostReplyClick = () => { - if (isThreadClosed) return; - openReplyModalEmpty(postCid, threadNumber, subplebbitAddress); - }; - - return ( -
-
- [] [ - ] [ - ] [] [] -
-
- [ - - ] -
-
- -
-
- ); -}; - -export default ThreadFooterFirstRow; diff --git a/src/views/board/board.tsx b/src/views/board/board.tsx index 57990181..cf03e798 100644 --- a/src/views/board/board.tsx +++ b/src/views/board/board.tsx @@ -18,7 +18,7 @@ import { getPageFromFeedPath, getSubplebbitAddress, isDirectoryBoard, normalizeM import ErrorDisplay from '../../components/error-display/error-display'; import LoadingEllipsis from '../../components/loading-ellipsis'; import BoardPagination from '../../components/board-pagination'; -import PageFooterDesktop from '../../components/page-footer-desktop'; +import { PageFooterDesktop } from '../../components/footer'; import { Post } from '../post'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; diff --git a/src/views/catalog/catalog.tsx b/src/views/catalog/catalog.tsx index 7e387154..31dfde2d 100644 --- a/src/views/catalog/catalog.tsx +++ b/src/views/catalog/catalog.tsx @@ -17,10 +17,9 @@ import useSortingStore from '../../stores/use-sorting-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import { getSubplebbitAddress, isDirectoryBoard, normalizeMultiboardFeedPath } from '../../lib/utils/route-utils'; import CatalogRow from '../../components/catalog-row'; -import CatalogFooterFirstRow from '../../components/catalog-footer-first-row'; +import { CatalogFooterFirstRow, PageFooterDesktop } from '../../components/footer'; import LoadingEllipsis from '../../components/loading-ellipsis'; import ErrorDisplay from '../../components/error-display/error-display'; -import PageFooterDesktop from '../../components/page-footer-desktop'; import styles from './catalog.module.css'; import { commentMatchesPattern } from '../../lib/utils/pattern-utils'; import { sortCatalogFeedForDisplay } from '../../lib/utils/catalog-sort'; diff --git a/src/views/post/post.tsx b/src/views/post/post.tsx index 99775e54..f2847345 100644 --- a/src/views/post/post.tsx +++ b/src/views/post/post.tsx @@ -10,10 +10,9 @@ import { useDirectories } from '../../hooks/use-directories'; import { isDirectoryBoard } from '../../lib/utils/route-utils'; import useIsMobile from '../../hooks/use-is-mobile'; import ErrorDisplay from '../../components/error-display/error-display'; -import PageFooterDesktop from '../../components/page-footer-desktop'; +import { PageFooterDesktop, ThreadFooterFirstRow } from '../../components/footer'; import PostDesktop from '../../components/post-desktop'; import PostMobile from '../../components/post-mobile'; -import ThreadFooterFirstRow from '../../components/thread-footer-first-row'; import styles from './post.module.css'; // useComment may not return cached feed data immediately due to its updatedAt comparison logic.