diff --git a/app/javascript/flavours/glitch/components/column_header.tsx b/app/javascript/flavours/glitch/components/column_header.tsx index d40b47b28c..fda5c71b36 100644 --- a/app/javascript/flavours/glitch/components/column_header.tsx +++ b/app/javascript/flavours/glitch/components/column_header.tsx @@ -276,9 +276,11 @@ export const ColumnHeader: React.FC = ({ ); + const HeadingElement = hasTitle ? 'h1' : 'div'; + const component = (
-

+ {hasTitle && ( <> {backButton} @@ -311,7 +313,7 @@ export const ColumnHeader: React.FC = ({ {extraButton} {collapseButton}

- +
(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => { - if (!isLoading && Children.count(children) === 0 && emptyMessage) { + if (!isLoading && !hasReactChildren(children) && emptyMessage) { return (
{emptyMessage} diff --git a/app/javascript/flavours/glitch/components/tab_list/index.tsx b/app/javascript/flavours/glitch/components/tab_list/index.tsx new file mode 100644 index 0000000000..98651ff4d0 --- /dev/null +++ b/app/javascript/flavours/glitch/components/tab_list/index.tsx @@ -0,0 +1,51 @@ +import type { ComponentPropsWithoutRef, FC } from 'react'; + +import classNames from 'classnames'; +import type { NavLinkProps } from 'react-router-dom'; +import { NavLink } from 'react-router-dom'; + +import classes from './styles.module.scss'; + +interface TabListProps { + /** + * Setting this will remove the default border and + * horizontal padding from the component + */ + plain?: boolean; +} + +/** + * Display a simple row of links as tabs. + * The current page will be highlighted automatically based on the link destination. + */ +export const TabList: FC> = ({ + plain, + className, + children, + ...otherProps +}) => { + return ( +
+ {children} +
+ ); +}; + +export const TabLink: FC = ({ + className, + children, + ...otherProps +}) => { + return ( + + {children} + + ); +}; diff --git a/app/javascript/flavours/glitch/components/tab_list/styles.module.scss b/app/javascript/flavours/glitch/components/tab_list/styles.module.scss new file mode 100644 index 0000000000..aeadd95f08 --- /dev/null +++ b/app/javascript/flavours/glitch/components/tab_list/styles.module.scss @@ -0,0 +1,39 @@ +.tabList { + display: flex; + gap: 16px; +} + +.withSpaceAndBorder { + padding-inline: 16px; + border-bottom: 1px solid var(--color-border-primary); +} + +.tab { + display: block; + padding: 18px 0; + color: var(--color-text-primary); + font-size: 15px; + font-weight: 500; + text-decoration: none; + border-radius: 0; + transition: color 0.2s ease-in-out; + + @container (width < 500px) { + flex: 1 1 0px; + text-align: center; + } + + &:hover { + text-decoration: none; + } + + &:not(:global(.active)):is(:hover, :focus) { + color: var(--color-text-brand-soft); + } + + &:global(.active) { + color: var(--color-text-brand); + border-bottom: 4px solid var(--color-border-brand); + padding-bottom: 14px; + } +} diff --git a/app/javascript/flavours/glitch/components/tab_list/tab_list.stories.tsx b/app/javascript/flavours/glitch/components/tab_list/tab_list.stories.tsx new file mode 100644 index 0000000000..56edb1b324 --- /dev/null +++ b/app/javascript/flavours/glitch/components/tab_list/tab_list.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; + +import { TabList, TabLink } from './index'; + +const meta = { + title: 'Components/TabList', + component: TabList, + subcomponents: { TabLink }, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( + + Activity + Media + Featured + + ), +}; + +export const Plain: Story = { + render: () => ( + + Activity + Media + Featured + + ), +}; diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/styles.module.scss b/app/javascript/flavours/glitch/features/account_timeline/components/styles.module.scss index 9c13026b97..dc9a4459d2 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/styles.module.scss +++ b/app/javascript/flavours/glitch/features/account_timeline/components/styles.module.scss @@ -355,48 +355,10 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; } } -.tabs, -.noTabs { - border-bottom: 1px solid var(--color-border-primary); -} - -.tabs { - display: flex; - gap: 16px; - padding: 0 16px; - - @container (width < 500px) { - a { - flex: 1 1 0px; - text-align: center; - } - } - - a { - display: block; - font-size: 15px; - font-weight: 500; - padding: 18px 0; - text-decoration: none; - color: var(--color-text-primary); - border-radius: 0; - transition: color 0.2s ease-in-out; - - &:not([aria-current='page']):is(:hover, :focus) { - color: var(--color-text-brand-soft); - } - } - - :global(.active) { - color: var(--color-text-brand); - border-bottom: 4px solid var(--color-border-brand); - padding-bottom: 14px; - } -} - .noTabs { width: 100%; border-width: 0 0 1px; + border-bottom: 1px solid var(--color-border-primary); } .bannerBase { diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx b/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx index c3d42d1407..b19211a680 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/tabs.tsx @@ -3,8 +3,8 @@ import type { FC } from 'react'; import { FormattedMessage } from 'react-intl'; import type { NavLinkProps } from 'react-router-dom'; -import { NavLink } from 'react-router-dom'; +import { TabLink, TabList } from '@/flavours/glitch/components/tab_list'; import { useAccount } from '@/flavours/glitch/hooks/useAccount'; import { useAccountId } from '@/flavours/glitch/hooks/useAccountId'; @@ -28,20 +28,20 @@ export const AccountTabs: FC = () => { } return ( -
- + + - + {show_media && ( - + - + )} {show_featured && ( - + - + )} -
+ ); }; diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_list_item.module.scss b/app/javascript/flavours/glitch/features/collections/components/collection_list_item.module.scss index 5d440d62f7..1079d1aebf 100644 --- a/app/javascript/flavours/glitch/features/collections/components/collection_list_item.module.scss +++ b/app/javascript/flavours/glitch/features/collections/components/collection_list_item.module.scss @@ -1,6 +1,5 @@ .wrapper { --list-item-padding: 16px; - --list-item-padding-block: 12px; --list-item-gap: 16px; &:not(.wrapperWithoutBorder) { diff --git a/app/javascript/flavours/glitch/features/collections/editor/index.tsx b/app/javascript/flavours/glitch/features/collections/editor/index.tsx index 2afa707908..f994039ff2 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/index.tsx @@ -144,7 +144,7 @@ export const CollectionEditorPage: React.FC<{ /> ) : ( - + )}
@@ -156,9 +156,11 @@ export const CollectionEditorPage: React.FC<{ ); }; -export const MaxCollectionsCallout: React.FC = () => ( +export const MaxCollectionsCallout: React.FC<{ className?: string }> = ({ + className, +}) => ( ( + + + + +); + export function useAccountCollections(accountId: string | null | undefined) { const dispatch = useAppDispatch(); @@ -65,33 +88,11 @@ export const Collections: React.FC<{ const { collections, status } = useAccountCollections(accountId); - const emptyMessage = - status === 'error' || !accountId ? ( - - ) : ( - <> - - -
- -
- - - - ); - const canCreateMoreCollections = collections.length < userCollectionLimit; const isOwnCollection = accountId === me; + const showCreateButton = + isOwnCollection && status === 'idle' && canCreateMoreCollections; + const titleMessage = isOwnCollection ? messages.headingMe : messages.headingOther; @@ -103,45 +104,88 @@ export const Collections: React.FC<{ name: , }); + const tabMessage = isOwnCollection + ? messages.createdByYou + : messages.createdByAuthor; + + const errorMessage = (status === 'error' || !accountId) && ( + + ); + return ( - - - - ) - } - /> + - {status === 'idle' && !canCreateMoreCollections && ( - +
+

{pageTitleHtml}

+ + + {intl.formatMessage(tabMessage, { + name: , + })} + + +
+ {collections.length > 0 ? ( + <> + {status === 'idle' && ( +
+

+ +

+ {showCreateButton && } +
+ )} + + {!canCreateMoreCollections && ( + + )} + {collections.map((item, index) => ( + + ))} + + + ) : ( + + } + message={ + + } + > + + )} - - {collections.map((item, index) => ( - - ))} -
diff --git a/app/javascript/flavours/glitch/features/collections/styles.module.scss b/app/javascript/flavours/glitch/features/collections/styles.module.scss new file mode 100644 index 0000000000..9ded10dcc2 --- /dev/null +++ b/app/javascript/flavours/glitch/features/collections/styles.module.scss @@ -0,0 +1,35 @@ +.header { + display: flex; + flex-direction: column; + gap: 8px; + padding-top: 24px; + padding-inline: 16px; + border-bottom: 1px solid var(--color-border-primary); +} + +.heading { + font-size: 22px; + font-weight: 500; + line-height: 1.2; + overflow-wrap: break-word; +} + +.listHeader { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 28px; + padding-top: 16px; + padding-inline: 16px; +} + +.subHeading { + font-size: 15px; + font-weight: 500; + line-height: 1.2; + overflow-wrap: break-word; +} + +.maxCollectionsError { + margin: 8px 16px; +} diff --git a/app/javascript/flavours/glitch/utils/has_react_children.ts b/app/javascript/flavours/glitch/utils/has_react_children.ts new file mode 100644 index 0000000000..f02762bace --- /dev/null +++ b/app/javascript/flavours/glitch/utils/has_react_children.ts @@ -0,0 +1,12 @@ +import { Children } from 'react'; +import type { ReactNode } from 'react'; + +export function hasReactChildren(children: ReactNode): boolean { + if (!children) { + return false; + } + + const childrenCount = Children.toArray(children).filter(Boolean).length; + + return childrenCount > 0; +}