Convert emoji/GIF/stickers picker from popover to centered dialog
The picker was positioned relative to the trigger button via a Popover, which felt awkward especially on smaller screens or inside other modals. Now renders as a centered Dialog with a backdrop for a more traditional modal experience.
This commit is contained in:
+119
-121
@@ -11,7 +11,7 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Dialog, DialogContent } from '@/components/ui/dialog';
|
||||
import { EmojiPicker } from '@/components/EmojiPicker';
|
||||
import { useCustomEmojis } from '@/hooks/useCustomEmojis';
|
||||
import { useFeedSettings } from '@/hooks/useFeedSettings';
|
||||
@@ -1178,132 +1178,130 @@ export function ComposeBox({
|
||||
)}
|
||||
|
||||
{/* Emoji / GIF picker */}
|
||||
<Popover open={pickerOpen} onOpenChange={setPickerOpen}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<PopoverTrigger asChild>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerOpen(true)}
|
||||
className={cn(
|
||||
'p-2 rounded-full transition-colors',
|
||||
pickerOpen
|
||||
? 'text-primary bg-primary/10'
|
||||
: 'text-muted-foreground hover:text-primary hover:bg-primary/10',
|
||||
)}
|
||||
>
|
||||
<Smile className="size-[18px]" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
{!pickerOpen && <TooltipContent>Emoji / GIF</TooltipContent>}
|
||||
</Tooltip>
|
||||
<Dialog open={pickerOpen} onOpenChange={setPickerOpen}>
|
||||
<DialogContent
|
||||
className="w-auto max-w-fit p-0 gap-0 border-border rounded-xl overflow-hidden [&>button]:hidden"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
{/* Tab bar */}
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('emoji')}
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'emoji'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<Smile className="size-3.5" />
|
||||
Emoji
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('gif')}
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'gif'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<rect x="1" y="1" width="16" height="16" rx="3" stroke="currentColor" strokeWidth="1.5" fill="none"/>
|
||||
<text x="9" y="9" textAnchor="middle" dominantBaseline="central" fontSize="7" fontWeight="700" fontFamily="system-ui,sans-serif" fill="currentColor" letterSpacing="0.5">GIF</text>
|
||||
</svg>
|
||||
GIF
|
||||
</button>
|
||||
{customEmojis.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('stickers')}
|
||||
className={cn(
|
||||
'p-2 rounded-full transition-colors',
|
||||
pickerOpen
|
||||
? 'text-primary bg-primary/10'
|
||||
: 'text-muted-foreground hover:text-primary hover:bg-primary/10',
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'stickers'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<Smile className="size-[18px]" />
|
||||
<Sticker className="size-3.5" />
|
||||
Stickers
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
</TooltipTrigger>
|
||||
{!pickerOpen && <TooltipContent>Emoji / GIF</TooltipContent>}
|
||||
</Tooltip>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
sideOffset={8}
|
||||
className="w-auto p-0 border-border"
|
||||
>
|
||||
{/* Tab bar */}
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('emoji')}
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'emoji'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<Smile className="size-3.5" />
|
||||
Emoji
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('gif')}
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'gif'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<rect x="1" y="1" width="16" height="16" rx="3" stroke="currentColor" strokeWidth="1.5" fill="none"/>
|
||||
<text x="9" y="9" textAnchor="middle" dominantBaseline="central" fontSize="7" fontWeight="700" fontFamily="system-ui,sans-serif" fill="currentColor" letterSpacing="0.5">GIF</text>
|
||||
</svg>
|
||||
GIF
|
||||
</button>
|
||||
{customEmojis.length > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPickerTab('stickers')}
|
||||
className={cn(
|
||||
'flex-1 flex items-center justify-center gap-1.5 py-2 text-xs font-medium transition-colors border-b-2',
|
||||
pickerTab === 'stickers'
|
||||
? 'text-primary border-primary'
|
||||
: 'text-muted-foreground hover:text-foreground border-border',
|
||||
)}
|
||||
>
|
||||
<Sticker className="size-3.5" />
|
||||
Stickers
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* Picker content */}
|
||||
{pickerTab === 'emoji' ? (
|
||||
<EmojiPicker
|
||||
customEmojis={customEmojis}
|
||||
onSelect={(selection) => {
|
||||
if (selection.type === 'native') {
|
||||
insertEmoji(selection.emoji);
|
||||
} else {
|
||||
insertEmoji(`:${selection.shortcode}:`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : pickerTab === 'stickers' ? (
|
||||
<div className="w-[316px] h-[435px]">
|
||||
{customEmojis.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-2">
|
||||
<Sticker className="size-8 opacity-40" />
|
||||
<p className="text-sm">No sticker packs yet</p>
|
||||
<p className="text-xs">Add emoji packs to your profile to use stickers</p>
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="grid grid-cols-4 gap-1.5 p-2">
|
||||
{customEmojis.map((emoji) => (
|
||||
<button
|
||||
key={emoji.shortcode}
|
||||
type="button"
|
||||
title={emoji.shortcode}
|
||||
onClick={() => {
|
||||
setContent((prev) => (prev ? prev + '\n' + emoji.url : emoji.url));
|
||||
setPickerOpen(false);
|
||||
expand();
|
||||
}}
|
||||
className="aspect-square rounded-lg overflow-hidden hover:bg-muted transition-colors p-1 group"
|
||||
>
|
||||
<img
|
||||
src={emoji.url}
|
||||
alt={emoji.shortcode}
|
||||
className="w-full h-full object-contain group-hover:scale-110 transition-transform duration-150"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<GifPicker onSelect={(gif) => {
|
||||
setContent((prev) => (prev ? prev + '\n' + gif.url : gif.url));
|
||||
setPickerOpen(false);
|
||||
expand();
|
||||
}} />
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
{/* Picker content */}
|
||||
{pickerTab === 'emoji' ? (
|
||||
<EmojiPicker
|
||||
customEmojis={customEmojis}
|
||||
onSelect={(selection) => {
|
||||
if (selection.type === 'native') {
|
||||
insertEmoji(selection.emoji);
|
||||
} else {
|
||||
insertEmoji(`:${selection.shortcode}:`);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : pickerTab === 'stickers' ? (
|
||||
<div className="w-[316px] h-[435px]">
|
||||
{customEmojis.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center h-full text-muted-foreground gap-2">
|
||||
<Sticker className="size-8 opacity-40" />
|
||||
<p className="text-sm">No sticker packs yet</p>
|
||||
<p className="text-xs">Add emoji packs to your profile to use stickers</p>
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="grid grid-cols-4 gap-1.5 p-2">
|
||||
{customEmojis.map((emoji) => (
|
||||
<button
|
||||
key={emoji.shortcode}
|
||||
type="button"
|
||||
title={emoji.shortcode}
|
||||
onClick={() => {
|
||||
setContent((prev) => (prev ? prev + '\n' + emoji.url : emoji.url));
|
||||
setPickerOpen(false);
|
||||
expand();
|
||||
}}
|
||||
className="aspect-square rounded-lg overflow-hidden hover:bg-muted transition-colors p-1 group"
|
||||
>
|
||||
<img
|
||||
src={emoji.url}
|
||||
alt={emoji.shortcode}
|
||||
className="w-full h-full object-contain group-hover:scale-110 transition-transform duration-150"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<GifPicker onSelect={(gif) => {
|
||||
setContent((prev) => (prev ? prev + '\n' + gif.url : gif.url));
|
||||
setPickerOpen(false);
|
||||
expand();
|
||||
}} />
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Content warning (NIP-36) */}
|
||||
<Tooltip>
|
||||
|
||||
Reference in New Issue
Block a user