fix(newsfeed): tighten card spacing and reposition pinned icon (#818)
* fix(newsfeed): tighten card spacing and reposition pinned icon - Move pinned icon to top-left corner with a tilted rotation - Remove the extra pin row that added excess top padding - Reduce spacing between post content, reactions, and comments - Remove card box shadow Co-authored-by: Rotimi Best <[email protected]> * fix(newsfeed): place pinned icon on card corner instead of avatar Co-authored-by: Rotimi Best <[email protected]> * fix(newsfeed): stop dropdown menus locking body scroll bits-ui defaults DropdownMenu.Content's preventScroll to true, so every open ran a body scroll lock: a forced full-document style recalc and reflow, a horizontal shift by the scrollbar width, and pointer-events: none on the body. On the newsfeed page — a long list of cards each with a scrollable comment thread — that made the comment ellipsis menu feel sluggish to open. Menus don't need scroll locking; only modal layers do. Also tighten card spacing, use the primary color for the pinned icon, give the card menu trigger a pointer cursor, and switch the comment save and submit buttons to the loading and secondary variants. * refactor(newsfeed): store reply targets as data, not markup Replies used to prepend a presentational <blockquote> — Tailwind classes, hardcoded English and a copied snippet of the parent — onto the comment body, and that whole string was persisted to course_newsfeed_comment.content. Editing a reply therefore showed raw markup in the textarea, the quoted snippet froze at write time, the label could never be localized, and two of its ui: utilities never compiled because they appeared only in dashboard code. Add a replyToCommentId column, distinct from parentId (the thread root), so a reply can name the comment it answers. The service verifies the target belongs to the same feed and the same thread; the paginated query joins it to return replyToAuthorFullname, so the label is rendered from live data by the new CommentTree.ReplyingTo component using the existing replying_to key. Rows written the old way still display their stored quote, and stripLegacyReplyQuote keeps that markup out of the edit textarea. * refactor(newsfeed): drop redundant comments on reply target plumbing * fix(newsfeed): address review feedback on pinned icon and quote stripping - The pin icon carried an unprefixed `text-muted-foreground` plus `ui:stroke-primary`, which is emitted nowhere because it appears only in dashboard code, so the outline fell back to the muted colour. Use `ui:text-primary` and let the stroke inherit currentColor. - Give the pinned state an accessible name: the icon is `aria-hidden`, and header.svelte no longer carries an indicator, so screen readers had nothing. - Filter feeds on `isPinned` alone. `is_pinned` is only the database column name; drizzle maps it to `isPinned` and the API never returns the snake_case form, so the `as any` fallback was dead and let the page and card disagree. - `ui:transition-color` is not a Tailwind utility and emitted nothing; the plural `ui:transition-colors` is what applies the transition. - Strip a legacy reply quote only when the blockquote carries the `reply-quote` class the old code always wrote, so a blockquote the author typed is kept. * docs(agents): forbid presentational markup in persisted content columns ---------
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
// bits-ui defaults this to true, which locks body scroll on every open:
|
||||
// a forced full-document style recalc + reflow, plus pointer-events: none.
|
||||
// A menu doesn't need it; only modal layers do.
|
||||
preventScroll = false,
|
||||
class: className,
|
||||
...restProps
|
||||
}: DropdownMenuPrimitive.ContentProps & {
|
||||
@@ -18,6 +22,7 @@
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-content"
|
||||
{sideOffset}
|
||||
{preventScroll}
|
||||
class={cn(
|
||||
'ui:bg-popover ui:text-popover-foreground ui:data-[state=open]:animate-in ui:data-[state=closed]:animate-out ui:data-[state=closed]:fade-out-0 ui:data-[state=open]:fade-in-0 ui:data-[state=closed]:zoom-out-95 ui:data-[state=open]:zoom-in-95 ui:data-[side=bottom]:slide-in-from-top-2 ui:data-[side=left]:slide-in-from-right-2 ui:data-[side=right]:slide-in-from-left-2 ui:data-[side=top]:slide-in-from-bottom-2 ui:max-h-(--bits-dropdown-menu-content-available-height) ui:origin-(--bits-dropdown-menu-content-transform-origin) ui:z-50 ui:min-w-32 ui:overflow-y-auto ui:overflow-x-hidden ui:rounded-md ui:border ui:p-1 ui:shadow-md ui:outline-none',
|
||||
className
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<UserAvatar src={authorAvatarUrl || undefined} class="ui:mt-1 ui:size-8 ui:shrink-0" />
|
||||
|
||||
<div
|
||||
class="ui:flex ui:w-full ui:flex-col ui:overflow-hidden ui:rounded-lg ui:border ui:border-input ui:shadow-xs ui:transition-[color,box-shadow] ui:focus-within:border-ring ui:focus-within:ring-[3px] ui:focus-within:ring-ring/50"
|
||||
class="ui:flex ui:w-full ui:flex-col ui:overflow-hidden ui:rounded-lg ui:border ui:border-input ui:transition-colors ui:focus-within:border-ring ui:focus-within:ring-[3px] ui:focus-within:ring-ring/50"
|
||||
>
|
||||
{#if replyingToUser}
|
||||
<div
|
||||
@@ -93,7 +93,7 @@
|
||||
/>
|
||||
<div class="ui:flex ui:items-center ui:justify-end ui:px-2 ui:pb-2">
|
||||
<Button
|
||||
variant={value.trim() ? 'default' : 'ghost'}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
class="ui:h-8 ui:gap-1.5 ui:rounded-md"
|
||||
loading={isSubmitting}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<script lang="ts">
|
||||
import CornerDownRightIcon from '@lucide/svelte/icons/corner-down-right';
|
||||
|
||||
interface Props {
|
||||
label: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { label, class: className = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<p class="ui:flex ui:items-center ui:gap-1 ui:text-xs ui:text-muted-foreground {className}">
|
||||
<CornerDownRightIcon class="ui:size-3 ui:shrink-0" aria-hidden="true" />
|
||||
<span class="ui:truncate">{label}</span>
|
||||
</p>
|
||||
@@ -5,6 +5,7 @@ import Content from './comment-tree-content.svelte';
|
||||
import Actions from './comment-tree-actions.svelte';
|
||||
import Input from './comment-tree-input.svelte';
|
||||
import Replies from './comment-tree-replies.svelte';
|
||||
import ReplyingTo from './comment-tree-replying-to.svelte';
|
||||
|
||||
export * from './types';
|
||||
|
||||
@@ -16,6 +17,7 @@ export {
|
||||
Actions,
|
||||
Input,
|
||||
Replies,
|
||||
ReplyingTo,
|
||||
// Alias names
|
||||
Root as CommentRoot,
|
||||
Item as CommentItem,
|
||||
@@ -23,5 +25,6 @@ export {
|
||||
Content as CommentContent,
|
||||
Actions as CommentActions,
|
||||
Input as CommentInput,
|
||||
Replies as CommentReplies
|
||||
Replies as CommentReplies,
|
||||
ReplyingTo as CommentReplyingTo
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user