mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Fix full-width message layouts (#591)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -401,7 +401,7 @@ export const ChannelPane = React.memo(function ChannelPane({
|
||||
showTopBorder={false}
|
||||
/>
|
||||
<div className="h-7 bg-background px-4 pb-1 pt-0 sm:px-6 -mt-1">
|
||||
<div className="mx-auto flex h-full w-full max-w-4xl items-center gap-2">
|
||||
<div className="flex h-full w-full items-center gap-2">
|
||||
{hasComposerBotActivity ? (
|
||||
<div className="shrink-0">
|
||||
<BotActivityComposerAction
|
||||
|
||||
@@ -277,7 +277,7 @@ export function InboxDetailPane({
|
||||
<div className="px-6 py-2">
|
||||
<article
|
||||
className={cn(
|
||||
"group/message flex items-start gap-2.5 px-2 py-1 transition-colors duration-1000",
|
||||
"group/message relative flex items-start gap-2.5 px-2 py-1 transition-colors duration-1000",
|
||||
message.isSelected
|
||||
? cn(
|
||||
isFocusHighlightVisible
|
||||
@@ -292,6 +292,39 @@ export function InboxDetailPane({
|
||||
: "home-inbox-context-message"
|
||||
}
|
||||
>
|
||||
{canReply || onToggleReaction ? (
|
||||
<div className="absolute right-2 top-1 z-10">
|
||||
<MessageActionBar
|
||||
activeReplyTargetId={replyTargetId}
|
||||
message={toActionBarMessage(message)}
|
||||
onReactionSelect={
|
||||
onToggleReaction
|
||||
? (emoji) => {
|
||||
const actionBarMessage =
|
||||
toActionBarMessage(message);
|
||||
const remove =
|
||||
actionBarMessage.reactions?.some(
|
||||
(reaction) =>
|
||||
reaction.emoji === emoji &&
|
||||
reaction.reactedByCurrentUser,
|
||||
) ?? false;
|
||||
return onToggleReaction(
|
||||
actionBarMessage,
|
||||
emoji,
|
||||
remove,
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onReply={
|
||||
canReply
|
||||
? () => handleSelectReplyTarget(message)
|
||||
: undefined
|
||||
}
|
||||
reactions={message.reactions ?? []}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<UserAvatar
|
||||
avatarUrl={message.avatarUrl}
|
||||
className="h-8 w-8 shrink-0 rounded-xl"
|
||||
@@ -299,53 +332,18 @@ export function InboxDetailPane({
|
||||
size="md"
|
||||
/>
|
||||
<div className="-mt-1 min-w-0 flex-1">
|
||||
<div className="flex min-w-0 flex-wrap items-start gap-x-2 gap-y-0">
|
||||
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0">
|
||||
<p className="truncate text-sm font-semibold leading-none tracking-tight text-foreground">
|
||||
{message.authorLabel}
|
||||
</p>
|
||||
<p className="shrink-0 text-xs font-normal leading-none tabular-nums text-muted-foreground/55">
|
||||
{message.fullTimestampLabel}
|
||||
</p>
|
||||
{message.isSelected ? (
|
||||
<span className="text-[10px] font-semibold uppercase leading-none tracking-[0.14em] text-muted-foreground/70">
|
||||
Inbox item
|
||||
</span>
|
||||
) : null}
|
||||
<p className="ml-auto text-xs text-muted-foreground">
|
||||
{message.fullTimestampLabel}
|
||||
</p>
|
||||
{canReply || onToggleReaction ? (
|
||||
<div className="relative ml-1">
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2">
|
||||
<MessageActionBar
|
||||
activeReplyTargetId={replyTargetId}
|
||||
message={toActionBarMessage(message)}
|
||||
onReactionSelect={
|
||||
onToggleReaction
|
||||
? (emoji) => {
|
||||
const actionBarMessage =
|
||||
toActionBarMessage(message);
|
||||
const remove =
|
||||
actionBarMessage.reactions?.some(
|
||||
(reaction) =>
|
||||
reaction.emoji === emoji &&
|
||||
reaction.reactedByCurrentUser,
|
||||
) ?? false;
|
||||
return onToggleReaction(
|
||||
actionBarMessage,
|
||||
emoji,
|
||||
remove,
|
||||
);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onReply={
|
||||
canReply
|
||||
? () => handleSelectReplyTarget(message)
|
||||
: undefined
|
||||
}
|
||||
reactions={message.reactions ?? []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<Markdown
|
||||
|
||||
@@ -2,7 +2,7 @@ export function DayDivider({ label }: { label: string }) {
|
||||
return (
|
||||
<section
|
||||
aria-label={label}
|
||||
className="sticky top-11 z-20 flex justify-center py-1"
|
||||
className="sticky top-11 z-[5] flex justify-center py-1"
|
||||
data-testid="message-timeline-day-divider"
|
||||
data-day-label={label}
|
||||
>
|
||||
|
||||
@@ -559,7 +559,7 @@ export function MessageComposer({
|
||||
aria-hidden="true"
|
||||
className="absolute inset-x-0 bottom-0 h-5 bg-background"
|
||||
/>
|
||||
<div className="relative mx-auto flex w-full max-w-4xl flex-col gap-3">
|
||||
<div className="relative flex w-full flex-col gap-3">
|
||||
<form
|
||||
className="relative isolate rounded-2xl border border-border/50 bg-background/70 px-3 pb-2 pt-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
|
||||
data-testid="message-composer"
|
||||
|
||||
@@ -178,26 +178,28 @@ export const MessageRow = React.memo(
|
||||
</h3>
|
||||
);
|
||||
|
||||
const metadataNode = (
|
||||
<div className="ml-auto flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<div className="relative">
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2">
|
||||
<MessageActionBar
|
||||
activeReplyTargetId={activeReplyTargetId}
|
||||
message={message}
|
||||
onDelete={onDelete}
|
||||
onEdit={onEdit}
|
||||
onMarkUnread={onMarkUnread}
|
||||
onReactionSelect={
|
||||
canToggleReactions ? handleReactionSelect : undefined
|
||||
}
|
||||
onReply={onReply}
|
||||
reactionErrorMessage={reactionErrorMessage}
|
||||
reactionPending={reactionPending}
|
||||
reactions={reactions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
const actionBarNode = (
|
||||
<div className="absolute right-2 top-1 z-10">
|
||||
<MessageActionBar
|
||||
activeReplyTargetId={activeReplyTargetId}
|
||||
message={message}
|
||||
onDelete={onDelete}
|
||||
onEdit={onEdit}
|
||||
onMarkUnread={onMarkUnread}
|
||||
onReactionSelect={
|
||||
canToggleReactions ? handleReactionSelect : undefined
|
||||
}
|
||||
onReply={onReply}
|
||||
reactionErrorMessage={reactionErrorMessage}
|
||||
reactionPending={reactionPending}
|
||||
reactions={reactions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const inlineMetadataNode = (
|
||||
<div className="flex shrink-0 items-baseline gap-2 text-xs">
|
||||
<MessageTimestamp createdAt={message.createdAt} time={message.time} />
|
||||
{message.pending ? (
|
||||
<p className="font-medium uppercase tracking-[0.14em] text-primary/80">
|
||||
Sending
|
||||
@@ -211,7 +213,6 @@ export const MessageRow = React.memo(
|
||||
<TooltipContent>This message has been edited</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
<MessageTimestamp createdAt={message.createdAt} time={message.time} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -281,7 +282,7 @@ export const MessageRow = React.memo(
|
||||
|
||||
<article
|
||||
className={cn(
|
||||
"group/message rounded-2xl px-2 py-1 transition-colors",
|
||||
"group/message relative rounded-2xl px-2 py-1 transition-colors",
|
||||
isThreadReplyLayout ? "space-y-1" : "flex items-start gap-2.5",
|
||||
highlighted ? "bg-primary/10 ring-1 ring-primary/30" : "",
|
||||
)}
|
||||
@@ -290,7 +291,7 @@ export const MessageRow = React.memo(
|
||||
>
|
||||
{isThreadReplyLayout ? (
|
||||
<>
|
||||
<div className="flex min-w-0 items-start gap-1.5">
|
||||
<div className="flex min-w-0 items-baseline gap-1.5">
|
||||
{message.pubkey ? (
|
||||
<UserProfilePopover
|
||||
pubkey={message.pubkey}
|
||||
@@ -298,7 +299,10 @@ export const MessageRow = React.memo(
|
||||
botIdenticonValue={message.author}
|
||||
>
|
||||
<button
|
||||
className="flex shrink-0 items-start gap-1.5 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
className={cn(
|
||||
"flex shrink-0 items-start gap-1.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
avatarButtonRadiusClass,
|
||||
)}
|
||||
type="button"
|
||||
>
|
||||
{avatarNode}
|
||||
@@ -313,16 +317,14 @@ export const MessageRow = React.memo(
|
||||
{authorNode}
|
||||
</>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex min-w-0 flex-wrap items-start gap-x-2 gap-y-0.5">
|
||||
{message.personaDisplayName &&
|
||||
message.personaDisplayName !== message.author ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{message.personaDisplayName}
|
||||
</span>
|
||||
) : null}
|
||||
{metadataNode}
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
||||
{inlineMetadataNode}
|
||||
{message.personaDisplayName &&
|
||||
message.personaDisplayName !== message.author ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{message.personaDisplayName}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-0.5">{messageBodyNode}</div>
|
||||
@@ -349,7 +351,7 @@ export const MessageRow = React.memo(
|
||||
<div className="flex shrink-0 items-start">{avatarNode}</div>
|
||||
)}
|
||||
<div className="-mt-1 min-w-0 flex-1 space-y-0">
|
||||
<div className="flex min-w-0 flex-wrap items-start gap-x-2 gap-y-0">
|
||||
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0">
|
||||
{message.pubkey ? (
|
||||
<UserProfilePopover
|
||||
pubkey={message.pubkey}
|
||||
@@ -366,18 +368,19 @@ export const MessageRow = React.memo(
|
||||
) : (
|
||||
authorNode
|
||||
)}
|
||||
{inlineMetadataNode}
|
||||
{message.personaDisplayName &&
|
||||
message.personaDisplayName !== message.author ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{message.personaDisplayName}
|
||||
</span>
|
||||
) : null}
|
||||
{metadataNode}
|
||||
</div>
|
||||
<div className="-mt-0.5">{messageBodyNode}</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{actionBarNode}
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
<div
|
||||
className="mx-auto flex w-full max-w-4xl flex-col gap-2 pb-10 pt-16"
|
||||
className="flex w-full flex-col gap-2 pb-10 pt-16"
|
||||
ref={contentRef}
|
||||
>
|
||||
<div ref={topSentinelRef} aria-hidden className="h-px" />
|
||||
|
||||
@@ -11,7 +11,9 @@ export function MessageTimestamp({
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<p className="cursor-default whitespace-nowrap">{time}</p>
|
||||
<p className="shrink-0 cursor-default whitespace-nowrap text-xs font-normal leading-none tabular-nums text-muted-foreground/55">
|
||||
{time}
|
||||
</p>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{formatFullDateTime(createdAt)}
|
||||
|
||||
@@ -200,7 +200,7 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group/message rounded-2xl px-2 py-1 transition-colors"
|
||||
className="group/message relative rounded-2xl px-2 py-1 transition-colors"
|
||||
data-testid="system-message-row"
|
||||
>
|
||||
<div className="flex items-start gap-2.5">
|
||||
@@ -211,9 +211,15 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({
|
||||
testId="system-message-avatar"
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-semibold leading-none tracking-tight text-foreground/90">
|
||||
{description.title}
|
||||
</p>
|
||||
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
||||
<p className="truncate text-sm font-semibold leading-none tracking-tight text-foreground/90">
|
||||
{description.title}
|
||||
</p>
|
||||
<MessageTimestamp
|
||||
createdAt={message.createdAt}
|
||||
time={message.time}
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-1 text-sm leading-snug text-muted-foreground/70">
|
||||
{description.action}
|
||||
</p>
|
||||
@@ -235,86 +241,77 @@ export const SystemMessageRow = React.memo(function SystemMessageRow({
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-1 text-xs text-muted-foreground/60">
|
||||
<div className="relative">
|
||||
<div className="absolute right-0 top-1/2 -translate-y-1/2">
|
||||
{canToggleReactions ? (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-full border border-border/70 bg-background/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-background/85 transition-all duration-150 ease-out",
|
||||
"max-w-0 border-0 shadow-none translate-y-1 opacity-0",
|
||||
"group-hover/message:max-w-9 group-hover/message:border group-hover/message:border-border/70 group-hover/message:shadow-sm group-hover/message:translate-y-0 group-hover/message:opacity-100",
|
||||
"group-focus-within/message:max-w-9 group-focus-within/message:border group-focus-within/message:border-border/70 group-focus-within/message:shadow-sm group-focus-within/message:translate-y-0 group-focus-within/message:opacity-100",
|
||||
isReactionPickerOpen
|
||||
? "max-w-9 border border-border/70 shadow-sm translate-y-0 opacity-100"
|
||||
: "",
|
||||
)}
|
||||
<div className="absolute right-2 top-1 z-10">
|
||||
{canToggleReactions ? (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-hidden rounded-full border border-border/70 bg-background/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-background/85 transition-all duration-150 ease-out",
|
||||
"max-w-0 border-0 shadow-none translate-y-1 opacity-0",
|
||||
"group-hover/message:max-w-9 group-hover/message:border group-hover/message:border-border/70 group-hover/message:shadow-sm group-hover/message:translate-y-0 group-hover/message:opacity-100",
|
||||
"group-focus-within/message:max-w-9 group-focus-within/message:border group-focus-within/message:border-border/70 group-focus-within/message:shadow-sm group-focus-within/message:translate-y-0 group-focus-within/message:opacity-100",
|
||||
isReactionPickerOpen
|
||||
? "max-w-9 border border-border/70 shadow-sm translate-y-0 opacity-100"
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1 p-1">
|
||||
<Popover
|
||||
onOpenChange={setIsReactionPickerOpen}
|
||||
open={isReactionPickerOpen}
|
||||
>
|
||||
<div className="flex items-center gap-1 p-1">
|
||||
<Popover
|
||||
onOpenChange={setIsReactionPickerOpen}
|
||||
open={isReactionPickerOpen}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open reactions"
|
||||
className="h-6 w-6 rounded-full p-0"
|
||||
disabled={reactionPending}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant={
|
||||
isReactionPickerOpen ? "secondary" : "ghost"
|
||||
}
|
||||
>
|
||||
{reactionPending ? (
|
||||
<Spinner className="h-3 w-3" />
|
||||
) : (
|
||||
<SmilePlus className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>React</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="w-auto p-0 rounded-2xl overflow-hidden border-0 bg-transparent shadow-none"
|
||||
side="top"
|
||||
sideOffset={10}
|
||||
>
|
||||
{reactionErrorMessage ? (
|
||||
<div className="px-3 pt-3 pb-0">
|
||||
<p className="text-xs text-destructive">
|
||||
{reactionErrorMessage}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<Picker
|
||||
data={data}
|
||||
onEmojiSelect={(emoji: { native: string }) => {
|
||||
void handleReactionSelect(emoji.native).finally(
|
||||
() => {
|
||||
setIsReactionPickerOpen(false);
|
||||
},
|
||||
);
|
||||
}}
|
||||
theme="auto"
|
||||
previewPosition="none"
|
||||
skinTonePosition="search"
|
||||
set="native"
|
||||
maxFrequentRows={2}
|
||||
perLine={8}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
aria-label="Open reactions"
|
||||
className="h-6 w-6 rounded-full p-0"
|
||||
disabled={reactionPending}
|
||||
size="sm"
|
||||
type="button"
|
||||
variant={isReactionPickerOpen ? "secondary" : "ghost"}
|
||||
>
|
||||
{reactionPending ? (
|
||||
<Spinner className="h-3 w-3" />
|
||||
) : (
|
||||
<SmilePlus className="h-3 w-3" />
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>React</TooltipContent>
|
||||
</Tooltip>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="w-auto p-0 rounded-2xl overflow-hidden border-0 bg-transparent shadow-none"
|
||||
side="top"
|
||||
sideOffset={10}
|
||||
>
|
||||
{reactionErrorMessage ? (
|
||||
<div className="px-3 pt-3 pb-0">
|
||||
<p className="text-xs text-destructive">
|
||||
{reactionErrorMessage}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
<Picker
|
||||
data={data}
|
||||
onEmojiSelect={(emoji: { native: string }) => {
|
||||
void handleReactionSelect(emoji.native).finally(() => {
|
||||
setIsReactionPickerOpen(false);
|
||||
});
|
||||
}}
|
||||
theme="auto"
|
||||
previewPosition="none"
|
||||
skinTonePosition="search"
|
||||
set="native"
|
||||
maxFrequentRows={2}
|
||||
perLine={8}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MessageTimestamp createdAt={message.createdAt} time={message.time} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,16 @@ export function TimelineSkeleton() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{skeletonRows.map((row) => (
|
||||
{skeletonRows.map((row, index) => (
|
||||
<div className="flex gap-2.5" key={row}>
|
||||
<Skeleton className="h-8 w-8 rounded-lg" />
|
||||
<Skeleton className="h-9 w-9 shrink-0 rounded-xl" />
|
||||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<Skeleton className="h-3.5 w-44" />
|
||||
<Skeleton className="h-4 w-full max-w-2xl" />
|
||||
<Skeleton className="h-4 w-full max-w-xl" />
|
||||
<div className="flex items-baseline gap-2">
|
||||
<Skeleton className="h-3.5 w-28" />
|
||||
<Skeleton className="h-3 w-12" />
|
||||
</div>
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className={index % 2 === 0 ? "h-4 w-4/5" : "h-4 w-2/3"} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -91,7 +91,6 @@ export function TypingIndicatorRow({
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2",
|
||||
isActivityVariant && "h-full",
|
||||
!isActivityVariant && "mx-auto max-w-4xl",
|
||||
)}
|
||||
>
|
||||
<div className="flex shrink-0 items-center">
|
||||
|
||||
@@ -45,13 +45,16 @@ function LoadingHeaderSkeleton() {
|
||||
function MessageRowsSkeleton() {
|
||||
return (
|
||||
<>
|
||||
{["first", "second", "third", "fourth", "fifth"].map((row) => (
|
||||
{["first", "second", "third", "fourth", "fifth"].map((row, index) => (
|
||||
<div className="flex gap-3" key={row}>
|
||||
<Skeleton className="h-8 w-8 shrink-0 rounded-lg" />
|
||||
<div className="min-w-0 flex-1 space-y-2 pt-0.5">
|
||||
<Skeleton className="h-3.5 w-40 max-w-[40%]" />
|
||||
<Skeleton className="h-4 w-full max-w-3xl" />
|
||||
<Skeleton className="h-4 w-full max-w-xl" />
|
||||
<Skeleton className="h-9 w-9 shrink-0 rounded-xl" />
|
||||
<div className="min-w-0 flex-1 space-y-1 pt-0.5">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<Skeleton className="h-3.5 w-28" />
|
||||
<Skeleton className="h-3 w-12" />
|
||||
</div>
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className={index % 2 === 0 ? "h-4 w-4/5" : "h-4 w-2/3"} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -177,13 +180,13 @@ function ChannelLoadingBody() {
|
||||
return (
|
||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<div className="flex-1 overflow-y-auto px-4 py-3 sm:px-6">
|
||||
<div className="mx-auto flex w-full max-w-4xl flex-col gap-4">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<MessageRowsSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/60 bg-background px-4 py-4 sm:px-6">
|
||||
<div className="mx-auto w-full max-w-4xl space-y-3">
|
||||
<div className="w-full space-y-3">
|
||||
<Skeleton className="h-10 w-full rounded-2xl" />
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-8 w-20 rounded-lg" />
|
||||
|
||||
@@ -60,7 +60,7 @@ function CopyableUrl({ url }: { url: string }) {
|
||||
|
||||
function DetailSkeleton() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl gap-8 px-4 py-8">
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="h-5 w-24 animate-pulse rounded bg-muted" />
|
||||
<div className="mt-6 h-8 w-64 animate-pulse rounded bg-muted" />
|
||||
@@ -181,7 +181,7 @@ export function RepoDetailPage() {
|
||||
|
||||
if (!repo) {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl gap-8 px-4 py-8">
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
<div className="min-w-0 flex-1">
|
||||
<Link
|
||||
to="/"
|
||||
@@ -205,7 +205,7 @@ export function RepoDetailPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl gap-8 px-4 py-8">
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
{/* Main content */}
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Back link */}
|
||||
|
||||
@@ -101,7 +101,7 @@ export function ReposPage() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl gap-8 px-4 py-8">
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
<div className="min-w-0 flex-1">
|
||||
<h2 className="mb-4 flex items-center gap-2 text-lg font-semibold">
|
||||
<BookMarked className="h-5 w-5" /> Repositories
|
||||
@@ -122,7 +122,7 @@ export function ReposPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-7xl gap-8 px-4 py-8">
|
||||
<div className="flex w-full gap-8 px-4 py-8">
|
||||
{/* Main content */}
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Mobile-only connect button */}
|
||||
|
||||
Reference in New Issue
Block a user