feat(ZoomSelector): 使用Slider组件控制字体
This commit is contained in:
parent
b88fa12214
commit
830c8abcf1
|
|
@ -102,7 +102,7 @@ function ContextMenuContent({
|
||||||
<ContextMenuPrimitive.Content
|
<ContextMenuPrimitive.Content
|
||||||
data-slot="context-menu-content"
|
data-slot="context-menu-content"
|
||||||
className={cn(
|
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
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...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,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { DropdownSelector } from "@/components/ui/dropdown-selector";
|
import { DropdownSelector } from "@/components/ui/dropdown-selector";
|
||||||
|
import { Slider } from "@/components/ui/slider";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||||
import { CodeEditor } from "@/components/workspace/code-editor";
|
import { CodeEditor } from "@/components/workspace/code-editor";
|
||||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||||
|
|
@ -457,10 +458,11 @@ export function ArtifactFileDetail({
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
)}
|
)}
|
||||||
{/* 仅在代码视图显示缩放控制 */}
|
{/* 代码视图显示缩放控制;Markdown 预览也显示缩放控制 */}
|
||||||
{isCodeFile && viewMode === "code" && (
|
{(isCodeFile && viewMode === "code") ||
|
||||||
|
(language === "markdown" && viewMode === "preview") ? (
|
||||||
<ArtifactZoomSelector value={zoom} onChange={setZoom} />
|
<ArtifactZoomSelector value={zoom} onChange={setZoom} />
|
||||||
)}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-6 flex min-w-0 items-center justify-center px-1">
|
<div className="col-span-6 flex min-w-0 items-center justify-center px-1">
|
||||||
<ArtifactTitle>
|
<ArtifactTitle>
|
||||||
|
|
@ -1652,45 +1654,32 @@ export const ArtifactZoomSelector = ({
|
||||||
...props
|
...props
|
||||||
}: ArtifactZoomSelectorProps) => {
|
}: ArtifactZoomSelectorProps) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const handleZoomIn = () => {
|
const resolvedIndex = useMemo(() => {
|
||||||
const currentIndex = ZOOM_LEVELS.indexOf(value);
|
const exactIndex = ZOOM_LEVELS.indexOf(value);
|
||||||
const nextValue = ZOOM_LEVELS[currentIndex + 1];
|
if (exactIndex >= 0) return exactIndex;
|
||||||
if (currentIndex < ZOOM_LEVELS.length - 1 && nextValue !== undefined) {
|
let nearestIndex = 0;
|
||||||
onChange?.(nextValue);
|
let nearestDistance = Number.POSITIVE_INFINITY;
|
||||||
|
ZOOM_LEVELS.forEach((level, index) => {
|
||||||
|
const distance = Math.abs(level - value);
|
||||||
|
if (distance < nearestDistance) {
|
||||||
|
nearestDistance = distance;
|
||||||
|
nearestIndex = index;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
return nearestIndex;
|
||||||
const handleZoomOut = () => {
|
}, [value]);
|
||||||
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 (
|
return (
|
||||||
<div
|
<div className={cn("inline-flex", className)} {...props}>
|
||||||
className={cn(
|
<DropdownMenu>
|
||||||
"bg-background border-border inline-flex h-[28px] items-center gap-1 rounded-[10px] border backdrop-blur-sm",
|
<DropdownMenuTrigger asChild>
|
||||||
"dark:border-border dark:bg-background",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
type="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}
|
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
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -1712,46 +1701,28 @@ export const ArtifactZoomSelector = ({
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<span
|
</DropdownMenuTrigger>
|
||||||
className={cn(
|
<DropdownMenuContent align="start" sideOffset={8} className="w-52 p-[20px] ">
|
||||||
"text-foreground min-w-[36px] text-center text-xs font-medium",
|
<div className="mb-2 flex items-center justify-between">
|
||||||
"dark:text-foreground",
|
<span className="text-muted-foreground text-xs">
|
||||||
)}
|
{ZOOM_LEVELS[0]}%
|
||||||
>
|
|
||||||
{value}%
|
|
||||||
</span>
|
</span>
|
||||||
<button
|
<span className="text-foreground text-xs font-medium">{value}%</span>
|
||||||
type="button"
|
</div>
|
||||||
onClick={handleZoomOut}
|
<Slider
|
||||||
disabled={!canZoomOut}
|
min={0}
|
||||||
className={cn(
|
max={ZOOM_LEVELS.length - 1}
|
||||||
"flex h-full w-10 items-center justify-center rounded transition-colors",
|
step={1}
|
||||||
"text-muted-foreground hover:bg-muted hover:text-foreground",
|
value={[resolvedIndex]}
|
||||||
"disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
|
onValueChange={(values) => {
|
||||||
"dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-foreground",
|
const nextIndex = values[0];
|
||||||
)}
|
if (nextIndex === undefined) return;
|
||||||
aria-label={t.artifactPreview.zoomOut}
|
const nextValue = ZOOM_LEVELS[nextIndex];
|
||||||
>
|
if (nextValue !== undefined) onChange?.(nextValue);
|
||||||
<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"
|
|
||||||
/>
|
/>
|
||||||
<path
|
</DropdownMenuContent>
|
||||||
d="M4.99927 7.5H9.99927"
|
</DropdownMenu>
|
||||||
stroke="#666666"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export function ArtifactFileList({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent className="min-w-[120px] p-1">
|
<ContextMenuContent className="min-w-[120px]">
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
dispatchMentionReference({
|
dispatchMentionReference({
|
||||||
|
|
|
||||||
|
|
@ -421,7 +421,7 @@ function RichFileCard({
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent className="min-w-[120px] p-1">
|
<ContextMenuContent className="min-w-[120px]">
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
disabled={!canReference}
|
disabled={!canReference}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
|
|
|
||||||
|
|
@ -462,12 +462,12 @@ pre{
|
||||||
|
|
||||||
/* 二三级标题 - 16px */
|
/* 二三级标题 - 16px */
|
||||||
[data-streamdown="heading-2"],
|
[data-streamdown="heading-2"],
|
||||||
[data-streamdown="heading-3"] {
|
[data-streamdown="heading-3"],[data-streamdown="heading-4"] {
|
||||||
font-size: calc(16px * var(--zoom-scale));
|
font-size: calc(16px * var(--zoom-scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 代码块 - 14px */
|
/* 代码块 - 14px */
|
||||||
[data-streamdown="code-block"] pre {
|
[data-streamdown="code-block"] pre,code {
|
||||||
font-size: calc(14px * var(--zoom-scale));
|
font-size: calc(14px * var(--zoom-scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -481,13 +481,19 @@ pre{
|
||||||
|
|
||||||
[data-streamdown="table-cell"] {
|
[data-streamdown="table-cell"] {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
font-size: calc(14px * var(--zoom-scale));
|
||||||
|
height:calc(42px * var(--zoom-scale)) ;
|
||||||
}
|
}
|
||||||
[data-streamdown="table-header"] {
|
[data-streamdown="table-header"] {
|
||||||
background: #9c9b9b26;
|
background: #9c9b9b26;
|
||||||
height: 50px;
|
height: calc(50px * var(--zoom-scale));
|
||||||
}
|
}
|
||||||
[data-streamdown="table-header"] th {
|
[data-streamdown="table-header"] th {
|
||||||
text-align: center;
|
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;
|
border-top-right-radius: 5px;
|
||||||
}
|
}
|
||||||
[data-streamdown="table-body"] tr:first-child td{
|
[data-streamdown="table-body"] tr:first-child td{
|
||||||
padding-top: 20px;
|
padding-top: calc(20px * var(--zoom-scale));
|
||||||
}
|
}
|
||||||
/* 行分隔线 */
|
/* 行分隔线 */
|
||||||
[data-streamdown="table-body"] tr{
|
[data-streamdown="table-body"] tr{
|
||||||
|
|
@ -515,14 +521,13 @@ pre{
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-streamdown="table-body"] tr:last-child {
|
[data-streamdown="table-body"] tr:last-child {
|
||||||
height: 50px;
|
padding-top: calc(50px * var(--zoom-scale));
|
||||||
|
|
||||||
}
|
}
|
||||||
[data-streamdown="table-row"] >[data-streamdown="table-cell"]{
|
[data-streamdown="table-row"] >[data-streamdown="table-cell"]{
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue