mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor footer
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.footerRow}>
|
||||
<div className={styles.footerLeft} />
|
||||
<div className={styles.footerRight}>
|
||||
<span className={styles.styleLabel}>{t('style')}:</span>
|
||||
<StyleSelector />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CatalogFooterFirstRow;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './catalog-footer-first-row';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) => (
|
||||
<footer className={styles.footer}>
|
||||
<hr />
|
||||
<div className={styles.firstRow}>{firstRow}</div>
|
||||
<div className={styles.boardsBarRow}>
|
||||
<BoardsBar />
|
||||
</div>
|
||||
<div className={styles.legalMeta}>
|
||||
<SiteLegalMeta order='license-first' />
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* CatalogFooterFirstRow
|
||||
* Catalog footer first row: Style selector on right only (no pagination).
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
export const CatalogFooterFirstRow = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={styles.footerRow}>
|
||||
<div className={styles.footerLeft} />
|
||||
<div className={styles.footerRight}>
|
||||
<span className={styles.styleLabel}>{t('style')}:</span>
|
||||
<StyleSelector />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* 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 (
|
||||
<div className={styles.threadRow}>
|
||||
<div className={styles.threadLeft}>
|
||||
[<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
|
||||
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
|
||||
<TopButton />] [<UpdateButton />] [<AutoButton />]
|
||||
</div>
|
||||
<div className={styles.threadCenter}>
|
||||
[
|
||||
<button type='button' className={styles.button} onClick={handlePostReplyClick} disabled={isThreadClosed} aria-label={t('post_a_reply')}>
|
||||
{t('post_a_reply')}
|
||||
</button>
|
||||
]
|
||||
</div>
|
||||
<div className={styles.threadRight}>
|
||||
<PostPageStats />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { PageFooterDesktop, CatalogFooterFirstRow, ThreadFooterFirstRow } from './footer';
|
||||
export type { PageFooterDesktopProps, ThreadFooterFirstRowProps } from './footer';
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from './page-footer-desktop';
|
||||
export type { PageFooterDesktopProps } from './page-footer-desktop';
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 (
|
||||
<footer className={styles.footer}>
|
||||
<hr />
|
||||
<div className={styles.firstRow}>{firstRow}</div>
|
||||
<div className={styles.boardsBarRow}>
|
||||
<BoardsBar />
|
||||
</div>
|
||||
<div className={styles.legalMeta}>
|
||||
<SiteLegalMeta order='license-first' />
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageFooterDesktop;
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default } from './thread-footer-first-row';
|
||||
export type { ThreadFooterFirstRowProps } from './thread-footer-first-row';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.left}>
|
||||
[<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
|
||||
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} isInModView={isInModView} />] [
|
||||
<TopButton />] [<UpdateButton />] [<AutoButton />]
|
||||
</div>
|
||||
<div className={styles.center}>
|
||||
[
|
||||
<button type='button' className='button' onClick={handlePostReplyClick} disabled={isThreadClosed} aria-label={t('post_a_reply')}>
|
||||
{t('post_a_reply')}
|
||||
</button>
|
||||
]
|
||||
</div>
|
||||
<div className={styles.right}>
|
||||
<PostPageStats />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThreadFooterFirstRow;
|
||||
@@ -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 } = {};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user