feat(ZoomSelector): 使用Slider组件控制字体
This commit is contained in:
parent
b88fa12214
commit
830c8abcf1
|
|
@ -102,7 +102,7 @@ function ContextMenuContent({
|
|||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
"z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-0 text-popover-foreground shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Slider as SliderPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Slider({
|
||||
className,
|
||||
defaultValue,
|
||||
value,
|
||||
min = 0,
|
||||
max = 100,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
||||
const _values = React.useMemo(
|
||||
() =>
|
||||
Array.isArray(value)
|
||||
? value
|
||||
: Array.isArray(defaultValue)
|
||||
? defaultValue
|
||||
: [min, max],
|
||||
[value, defaultValue, min, max]
|
||||
)
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
data-slot="slider"
|
||||
defaultValue={defaultValue}
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
className={cn(
|
||||
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track
|
||||
data-slot="slider-track"
|
||||
className={cn(
|
||||
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
|
||||
)}
|
||||
>
|
||||
<SliderPrimitive.Range
|
||||
data-slot="slider-range"
|
||||
className={cn(
|
||||
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
|
||||
)}
|
||||
/>
|
||||
</SliderPrimitive.Track>
|
||||
{Array.from({ length: _values.length }, (_, index) => (
|
||||
<SliderPrimitive.Thumb
|
||||
data-slot="slider-thumb"
|
||||
key={index}
|
||||
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||
/>
|
||||
))}
|
||||
</SliderPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Slider }
|
||||
|
|
@ -34,6 +34,7 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { DropdownSelector } from "@/components/ui/dropdown-selector";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { CodeEditor } from "@/components/workspace/code-editor";
|
||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||
|
|
@ -457,10 +458,11 @@ export function ArtifactFileDetail({
|
|||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
)}
|
||||
{/* 仅在代码视图显示缩放控制 */}
|
||||
{isCodeFile && viewMode === "code" && (
|
||||
{/* 代码视图显示缩放控制;Markdown 预览也显示缩放控制 */}
|
||||
{(isCodeFile && viewMode === "code") ||
|
||||
(language === "markdown" && viewMode === "preview") ? (
|
||||
<ArtifactZoomSelector value={zoom} onChange={setZoom} />
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
<div className="col-span-6 flex min-w-0 items-center justify-center px-1">
|
||||
<ArtifactTitle>
|
||||
|
|
@ -1652,45 +1654,32 @@ export const ArtifactZoomSelector = ({
|
|||
...props
|
||||
}: ArtifactZoomSelectorProps) => {
|
||||
const { t } = useI18n();
|
||||
const handleZoomIn = () => {
|
||||
const currentIndex = ZOOM_LEVELS.indexOf(value);
|
||||
const nextValue = ZOOM_LEVELS[currentIndex + 1];
|
||||
if (currentIndex < ZOOM_LEVELS.length - 1 && nextValue !== undefined) {
|
||||
onChange?.(nextValue);
|
||||
const resolvedIndex = useMemo(() => {
|
||||
const exactIndex = ZOOM_LEVELS.indexOf(value);
|
||||
if (exactIndex >= 0) return exactIndex;
|
||||
let nearestIndex = 0;
|
||||
let nearestDistance = Number.POSITIVE_INFINITY;
|
||||
ZOOM_LEVELS.forEach((level, index) => {
|
||||
const distance = Math.abs(level - value);
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance;
|
||||
nearestIndex = index;
|
||||
}
|
||||
};
|
||||
|
||||
const handleZoomOut = () => {
|
||||
const currentIndex = ZOOM_LEVELS.indexOf(value);
|
||||
const prevValue = ZOOM_LEVELS[currentIndex - 1];
|
||||
if (currentIndex > 0 && prevValue !== undefined) {
|
||||
onChange?.(prevValue);
|
||||
}
|
||||
};
|
||||
|
||||
const canZoomIn = ZOOM_LEVELS.indexOf(value) < ZOOM_LEVELS.length - 1;
|
||||
const canZoomOut = ZOOM_LEVELS.indexOf(value) > 0;
|
||||
});
|
||||
return nearestIndex;
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"bg-background border-border inline-flex h-[28px] items-center gap-1 rounded-[10px] border backdrop-blur-sm",
|
||||
"dark:border-border dark:bg-background",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("inline-flex", className)} {...props}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleZoomIn}
|
||||
disabled={!canZoomIn}
|
||||
className={cn(
|
||||
"flex h-full w-10 items-center justify-center rounded py-1 transition-colors",
|
||||
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
|
||||
"dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
||||
)}
|
||||
aria-label={t.artifactPreview.zoomIn}
|
||||
className={cn(
|
||||
"bg-background border-border text-muted-foreground hover:text-foreground inline-flex h-[28px] w-[28px] items-center justify-center rounded-[10px] border transition-colors",
|
||||
"hover:bg-muted/60",
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -1712,46 +1701,28 @@ export const ArtifactZoomSelector = ({
|
|||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<span
|
||||
className={cn(
|
||||
"text-foreground min-w-[36px] text-center text-xs font-medium",
|
||||
"dark:text-foreground",
|
||||
)}
|
||||
>
|
||||
{value}%
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" sideOffset={8} className="w-52 p-[20px] ">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{ZOOM_LEVELS[0]}%
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleZoomOut}
|
||||
disabled={!canZoomOut}
|
||||
className={cn(
|
||||
"flex h-full w-10 items-center justify-center rounded transition-colors",
|
||||
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
||||
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
|
||||
"dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
||||
)}
|
||||
aria-label={t.artifactPreview.zoomOut}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<circle cx="7.55558" cy="7.55534" r="6.16667" stroke="#666666" />
|
||||
<path
|
||||
d="M13.8688 15.4646C14.064 15.6598 14.3806 15.6598 14.5759 15.4646C14.7711 15.2693 14.7711 14.9527 14.5759 14.7574L14.2223 15.111L13.8688 15.4646ZM14.2223 15.111L14.5759 14.7574L11.9092 12.0908L11.5557 12.4443L11.2021 12.7979L13.8688 15.4646L14.2223 15.111Z"
|
||||
fill="#666666"
|
||||
<span className="text-foreground text-xs font-medium">{value}%</span>
|
||||
</div>
|
||||
<Slider
|
||||
min={0}
|
||||
max={ZOOM_LEVELS.length - 1}
|
||||
step={1}
|
||||
value={[resolvedIndex]}
|
||||
onValueChange={(values) => {
|
||||
const nextIndex = values[0];
|
||||
if (nextIndex === undefined) return;
|
||||
const nextValue = ZOOM_LEVELS[nextIndex];
|
||||
if (nextValue !== undefined) onChange?.(nextValue);
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M4.99927 7.5H9.99927"
|
||||
stroke="#666666"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ export function ArtifactFileList({
|
|||
</CardHeader>
|
||||
</Card>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="min-w-[120px] p-1">
|
||||
<ContextMenuContent className="min-w-[120px]">
|
||||
<ContextMenuItem
|
||||
onSelect={() => {
|
||||
dispatchMentionReference({
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ function RichFileCard({
|
|||
/>
|
||||
</a>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="min-w-[120px] p-1">
|
||||
<ContextMenuContent className="min-w-[120px]">
|
||||
<ContextMenuItem
|
||||
disabled={!canReference}
|
||||
onSelect={() => {
|
||||
|
|
|
|||
|
|
@ -462,12 +462,12 @@ pre{
|
|||
|
||||
/* 二三级标题 - 16px */
|
||||
[data-streamdown="heading-2"],
|
||||
[data-streamdown="heading-3"] {
|
||||
[data-streamdown="heading-3"],[data-streamdown="heading-4"] {
|
||||
font-size: calc(16px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
/* 代码块 - 14px */
|
||||
[data-streamdown="code-block"] pre {
|
||||
[data-streamdown="code-block"] pre,code {
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
|
|
@ -481,13 +481,19 @@ pre{
|
|||
|
||||
[data-streamdown="table-cell"] {
|
||||
background-color: transparent;
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
height:calc(42px * var(--zoom-scale)) ;
|
||||
}
|
||||
[data-streamdown="table-header"] {
|
||||
background: #9c9b9b26;
|
||||
height: 50px;
|
||||
height: calc(50px * var(--zoom-scale));
|
||||
}
|
||||
[data-streamdown="table-header"] th {
|
||||
text-align: center;
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
[data-slot="hover-card-trigger"] [data-slot="badge"]{
|
||||
font-size: calc(14px * var(--zoom-scale));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -500,7 +506,7 @@ pre{
|
|||
border-top-right-radius: 5px;
|
||||
}
|
||||
[data-streamdown="table-body"] tr:first-child td{
|
||||
padding-top: 20px;
|
||||
padding-top: calc(20px * var(--zoom-scale));
|
||||
}
|
||||
/* 行分隔线 */
|
||||
[data-streamdown="table-body"] tr{
|
||||
|
|
@ -515,14 +521,13 @@ pre{
|
|||
}
|
||||
|
||||
[data-streamdown="table-body"] tr:last-child {
|
||||
height: 50px;
|
||||
padding-top: calc(50px * var(--zoom-scale));
|
||||
|
||||
}
|
||||
[data-streamdown="table-row"] >[data-streamdown="table-cell"]{
|
||||
line-height: 14px;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue