Merge pull request #50 from suenyiyang/feat/optimize-clamped-content
feat: add content-clamp component to show full text when truncated
This commit is contained in:
@@ -8,6 +8,7 @@ import { MdMonitor } from "react-icons/md";
|
||||
import { RxDragHandleDots2 } from "react-icons/rx";
|
||||
import { FaFolderMinus } from "react-icons/fa6";
|
||||
import { FiMinus, FiX } from "react-icons/fi";
|
||||
import { ContentClamp } from "../ui/content-clamp";
|
||||
|
||||
export function LaunchWindow() {
|
||||
const { recording, toggleRecording } = useScreenRecorder();
|
||||
@@ -61,11 +62,6 @@ export function LaunchWindow() {
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const truncateText = (text: string, maxLength: number = 6) => {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + "...";
|
||||
};
|
||||
|
||||
const openSourceSelector = () => {
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.openSourceSelector();
|
||||
@@ -121,7 +117,7 @@ export function LaunchWindow() {
|
||||
disabled={recording}
|
||||
>
|
||||
<MdMonitor size={14} className="text-white" />
|
||||
{truncateText(selectedSource, 6)}
|
||||
<ContentClamp truncateLength={6}>{selectedSource}</ContentClamp>
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
@@ -131,7 +127,7 @@ export function LaunchWindow() {
|
||||
size="sm"
|
||||
onClick={hasSelectedSource ? toggleRecording : openSourceSelector}
|
||||
disabled={!hasSelectedSource && !recording}
|
||||
className={`gap-1 bg-transparent hover:bg-transparent px-0 flex-1 text-center text-xs ${styles.electronNoDrag}`}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-center text-xs ${styles.electronNoDrag}`}
|
||||
>
|
||||
{recording ? (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Popover, PopoverArrow, PopoverContent, PopoverTrigger } from "./popover"
|
||||
|
||||
interface ContentClampProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children: React.ReactNode
|
||||
truncateLength?: number
|
||||
}
|
||||
|
||||
function ContentClamp({
|
||||
children,
|
||||
className,
|
||||
truncateLength = 50,
|
||||
...props
|
||||
}: ContentClampProps) {
|
||||
const text = typeof children === "string" ? children : String(children ?? "")
|
||||
const isTruncated = text.length > truncateLength
|
||||
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const timeoutRef = React.useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
timeoutRef.current = null
|
||||
}
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setOpen(false)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (!isTruncated) {
|
||||
return (
|
||||
<div className={cn("inline", className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const truncatedText = text.slice(0, truncateLength) + "..."
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<span
|
||||
className={className}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onClick={(e) => e.preventDefault()}
|
||||
{...props}
|
||||
>
|
||||
{truncatedText}
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto max-w-sm rounded-lg border border-white bg-popover p-3 text-sm text-popover-foreground"
|
||||
sideOffset={8}
|
||||
animated={false}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<PopoverArrow className="fill-white" />
|
||||
{children}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export { ContentClamp }
|
||||
@@ -21,8 +21,11 @@ function PopoverContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
animated = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content> & {
|
||||
animated?: boolean
|
||||
}) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
@@ -30,7 +33,9 @@ function PopoverContent({
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
"bg-popover text-popover-foreground z-50 w-72 rounded-md border p-4 shadow-md outline-hidden",
|
||||
animated &&
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-(--radix-popover-content-transform-origin)",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -45,4 +50,17 @@ function PopoverAnchor({
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
function PopoverArrow({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Arrow>) {
|
||||
return (
|
||||
<PopoverPrimitive.Arrow
|
||||
data-slot="popover-arrow"
|
||||
className={cn("fill-popover", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverArrow }
|
||||
|
||||
Reference in New Issue
Block a user