style: refine acrylic workbench surfaces
This commit is contained in:
@@ -10,14 +10,12 @@ import {
|
||||
} from "lucide-react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
MAP_READABLE_SURFACE_STRONG_CLASS_NAME
|
||||
} from "@/features/map/core/components/map-control-styles";
|
||||
import { MAP_TOOL_PANEL_SURFACE_CLASS_NAME } from "@/features/map/core/components/map-control-styles";
|
||||
import { showMapNotice } from "@/features/map/core/components/notice";
|
||||
import { isGeneratedScheduledConditionSessionId } from "@/features/workbench/data/scheduled-conditions";
|
||||
import { createConditionConversationPrompt } from "@/features/workbench/utils/scheduled-condition-prompts";
|
||||
import type { ScheduledConditionItem } from "@/features/workbench/types";
|
||||
import { createConditionConversationPrompt } from "@/features/workbench/utils/scheduled-condition-prompts";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -41,11 +39,15 @@ import {
|
||||
type ConditionTaskFilterValue
|
||||
} from "./scheduled-condition-feed-utils";
|
||||
|
||||
const CONDITION_CONTENT_SURFACE_CLASS_NAME = "surface-reading rounded-xl border border-slate-200/70";
|
||||
const CONDITION_CONTROL_SURFACE_CLASS_NAME = "surface-control rounded-xl border border-slate-200/70";
|
||||
|
||||
type ScheduledConditionFeedProps = {
|
||||
conditions?: ScheduledConditionItem[];
|
||||
expanded?: boolean;
|
||||
focusRequest?: ScheduledConditionFeedFocusRequest | null;
|
||||
loading?: boolean;
|
||||
visible?: boolean;
|
||||
selectedConditionId?: string | null;
|
||||
onExpandedChange?: (expanded: boolean) => void;
|
||||
onFocusRequestHandled?: (requestId: number) => void;
|
||||
@@ -65,6 +67,7 @@ export function ScheduledConditionFeed({
|
||||
expanded: controlledExpanded,
|
||||
focusRequest,
|
||||
loading = false,
|
||||
visible = true,
|
||||
selectedConditionId: controlledSelectedConditionId,
|
||||
onExpandedChange,
|
||||
onFocusRequestHandled,
|
||||
@@ -115,7 +118,7 @@ export function ScheduledConditionFeed({
|
||||
? `筛选 ${filteredConditionCount}/${totalConditionCount} 条 · 预约 ${futureWorkOrders.length}/${allFutureWorkOrders.length} 条`
|
||||
: expanded
|
||||
? `最近 ${recentConditions.length} 条 · 预约 ${futureWorkOrders.length} 条`
|
||||
: `最近 ${recentConditions.length} 条`;
|
||||
: `最近 ${recentConditions.length} 条 · 预约 ${futureWorkOrders.length} 条`;
|
||||
const selectedCondition =
|
||||
timelineConditions.find((condition) => condition.id === selectedConditionId) ??
|
||||
recentConditions[0] ??
|
||||
@@ -238,14 +241,16 @@ export function ScheduledConditionFeed({
|
||||
return (
|
||||
<section
|
||||
aria-label="工况任务"
|
||||
aria-hidden={!visible}
|
||||
className={cn(
|
||||
"scheduled-feed-panel-shell pointer-events-auto max-w-[calc(100vw-6rem)] overflow-hidden p-3 motion-reduce:transition-none",
|
||||
visible ? "scheduled-feed-shell-enter" : "scheduled-feed-shell-exit",
|
||||
MAP_TOOL_PANEL_SURFACE_CLASS_NAME,
|
||||
expanded ? "flex max-h-[calc(100dvh-8rem)] w-[880px] flex-col 2xl:w-[960px]" : "w-[432px]",
|
||||
"rounded-2xl border",
|
||||
expanded ? "glass-panel" : "glass-control"
|
||||
"rounded-2xl"
|
||||
)}
|
||||
>
|
||||
<header className={cn("flex items-center gap-2.5 px-2.5 py-2", "rounded-xl", expanded ? "bg-white/30" : MAP_READABLE_SURFACE_STRONG_CLASS_NAME)}>
|
||||
<header className={cn("flex items-center gap-2.5 px-2.5 py-2", CONDITION_CONTROL_SURFACE_CLASS_NAME)}>
|
||||
<span className={cn("grid h-8 w-8 shrink-0 place-items-center bg-blue-50 text-blue-700", "rounded-lg")}>
|
||||
<CalendarClock size={17} aria-hidden="true" />
|
||||
</span>
|
||||
@@ -366,10 +371,7 @@ function ConditionTimelinePanel({
|
||||
className={cn(
|
||||
"flex min-h-0 flex-col overflow-hidden p-2.5",
|
||||
expanded ? "h-[calc(100dvh-14rem)] max-h-[calc(100dvh-14rem)]" : "h-[420px] max-h-[420px]",
|
||||
"rounded-xl",
|
||||
expanded
|
||||
? "border border-white/30 bg-white/10"
|
||||
: MAP_READABLE_SURFACE_STRONG_CLASS_NAME
|
||||
CONDITION_CONTENT_SURFACE_CLASS_NAME
|
||||
)}
|
||||
>
|
||||
{expanded && !loading ? (
|
||||
@@ -387,7 +389,7 @@ function ConditionTimelinePanel({
|
||||
/>
|
||||
) : null}
|
||||
{expanded && !loading && futureWorkOrders.length > 0 ? (
|
||||
<section className="mb-2 rounded-xl border border-blue-100 bg-blue-50/55 p-2">
|
||||
<section className="mb-2">
|
||||
<ConditionGroupHeader icon={ClipboardList} title="预约任务" count={futureWorkOrders.length} compact />
|
||||
<div className="scheduled-feed-scroll max-h-[132px] overflow-y-auto pr-1">
|
||||
<ConditionRows
|
||||
@@ -447,7 +449,7 @@ function ConditionFilterBar({
|
||||
const filterActive = taskFilter !== CONDITION_FILTER_ALL || statusFilter !== CONDITION_FILTER_ALL;
|
||||
|
||||
return (
|
||||
<div className="mb-2 rounded-xl border border-slate-200/70 bg-slate-50/95 p-2 ring-1 ring-white/80">
|
||||
<div className={cn(CONDITION_CONTENT_SURFACE_CLASS_NAME, "mb-2 p-2")}>
|
||||
<div className="mb-2 flex items-center justify-between gap-2 px-1">
|
||||
<span className="text-xs font-semibold text-slate-700">筛选</span>
|
||||
<span className="shrink-0 text-xs font-semibold text-slate-400">
|
||||
@@ -478,7 +480,7 @@ function ConditionFilterBar({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onResetFilters}
|
||||
className="mt-2 inline-flex h-7 w-full items-center justify-center gap-1.5 rounded-lg border border-blue-100 bg-white px-2 text-xs font-semibold text-blue-700 shadow-sm transition hover:border-blue-200 hover:bg-blue-50"
|
||||
className="surface-control mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-slate-200/70 px-2 text-xs font-semibold text-blue-700 transition-[background-color,transform] hover:bg-blue-50 active:scale-95"
|
||||
>
|
||||
<XCircle size={12} aria-hidden="true" />
|
||||
重置筛选
|
||||
@@ -515,7 +517,7 @@ function ConditionFilterDropdown<TValue extends string>({
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"group flex h-9 w-full min-w-0 items-center gap-2 border border-white/80 bg-white px-2 text-left text-xs shadow-sm outline-none transition hover:border-blue-100 hover:bg-blue-50/35 focus-visible:border-blue-200 focus-visible:ring-2 focus-visible:ring-blue-100 data-[state=open]:border-blue-200 data-[state=open]:bg-blue-50/60 data-[state=open]:ring-2 data-[state=open]:ring-blue-100",
|
||||
"surface-reading group flex h-9 w-full min-w-0 items-center gap-2 border border-slate-200/70 px-2 text-left text-xs outline-none transition-colors hover:bg-blue-50 focus-visible:border-blue-200 focus-visible:ring-2 focus-visible:ring-blue-100 data-[state=open]:border-blue-200 data-[state=open]:bg-blue-50 data-[state=open]:ring-2 data-[state=open]:ring-blue-100",
|
||||
"rounded-xl"
|
||||
)}
|
||||
>
|
||||
@@ -535,7 +537,7 @@ function ConditionFilterDropdown<TValue extends string>({
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
className="glass-menu scheduled-feed-scroll z-50 max-h-[280px] w-[var(--radix-dropdown-menu-trigger-width)] overflow-y-auto rounded-xl border p-1.5 text-slate-700"
|
||||
className="surface-reading scheduled-feed-scroll z-50 max-h-[280px] w-[var(--radix-dropdown-menu-trigger-width)] overflow-y-auto rounded-xl border border-slate-200/70 p-1.5 text-slate-700 shadow-md shadow-slate-900/10"
|
||||
>
|
||||
<DropdownMenuRadioGroup value={value} onValueChange={(nextValue) => onValueChange(nextValue as TValue)}>
|
||||
{options.map((option) => (
|
||||
@@ -543,12 +545,12 @@ function ConditionFilterDropdown<TValue extends string>({
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={cn(
|
||||
"my-0.5 min-h-9 gap-2 border border-transparent py-1.5 pl-7 pr-2 text-xs transition focus:bg-blue-50/80 focus:text-blue-800 data-[state=checked]:border-blue-100 data-[state=checked]:bg-blue-50/90 data-[state=checked]:text-blue-800",
|
||||
"my-0.5 min-h-9 gap-2 border border-transparent bg-transparent py-1.5 pl-7 pr-2 text-xs transition focus:bg-blue-50 focus:text-blue-800 data-[state=checked]:border-blue-100 data-[state=checked]:bg-blue-50 data-[state=checked]:text-blue-800",
|
||||
"rounded-xl"
|
||||
)}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate font-semibold">{option.label}</span>
|
||||
<span className="shrink-0 rounded-full border border-slate-200 bg-white px-1.5 py-0.5 text-xs font-semibold leading-4 text-slate-500">
|
||||
<span className="surface-control shrink-0 rounded-full border px-1.5 py-0.5 text-xs font-semibold leading-4 text-slate-500">
|
||||
{option.count}
|
||||
</span>
|
||||
</DropdownMenuRadioItem>
|
||||
@@ -562,7 +564,7 @@ function ConditionFilterDropdown<TValue extends string>({
|
||||
|
||||
function ConditionEmptyState({ title, description }: { title: string; description: string }) {
|
||||
return (
|
||||
<div className="flex min-h-full flex-col items-center justify-center rounded-xl border border-dashed border-slate-200 bg-[var(--glass-readable)] px-4 py-5 text-center">
|
||||
<div className="surface-reading flex min-h-full flex-col items-center justify-center rounded-xl border border-dashed border-slate-200/80 px-4 py-5 text-center">
|
||||
<span className="grid h-8 w-8 place-items-center rounded-lg bg-slate-100 text-slate-400">
|
||||
<ChevronRight size={15} aria-hidden="true" />
|
||||
</span>
|
||||
@@ -587,7 +589,7 @@ function ConditionTimelineSkeleton({ rows }: { rows: number }) {
|
||||
) : null}
|
||||
<span className="relative z-10 h-6 w-6 animate-pulse rounded-full bg-slate-200 ring-4 ring-white" />
|
||||
</span>
|
||||
<span className="min-w-0 rounded-xl border border-transparent bg-[var(--glass-menu)] px-2 py-1.5">
|
||||
<span className="surface-reading min-w-0 rounded-xl border border-slate-200/70 px-2 py-1.5">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<span className="min-w-0 flex-1 space-y-1.5">
|
||||
<span className="block h-3.5 w-24 animate-pulse rounded-full bg-slate-200/90" />
|
||||
@@ -607,13 +609,12 @@ function ConditionDetailSkeleton() {
|
||||
<DetailScroll>
|
||||
<div
|
||||
className={cn(
|
||||
"relative min-h-[420px] overflow-hidden border border-slate-200/60 bg-white/30 px-4 py-3",
|
||||
"surface-reading relative min-h-[420px] overflow-hidden border border-slate-200/70 px-4 py-3",
|
||||
"rounded-xl"
|
||||
)}
|
||||
role="status"
|
||||
aria-label="工况详情加载中"
|
||||
>
|
||||
<div className="absolute inset-y-0 left-0 w-1 bg-blue-300" aria-hidden="true" />
|
||||
<div className="flex gap-2.5">
|
||||
<span className={cn("h-9 w-9 shrink-0 animate-pulse bg-slate-100", "rounded-lg")} />
|
||||
<div className="min-w-0 flex-1 space-y-2">
|
||||
@@ -628,7 +629,7 @@ function ConditionDetailSkeleton() {
|
||||
</div>
|
||||
<div className="mt-4 grid gap-2">
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<div key={index} className="rounded-xl border border-slate-200/70 bg-slate-50/80 px-3 py-3">
|
||||
<div key={index} className="surface-reading rounded-xl border border-slate-200/70 px-3 py-3">
|
||||
<span className="block h-3.5 w-28 animate-pulse rounded-full bg-slate-200" />
|
||||
<span className="mt-2 block h-3 w-full animate-pulse rounded-full bg-slate-100" />
|
||||
<span className="mt-1.5 block h-3 w-3/4 animate-pulse rounded-full bg-slate-100" />
|
||||
@@ -706,8 +707,8 @@ function ConditionTimelineRow({ condition, selected, isLast, onSelect }: Conditi
|
||||
aria-pressed={selected}
|
||||
onClick={onSelect}
|
||||
className={cn(
|
||||
"group grid min-h-[60px] w-full grid-cols-[42px_24px_minmax(0,1fr)] gap-2 rounded-xl px-1 py-1 text-left outline-none transition-colors focus-visible:bg-blue-50/40 focus-visible:ring-2 focus-visible:ring-blue-200/80 focus-visible:ring-offset-1 focus-visible:ring-offset-white",
|
||||
selected ? "bg-blue-50/70" : "hover:bg-blue-50/45"
|
||||
"group grid min-h-[60px] w-full grid-cols-[42px_24px_minmax(0,1fr)] gap-2 rounded-xl border px-1 py-1 text-left outline-none transition-colors focus-visible:bg-blue-50 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-200",
|
||||
selected ? "surface-reading border-blue-100 text-blue-900" : "border-transparent hover:bg-slate-50"
|
||||
)}
|
||||
>
|
||||
<span className="pt-2 font-mono text-xs font-semibold leading-4 text-slate-500">{formatTime(condition.scheduledAt)}</span>
|
||||
@@ -735,10 +736,10 @@ function ConditionTimelineRow({ condition, selected, isLast, onSelect }: Conditi
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"min-w-0 rounded-xl border px-2 py-1.5 transition-colors group-focus-visible:border-blue-300 group-focus-visible:bg-white",
|
||||
"min-w-0 px-2 py-1.5 transition-colors",
|
||||
selected
|
||||
? "border-blue-300 bg-white text-blue-900 shadow-sm shadow-blue-600/10 ring-1 ring-blue-300/40"
|
||||
: "border-transparent bg-[var(--glass-menu)] text-slate-700 group-hover:border-blue-100 group-hover:bg-white"
|
||||
? "text-blue-900"
|
||||
: "text-slate-700"
|
||||
)}
|
||||
>
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user