From 04b3f0fc32d1e2312b6ef0018a69632d689aac92 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Fri, 10 Apr 2026 14:11:11 +0200 Subject: [PATCH] [Glitch] Adds collection notification UI Port df4b4f1620413e279f1be69177ed8029396bff9f to glitch-soc Signed-off-by: Claire --- .../form_fields/combobox.module.scss | 6 +- .../form_fields/copy_link_field.module.scss | 6 +- .../collection_list_item.module.scss | 61 ---------- .../components/collection_list_item.tsx | 107 ++-------------- .../components/collection_lockup.module.scss | 62 ++++++++++ .../components/collection_lockup.tsx | 115 ++++++++++++++++++ .../collection_preview_card.module.scss | 8 ++ .../components/collection_preview_card.tsx | 20 +++ .../components/share_modal.module.scss | 20 --- .../collections/components/share_modal.tsx | 20 +-- .../components/notification_collection.tsx | 69 +++++++++++ .../components/notification_group.tsx | 10 ++ 12 files changed, 302 insertions(+), 202 deletions(-) create mode 100644 app/javascript/flavours/glitch/features/collections/components/collection_lockup.module.scss create mode 100644 app/javascript/flavours/glitch/features/collections/components/collection_lockup.tsx create mode 100644 app/javascript/flavours/glitch/features/collections/components/collection_preview_card.module.scss create mode 100644 app/javascript/flavours/glitch/features/collections/components/collection_preview_card.tsx create mode 100644 app/javascript/flavours/glitch/features/notifications_v2/components/notification_collection.tsx diff --git a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss index 7947b698a5..732893200c 100644 --- a/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss +++ b/app/javascript/flavours/glitch/components/form_fields/combobox.module.scss @@ -8,9 +8,9 @@ .menuButton { position: absolute; - inset-inline-end: 0; - top: 0; - padding: 9px; + inset-inline-end: 1px; + top: 1px; + padding: 7px; &::before { // Subtle divider line separating the button from the input field diff --git a/app/javascript/flavours/glitch/components/form_fields/copy_link_field.module.scss b/app/javascript/flavours/glitch/components/form_fields/copy_link_field.module.scss index 06834e9d91..68ee85c27c 100644 --- a/app/javascript/flavours/glitch/components/form_fields/copy_link_field.module.scss +++ b/app/javascript/flavours/glitch/components/form_fields/copy_link_field.module.scss @@ -8,7 +8,7 @@ .copyButton { position: absolute; - inset-inline-end: 0; - top: 0; - padding: 9px; + inset-inline-end: 1px; + top: 1px; + padding: 7px; } 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 eaee216e6a..4fdd129a05 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 @@ -9,67 +9,6 @@ } } -.content { - position: relative; - flex-grow: 1; - display: flex; - align-items: center; - column-gap: 12px; -} - -.avatarGrid { - position: relative; - display: grid; - grid-template-columns: repeat(2, min-content); - gap: 2px; - - &.avatarGridSensitive { - .avatar { - filter: blur(4px); - } - } -} - -.avatar { - background: var(--color-bg-brand-softest); -} - -.avatarSensitiveBadge { - position: absolute; - inset: 0; - margin: auto; - padding: 3px; - width: 18px; - height: 18px; - border-radius: 8px; - fill: var(--color-text-primary); - background: var(--color-bg-warning-softest); -} - -.link { - display: block; - font-size: 15px; - font-weight: 500; - text-decoration: none; - color: var(--color-text-primary); - - &:hover { - color: var(--color-text-brand); - } - - &::after { - // Increase clickable area by extending link across parent - content: ''; - position: absolute; - inset: 0; - } -} - -.info { - font-size: 13px; - color: var(--color-text-secondary); -} - .menuButton { padding: 4px; margin-top: -2px; diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_list_item.tsx b/app/javascript/flavours/glitch/features/collections/components/collection_list_item.tsx index cb9a875900..8f0573d6f2 100644 --- a/app/javascript/flavours/glitch/features/collections/components/collection_list_item.tsx +++ b/app/javascript/flavours/glitch/features/collections/components/collection_list_item.tsx @@ -1,69 +1,30 @@ import { useId } from 'react'; -import { FormattedMessage } from 'react-intl'; - import classNames from 'classnames'; -import { Link } from 'react-router-dom'; -import { CollectionMenu } from '@/flavours/glitch/features/collections/components/collection_menu'; -import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; -import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; -import { AvatarById } from 'flavours/glitch/components/avatar'; -import { useAccountHandle } from 'flavours/glitch/components/display_name/default'; -import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { Article } from 'flavours/glitch/components/scrollable_list/components'; -import { useAccount } from 'flavours/glitch/hooks/useAccount'; -import { domain } from 'flavours/glitch/initial_state'; +import type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup'; +import { CollectionLockup } from 'flavours/glitch/features/collections/components/collection_lockup'; +import { CollectionMenu } from 'flavours/glitch/features/collections/components/collection_menu'; import classes from './collection_list_item.module.scss'; -export const AvatarGrid: React.FC<{ - accountIds: (string | undefined)[]; - sensitive?: boolean; -}> = ({ accountIds: ids, sensitive }) => { - const avatarIds = [ids[0], ids[1], ids[2], ids[3]]; - return ( -
- {avatarIds.map((id) => ( - - ))} - {sensitive && } -
- ); -}; - -export const CollectionListItem: React.FC<{ - collection: ApiCollectionJSON; +interface CollectionListItemProps extends CollectionLockupProps { withoutBorder?: boolean; - withAuthorHandle?: boolean; - withTimestamp?: boolean; positionInList: number; listSize: number; -}> = ({ +} + +export const CollectionListItem: React.FC = ({ collection, withoutBorder, - withAuthorHandle = true, - withTimestamp, positionInList, listSize, + ...otherProps }) => { - const { id, name } = collection; const uniqueId = useId(); const linkId = `${uniqueId}-link`; const infoId = `${uniqueId}-info`; - const authorAccount = useAccount(collection.account_id); - const authorHandle = useAccountHandle(authorAccount, domain); return (
-
- item.account_id)} - sensitive={collection.sensitive} - /> -
-

- - {name} - -

-
    - {collection.sensitive && ( -
  • - -
  • - )} - {withAuthorHandle && authorAccount && ( - - )} - - {withTimestamp && ( - - ), - }} - tagName='li' - /> - )} -
-
-
+ = ({ accountIds: ids, sensitive }) => { + const avatarIds = [ids[0], ids[1], ids[2], ids[3]]; + return ( +
+ {avatarIds.map((id) => ( + + ))} + {sensitive && } +
+ ); +}; + +export interface CollectionLockupProps { + collection: ApiCollectionJSON; + withAuthorHandle?: boolean; + withTimestamp?: boolean; +} + +export const CollectionLockup: React.FC = ({ + collection, + withAuthorHandle = true, + withTimestamp, +}) => { + const { id, name } = collection; + const uniqueId = useId(); + const linkId = `${uniqueId}-link`; + const infoId = `${uniqueId}-info`; + const authorAccount = useAccount(collection.account_id); + const authorHandle = useAccountHandle(authorAccount, domain); + + return ( +
+ item.account_id)} + sensitive={collection.sensitive} + /> +
+

+ + {name} + +

+
    + {collection.sensitive && ( +
  • + +
  • + )} + {withAuthorHandle && authorAccount && ( + + )} + + {withTimestamp && ( + + ), + }} + tagName='li' + /> + )} +
+
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.module.scss b/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.module.scss new file mode 100644 index 0000000000..16b883b72b --- /dev/null +++ b/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.module.scss @@ -0,0 +1,8 @@ +.wrapper { + display: flex; + align-items: start; + padding: 12px; + gap: 12px; + border-radius: 12px; + border: 1px solid var(--color-border-primary); +} diff --git a/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.tsx b/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.tsx new file mode 100644 index 0000000000..47899cc510 --- /dev/null +++ b/app/javascript/flavours/glitch/features/collections/components/collection_preview_card.tsx @@ -0,0 +1,20 @@ +import type { CollectionLockupProps } from 'flavours/glitch/features/collections/components/collection_lockup'; +import { CollectionLockup } from 'flavours/glitch/features/collections/components/collection_lockup'; + +import classes from './collection_preview_card.module.scss'; + +interface CollectionPreviewCardProps extends CollectionLockupProps { + onRemove?: () => void; +} + +export const CollectionPreviewCard: React.FC = ({ + collection, + onRemove, + ...otherProps +}) => { + return ( +
+ +
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/collections/components/share_modal.module.scss b/app/javascript/flavours/glitch/features/collections/components/share_modal.module.scss index 2344ea519e..de10bb63c0 100644 --- a/app/javascript/flavours/glitch/features/collections/components/share_modal.module.scss +++ b/app/javascript/flavours/glitch/features/collections/components/share_modal.module.scss @@ -5,27 +5,7 @@ } .preview { - display: flex; - flex-wrap: wrap-reverse; - align-items: start; - justify-content: space-between; - gap: 8px; - padding: 16px; margin-bottom: 16px; - border-radius: 8px; - color: var(--color-text-primary); - background: linear-gradient( - 145deg, - var(--color-bg-brand-soft), - var(--color-bg-primary) - ); - border: 1px solid var(--color-bg-brand-base); -} - -.previewHeading { - font-size: 22px; - line-height: 1.3; - margin-bottom: 4px; } .actions { diff --git a/app/javascript/flavours/glitch/features/collections/components/share_modal.tsx b/app/javascript/flavours/glitch/features/collections/components/share_modal.tsx index 17caa1207a..91c3a45e8b 100644 --- a/app/javascript/flavours/glitch/features/collections/components/share_modal.tsx +++ b/app/javascript/flavours/glitch/features/collections/components/share_modal.tsx @@ -8,8 +8,6 @@ import { me } from '@/flavours/glitch/initial_state'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { changeCompose, focusCompose } from 'flavours/glitch/actions/compose'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; -import { AvatarById } from 'flavours/glitch/components/avatar'; -import { AvatarGroup } from 'flavours/glitch/components/avatar_group'; import { Button } from 'flavours/glitch/components/button'; import { CopyLinkField } from 'flavours/glitch/components/form_fields'; import { IconButton } from 'flavours/glitch/components/icon_button'; @@ -20,8 +18,7 @@ import { } from 'flavours/glitch/components/modal_shell'; import { useAppDispatch } from 'flavours/glitch/store'; -import { AuthorNote } from '../detail'; - +import { CollectionPreviewCard } from './collection_preview_card'; import classes from './share_modal.module.scss'; const messages = defineMessages({ @@ -96,24 +93,13 @@ export const CollectionShareModal: React.FC<{ />
-
-

{collection.name}

- -
- - {collection.items.slice(0, 5).map(({ account_id }) => { - if (!account_id) return; - return ( - - ); - })} - +
diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_collection.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_collection.tsx new file mode 100644 index 0000000000..e9427dccc6 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_collection.tsx @@ -0,0 +1,69 @@ +import { FormattedMessage } from 'react-intl'; + +import classNames from 'classnames'; + +import { DisplayNameSimple } from '@/flavours/glitch/components/display_name/simple'; +import { Icon } from '@/flavours/glitch/components/icon'; +import { useAccount } from '@/flavours/glitch/hooks/useAccount'; +import CollectionsFilledIcon from '@/material-icons/400-24px/category-fill.svg?react'; +import type { + NotificationGroupAddedToCollection, + NotificationGroupCollectionUpdate, +} from 'flavours/glitch/models/notification_group'; + +import { CollectionPreviewCard } from '../../collections/components/collection_preview_card'; + +export const NotificationCollection: React.FC<{ + notification: + | NotificationGroupAddedToCollection + | NotificationGroupCollectionUpdate; + unread: boolean; +}> = ({ notification, unread }) => { + const { collection, type } = notification; + const collectionCreatorAccount = useAccount(collection.account_id); + + return ( +
+
+ +
+ +
+
+
+ {type === 'added_to_collection' && ( + + ), + }} + /> + )} + {type === 'collection_update' && ( + + ), + }} + /> + )} +
+
+ + +
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group.tsx index 5382287935..ec0d63e259 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group.tsx @@ -9,6 +9,7 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; import { NotificationAdminReport } from './notification_admin_report'; import { NotificationAdminSignUp } from './notification_admin_sign_up'; import { NotificationAnnualReport } from './notification_annual_report'; +import { NotificationCollection } from './notification_collection'; import { NotificationFavourite } from './notification_favourite'; import { NotificationFollow } from './notification_follow'; import { NotificationFollowRequest } from './notification_follow_request'; @@ -156,6 +157,15 @@ export const NotificationGroup: React.FC<{ /> ); break; + case 'added_to_collection': + case 'collection_update': + content = ( + + ); + break; default: return null; }