feat(charts): manage investigation charts in a scrolling workspace
This commit is contained in:
@@ -2,13 +2,13 @@ import { Check, Map } from "lucide-react";
|
||||
import { type FocusEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
MAP_SELECTION_CARD_FOCUS_CLASS_NAME,
|
||||
MAP_TOOL_PANEL_SURFACE_CLASS_NAME
|
||||
} from "./map-control-styles";
|
||||
|
||||
@@ -105,18 +105,18 @@ export function BaseLayersControl({
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={option.disabled}
|
||||
aria-pressed={active}
|
||||
onClick={() => onSelectLayer(option.id)}
|
||||
className={cn(
|
||||
"flex min-h-[76px] flex-col p-1 text-left transition duration-150",
|
||||
"flex min-h-[76px] flex-col p-1 text-left",
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
MAP_SELECTION_CARD_FOCUS_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
active
|
||||
? "border-[var(--action-blue)] text-[var(--action-blue-hover)] shadow-xs"
|
||||
: "text-slate-700 hover:border-[var(--palette-brand)] hover:bg-[var(--action-soft)]/40",
|
||||
option.disabled && "cursor-not-allowed opacity-50"
|
||||
: "text-slate-700",
|
||||
mapOptionInteractionVariants({ state: active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<BaseLayerSwatch option={option} active={active} />
|
||||
@@ -159,22 +159,22 @@ export function BaseLayersControl({
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={option.disabled}
|
||||
aria-pressed={active}
|
||||
onClick={() => onSelectLayer(option.id)}
|
||||
className={cn(
|
||||
"flex h-[84px] w-[84px] flex-col p-1 text-center transition duration-150",
|
||||
"flex h-[84px] w-[84px] flex-col p-1 text-center text-slate-800",
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
MAP_SELECTION_CARD_FOCUS_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
active ? "border-[var(--action-blue)] shadow-xs" : "border-[var(--surface-divider)] hover:border-[var(--palette-brand)] hover:bg-[var(--action-soft)]/40",
|
||||
option.disabled && "cursor-not-allowed opacity-50"
|
||||
active ? "border-[var(--action-blue)] shadow-xs" : "border-[var(--surface-divider)]",
|
||||
mapOptionInteractionVariants({ state: active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<BaseLayerSwatch option={option} active={active} />
|
||||
<span
|
||||
data-base-layer-label="true"
|
||||
className="block h-4 shrink-0 truncate px-0.5 pt-1 text-xs font-semibold leading-none text-slate-800"
|
||||
className="block h-4 shrink-0 truncate px-0.5 pt-1 text-xs font-semibold leading-none"
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
@@ -185,9 +185,10 @@ export function BaseLayersControl({
|
||||
<div className={cn("absolute bottom-0 right-0 grid h-[96px] w-[96px] place-items-center p-1.5", MAP_CONTROL_RADIUS_CLASS_NAME, MAP_CONTROL_SURFACE_CLASS_NAME)}>
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-label={`${title}:当前 ${activeLayer?.label ?? ""}`}
|
||||
title={`${title}:当前 ${activeLayer?.label ?? ""}`}
|
||||
className={cn("grid h-[84px] w-[84px] place-items-center p-1 text-slate-700 shadow-xs transition hover:border-[var(--palette-brand)] hover:text-[var(--action-blue-hover)]", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME, MAP_SELECTION_CARD_FOCUS_CLASS_NAME)}
|
||||
className={cn("grid h-[84px] w-[84px] place-items-center p-1 text-slate-700 shadow-xs", MAP_READABLE_RADIUS_CLASS_NAME, MAP_READABLE_SURFACE_CLASS_NAME, mapOptionInteractionVariants())}
|
||||
>
|
||||
<span className={cn("relative h-full w-full overflow-hidden border border-white shadow-inner", MAP_ICON_CELL_RADIUS_CLASS_NAME)}>
|
||||
{activeLayer ? (
|
||||
@@ -195,7 +196,7 @@ export function BaseLayersControl({
|
||||
) : (
|
||||
<Map size={18} aria-hidden="true" className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
||||
)}
|
||||
<span className="surface-reading absolute bottom-1 left-1 right-1 rounded px-1 py-0.5 text-xs font-semibold leading-none text-slate-800">
|
||||
<span className="surface-reading absolute bottom-1 left-1 right-1 rounded px-1 py-0.5 text-xs font-semibold leading-none">
|
||||
{activeLayer?.label ?? title}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { LucideIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
||||
@@ -79,14 +80,15 @@ export function MapModeButton({ icon: Icon, label, active = false, disabled = fa
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-pressed={active}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex h-14 flex-col items-center justify-center gap-1 text-xs font-semibold transition duration-150",
|
||||
"flex h-14 flex-col items-center justify-center gap-1 border border-transparent text-xs font-semibold",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
active ? "bg-[var(--action-blue)] text-white" : "surface-control text-slate-600 hover:text-[var(--action-blue-hover)]",
|
||||
disabled && "cursor-not-allowed opacity-55 hover:text-slate-600"
|
||||
active ? "bg-[var(--action-blue)] text-white" : "surface-control text-slate-600",
|
||||
mapOptionInteractionVariants({ state: active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<Icon size={17} aria-hidden="true" />
|
||||
@@ -109,13 +111,14 @@ export function MapActionChip({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex h-10 items-center justify-center gap-1.5 text-xs font-semibold text-slate-700 transition duration-150 hover:border-[var(--surface-border)] hover:text-[var(--action-blue-hover)]",
|
||||
"flex h-10 items-center justify-center gap-1.5 text-xs font-semibold text-slate-700",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
disabled && "cursor-not-allowed opacity-55 hover:border-white/70 hover:text-slate-700"
|
||||
mapOptionInteractionVariants()
|
||||
)}
|
||||
>
|
||||
<Icon size={14} aria-hidden="true" />
|
||||
@@ -157,23 +160,20 @@ export function MapActionRow({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex min-h-12 w-full items-center gap-2.5 border px-2.5 py-2 text-left",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
isPrimary
|
||||
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-lg shadow-[color:var(--action-blue)]/25 hover:bg-[var(--action-blue-hover)]"
|
||||
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-lg shadow-[color:var(--action-blue)]/25"
|
||||
: strong
|
||||
? "border-[var(--surface-border)] bg-[var(--action-soft)]/95 text-[var(--action-blue-pressed)]"
|
||||
: "surface-control border-transparent text-slate-700 hover:border-[var(--surface-border)] hover:bg-[var(--surface-reading)] hover:text-[var(--action-blue-hover)]",
|
||||
isDanger && "hover:border-red-100 hover:text-red-700",
|
||||
: "surface-control border-transparent text-slate-700",
|
||||
selected && !isPrimary && "border-[var(--surface-border)] bg-[var(--action-soft)] text-[var(--action-blue-pressed)]",
|
||||
mapOptionInteractionVariants({ state: isDanger ? "danger" : isPrimary ? "primary" : selected || strong ? "selected" : "default" }),
|
||||
muted && "opacity-70",
|
||||
disabled &&
|
||||
(isPrimary
|
||||
? "cursor-not-allowed opacity-55 hover:border-[var(--action-blue)] hover:bg-[var(--action-blue-hover)] hover:text-white"
|
||||
: "cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-[var(--surface-reading)]/95 hover:text-slate-700"),
|
||||
disabled && "shadow-none"
|
||||
)}
|
||||
>
|
||||
@@ -185,8 +185,8 @@ export function MapActionRow({
|
||||
? "bg-[var(--surface-reading)]/16 text-white"
|
||||
: selected || strong
|
||||
? "bg-[var(--action-blue)] text-white"
|
||||
: "bg-[var(--surface-control)] text-[var(--ink-muted)]",
|
||||
isDanger && "bg-red-50 text-red-600"
|
||||
: "bg-[var(--surface-control)] text-inherit",
|
||||
isDanger && "bg-[var(--status-danger-soft)] text-[var(--status-danger-foreground)]"
|
||||
)}
|
||||
>
|
||||
<Icon size={15} aria-hidden="true" />
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
TooltipTrigger
|
||||
} from "@/shared/ui/tooltip";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
@@ -46,27 +47,26 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-pressed={item.active}
|
||||
disabled={item.disabled}
|
||||
onClick={item.onClick}
|
||||
className={cn(
|
||||
"flex min-h-10 items-center gap-2 border px-2.5 py-2 text-left text-xs font-semibold transition duration-150",
|
||||
"flex min-h-10 items-center gap-2 border px-2.5 py-2 text-left text-xs font-semibold",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
item.active
|
||||
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "text-slate-700 hover:border-[var(--surface-border)] hover:bg-[var(--surface-reading)] hover:text-[var(--action-blue-hover)]",
|
||||
: "text-slate-700",
|
||||
!item.active && MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
isDanger && "hover:border-red-100 hover:text-red-700",
|
||||
item.disabled &&
|
||||
"cursor-not-allowed opacity-55 hover:border-white/70 hover:bg-[var(--surface-reading)]/95 hover:text-slate-700"
|
||||
mapOptionInteractionVariants({ state: isDanger ? "danger" : item.active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"grid h-7 w-7 shrink-0 place-items-center",
|
||||
"grid h-7 w-7 shrink-0 place-items-center text-inherit transition-[background-color] duration-150 motion-reduce:transition-none",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
item.active ? "bg-[var(--surface-reading)]/16 text-white" : "bg-[var(--surface-control)] text-[var(--ink-muted)]",
|
||||
isDanger && "bg-red-50 text-red-600"
|
||||
item.active ? "bg-[var(--surface-reading)]/16" : "bg-[var(--surface-control)]",
|
||||
isDanger && "bg-[var(--status-danger-soft)] text-[var(--status-danger-foreground)]"
|
||||
)}
|
||||
>
|
||||
<Icon size={15} aria-hidden="true" />
|
||||
@@ -100,6 +100,7 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-label={item.label}
|
||||
aria-pressed={item.active}
|
||||
disabled={item.disabled}
|
||||
@@ -109,9 +110,8 @@ export function MapDrawToolbar({ items, label = "绘制工具", className = "",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
item.active
|
||||
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "hover:bg-[var(--action-soft)] hover:text-[var(--action-blue-hover)]",
|
||||
isDanger && "hover:bg-red-50 hover:text-red-700",
|
||||
item.disabled && "opacity-45 hover:bg-transparent hover:text-slate-600"
|
||||
: undefined,
|
||||
mapOptionInteractionVariants({ state: isDanger ? "danger" : item.active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<Icon size={16} aria-hidden="true" />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Eye, EyeOff, Layers3, Lock } from "lucide-react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_MAJOR_PANEL_RADIUS_CLASS_NAME,
|
||||
@@ -79,23 +80,24 @@ export function MapLayerControl({
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={disabled}
|
||||
aria-pressed={item.visible}
|
||||
onClick={() => onToggleLayer(item.id, !item.visible)}
|
||||
className={cn(
|
||||
"flex min-h-12 w-full items-center gap-3 border px-2.5 text-left text-sm transition duration-150",
|
||||
"flex min-h-12 w-full items-center gap-3 border px-2.5 text-left text-sm",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
item.visible
|
||||
? "surface-control border-[var(--surface-border)] text-slate-900"
|
||||
: "surface-well border-transparent text-[var(--ink-muted)] hover:border-[var(--surface-divider)] hover:bg-[var(--surface-reading)]",
|
||||
disabled && "cursor-not-allowed opacity-60 hover:bg-transparent"
|
||||
: "surface-well border-transparent text-[var(--ink-muted)]",
|
||||
mapOptionInteractionVariants({ state: item.visible ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"grid h-8 w-8 shrink-0 place-items-center",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
item.visible ? "bg-[var(--action-blue)] text-white" : "bg-[var(--surface-control)] text-[var(--ink-muted)]"
|
||||
item.visible ? "bg-[var(--action-blue)] text-white" : "bg-[var(--surface-control)] text-inherit"
|
||||
)}
|
||||
>
|
||||
{item.locked ? <Lock size={15} aria-hidden="true" /> : <Icon size={15} aria-hidden="true" />}
|
||||
|
||||
@@ -4,12 +4,12 @@ import { cn } from "@/shared/ui/cn";
|
||||
import type { BaseLayerOption } from "./base-layers-control";
|
||||
import type { MapLayerControlItem, MapLayerControlVisual } from "./layer-control";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
MAP_READABLE_SURFACE_STRONG_CLASS_NAME,
|
||||
MAP_SELECTION_CARD_FOCUS_CLASS_NAME
|
||||
MAP_READABLE_SURFACE_STRONG_CLASS_NAME
|
||||
} from "./map-control-styles";
|
||||
|
||||
export type MapLayerPanelProps = {
|
||||
@@ -61,14 +61,14 @@ function LayerVisibilitySection({ items, onToggleLayer }: LayerVisibilitySection
|
||||
onToggle={(event) => handleGroupToggle(group.id, event.currentTarget.open)}
|
||||
className="surface-well group/layer overflow-hidden rounded-xl border"
|
||||
>
|
||||
<summary className="flex min-h-11 cursor-pointer list-none items-center gap-2 px-2.5 py-1.5 text-left marker:content-none active:scale-[0.99]">
|
||||
<summary data-map-option="" className={cn("flex min-h-11 cursor-pointer list-none items-center gap-2 border border-transparent px-2.5 py-1.5 text-left text-slate-700 marker:content-none", mapOptionInteractionVariants())}>
|
||||
<ChevronRight
|
||||
size={14}
|
||||
aria-hidden="true"
|
||||
className="shrink-0 text-[var(--ink-muted)] transition-transform duration-150 ease-out motion-reduce:transition-none group-open/layer:rotate-90"
|
||||
className="shrink-0 text-inherit transition-transform duration-150 ease-out motion-reduce:transition-none group-open/layer:rotate-90"
|
||||
/>
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="block truncate text-xs font-semibold text-slate-700">{group.label}</span>
|
||||
<span className="block truncate text-xs font-semibold">{group.label}</span>
|
||||
{group.description ? (
|
||||
<span className="mt-0.5 block truncate text-[10px] leading-tight text-[var(--ink-muted)]">
|
||||
{group.description}
|
||||
@@ -169,18 +169,19 @@ function LayerVisibilityRow({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={disabled}
|
||||
aria-pressed={item.visible}
|
||||
aria-label={`${item.label}${meta ? `,${meta}` : ""}`}
|
||||
title={meta}
|
||||
onClick={() => onToggleLayer(item.id, !item.visible)}
|
||||
className={cn(
|
||||
"flex min-h-12 w-full items-center gap-2.5 border px-2 py-1.5 text-left transition-[background-color,border-color,color,box-shadow,transform] duration-150 ease-out motion-reduce:transition-none active:scale-[0.98] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-1",
|
||||
"flex min-h-12 w-full items-center gap-2.5 border px-2 py-1.5 text-left",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
item.visible
|
||||
? "border-[var(--surface-border)]/90 bg-[var(--surface-reading)] text-slate-950 shadow-xs shadow-slate-950/5"
|
||||
: "border-transparent bg-[var(--surface-control)]/55 text-slate-600 [@media(hover:hover)]:hover:border-[var(--surface-divider)] [@media(hover:hover)]:hover:bg-[var(--surface-reading)]/75",
|
||||
disabled && "cursor-not-allowed opacity-55 active:scale-100"
|
||||
: "border-transparent bg-[var(--surface-control)]/55 text-slate-600",
|
||||
mapOptionInteractionVariants({ state: item.visible ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<LayerVisual visual={item.visual} visible={item.visible} />
|
||||
@@ -195,7 +196,7 @@ function LayerVisibilityRow({
|
||||
<span
|
||||
className={cn(
|
||||
"grid h-8 w-8 shrink-0 place-items-center rounded-lg transition-[background-color,color,opacity] duration-150 motion-reduce:transition-none",
|
||||
item.visible ? "bg-[var(--action-soft)] text-[var(--action-blue-hover)]" : "bg-transparent text-[var(--ink-muted)]"
|
||||
item.visible ? "bg-[var(--action-soft)] text-[var(--action-blue-hover)]" : "bg-transparent text-inherit"
|
||||
)}
|
||||
>
|
||||
{item.locked ? <Lock size={15} aria-hidden="true" /> : <Icon size={15} aria-hidden="true" />}
|
||||
@@ -297,18 +298,18 @@ function BaseLayerSection({ options, activeLayerId, onSelectLayer }: BaseLayerSe
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
disabled={option.disabled}
|
||||
aria-pressed={active}
|
||||
onClick={() => onSelectLayer(option.id)}
|
||||
className={cn(
|
||||
"flex min-h-[76px] flex-col p-1 text-left transition duration-150",
|
||||
"flex min-h-[76px] flex-col p-1 text-left",
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
MAP_SELECTION_CARD_FOCUS_CLASS_NAME,
|
||||
active ? MAP_READABLE_SURFACE_STRONG_CLASS_NAME : MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
active
|
||||
? "border-[var(--action-blue)] text-[var(--action-blue-hover)]"
|
||||
: "text-slate-700 hover:border-[var(--palette-brand)] hover:bg-[var(--action-soft)]/40",
|
||||
option.disabled && "cursor-not-allowed opacity-50"
|
||||
: "text-slate-700",
|
||||
mapOptionInteractionVariants({ state: active ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<BaseLayerSwatch option={option} active={active} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { buttonInteractionVariants } from "@/shared/ui/button-interaction";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export const MAP_MAJOR_PANEL_RADIUS_CLASS_NAME = "rounded-2xl";
|
||||
|
||||
@@ -28,5 +28,27 @@ export const MAP_READABLE_SURFACE_CLASS_NAME =
|
||||
export const MAP_READABLE_SURFACE_STRONG_CLASS_NAME =
|
||||
"surface-control border";
|
||||
|
||||
export const MAP_CONTROL_HOVER_CLASS_NAME =
|
||||
buttonInteractionVariants({ tone: "neutral" });
|
||||
export const mapOptionInteractionVariants = cva(
|
||||
"map-option-interaction outline-hidden transition-colors duration-150 motion-reduce:transition-none focus-visible:outline-1 focus-visible:outline-solid focus-visible:outline-[var(--action-focus-ring)] focus-visible:-outline-offset-1 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-55 data-[disabled]:pointer-events-none data-[disabled]:opacity-55",
|
||||
{
|
||||
variants: {
|
||||
state: {
|
||||
default: "[@media(hover:hover)]:hover:border-[var(--surface-border)] [@media(hover:hover)]:hover:bg-[var(--surface-reading)] [@media(hover:hover)]:hover:text-[var(--action-blue-hover)] focus-visible:border-[var(--surface-border)] focus-visible:bg-[var(--surface-reading)] focus-visible:text-[var(--action-blue-hover)] data-[highlighted]:border-[var(--surface-border)] data-[highlighted]:bg-[var(--surface-reading)] data-[highlighted]:text-[var(--action-blue-hover)]",
|
||||
selected: "[@media(hover:hover)]:hover:border-[var(--action-border-hover)] focus-visible:border-[var(--action-border-hover)] data-[highlighted]:border-[var(--action-border-hover)]",
|
||||
primary: "[@media(hover:hover)]:hover:border-[var(--action-blue-hover)] [@media(hover:hover)]:hover:bg-[var(--action-blue-hover)] [@media(hover:hover)]:hover:text-white focus-visible:border-[var(--action-blue-hover)] focus-visible:bg-[var(--action-blue-hover)] focus-visible:text-white data-[highlighted]:border-[var(--action-blue-hover)] data-[highlighted]:bg-[var(--action-blue-hover)] data-[highlighted]:text-white",
|
||||
danger: "[@media(hover:hover)]:hover:border-[var(--status-danger-border)] [@media(hover:hover)]:hover:bg-[var(--status-danger-soft)] [@media(hover:hover)]:hover:text-[var(--status-danger-foreground)] focus-visible:border-[var(--status-danger-border)] focus-visible:bg-[var(--status-danger-soft)] focus-visible:text-[var(--status-danger-foreground)] data-[highlighted]:border-[var(--status-danger-border)] data-[highlighted]:bg-[var(--status-danger-soft)] data-[highlighted]:text-[var(--status-danger-foreground)]"
|
||||
}
|
||||
},
|
||||
defaultVariants: { state: "default" }
|
||||
}
|
||||
);
|
||||
|
||||
export const MAP_CONTROL_HOVER_CLASS_NAME = mapOptionInteractionVariants();
|
||||
|
||||
export const MAP_ACTION_ROW_INTERACTION_CLASS_NAME = [
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
"surface-control border border-transparent text-slate-700",
|
||||
// Radix menu items receive focus while the pointer highlights a row.
|
||||
"focus:bg-[var(--surface-reading)] focus:text-[var(--action-blue-hover)]",
|
||||
mapOptionInteractionVariants()
|
||||
].join(" ");
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { LucideIcon } from "lucide-react";
|
||||
import { Copy, Ruler, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/shared/ui/cn";
|
||||
import {
|
||||
mapOptionInteractionVariants,
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_READABLE_RADIUS_CLASS_NAME,
|
||||
@@ -91,16 +92,17 @@ export function MapMeasurePanel({
|
||||
<button
|
||||
key={mode.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-pressed={mode.id === activeModeId}
|
||||
disabled={mode.disabled}
|
||||
onClick={() => onSelectMode?.(mode.id)}
|
||||
className={cn(
|
||||
"flex h-10 min-w-0 touch-manipulation items-center justify-center gap-1.5 px-2 text-xs font-semibold transition-[color,background-color,box-shadow,transform] duration-150 ease-out focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--action-focus-offset)] active:scale-95 motion-reduce:transition-none",
|
||||
"flex h-10 min-w-0 touch-manipulation items-center justify-center gap-1.5 border border-transparent px-2 text-xs font-semibold",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
mode.id === activeModeId
|
||||
? "bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "text-slate-600 [@media(hover:hover)]:hover:bg-[var(--surface-reading)]/85 [@media(hover:hover)]:hover:text-[var(--action-blue-hover)]",
|
||||
mode.disabled && "cursor-not-allowed opacity-45 active:scale-100"
|
||||
: "text-slate-600",
|
||||
mapOptionInteractionVariants({ state: mode.id === activeModeId ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
<mode.icon size={15} strokeWidth={2} aria-hidden="true" className="shrink-0" />
|
||||
@@ -134,14 +136,16 @@ export function MapMeasurePanel({
|
||||
<button
|
||||
key={unit.id}
|
||||
type="button"
|
||||
data-map-option=""
|
||||
aria-pressed={unit.id === activeUnitId}
|
||||
onClick={() => onSelectUnit?.(unit.id)}
|
||||
className={cn(
|
||||
"h-10 min-w-10 touch-manipulation px-2 text-xs font-semibold transition-[color,background-color,box-shadow,transform] duration-150 ease-out focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-1 focus-visible:ring-offset-[var(--action-focus-offset)] active:scale-95 motion-reduce:transition-none",
|
||||
"h-10 min-w-10 touch-manipulation border border-transparent px-2 text-xs font-semibold",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
unit.id === activeUnitId
|
||||
? "bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]"
|
||||
: "text-slate-600 [@media(hover:hover)]:hover:bg-[var(--surface-reading)]/85 [@media(hover:hover)]:hover:text-[var(--action-blue-hover)]"
|
||||
: "text-slate-600",
|
||||
mapOptionInteractionVariants({ state: unit.id === activeUnitId ? "selected" : "default" })
|
||||
)}
|
||||
>
|
||||
{unit.label}
|
||||
@@ -228,28 +232,27 @@ function MeasureActionButton({
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-map-option=""
|
||||
title={action.description}
|
||||
data-state={active ? "active" : undefined}
|
||||
disabled={action.disabled}
|
||||
onClick={action.onClick}
|
||||
className={cn(
|
||||
"flex w-full touch-manipulation items-center gap-2 font-semibold transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-out focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-[var(--action-focus-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--action-focus-offset)] active:scale-[0.98] motion-reduce:transition-none",
|
||||
"flex w-full touch-manipulation items-center gap-2 font-semibold",
|
||||
primary ? "h-12 justify-start border px-2.5 text-sm" : "h-10 justify-center border text-xs",
|
||||
MAP_COMPACT_RADIUS_CLASS_NAME,
|
||||
primary
|
||||
? active
|
||||
? "border-[var(--palette-brand)] bg-[var(--surface-reading)] text-slate-950 shadow-[0_3px_10px_rgba(37,99,235,0.14)] ring-1 ring-inset ring-[var(--surface-border)] [@media(hover:hover)]:hover:border-[var(--palette-brand)] [@media(hover:hover)]:hover:bg-[var(--action-soft)]/70"
|
||||
: action.variant === "primary"
|
||||
? "border-[var(--surface-divider)] bg-[var(--surface-reading)]/90 text-slate-900 shadow-xs [@media(hover:hover)]:hover:border-[var(--surface-border)] [@media(hover:hover)]:hover:bg-[var(--surface-reading)]"
|
||||
: "border-slate-800 bg-slate-800 text-white shadow-xs [@media(hover:hover)]:hover:border-slate-900 [@media(hover:hover)]:hover:bg-slate-900"
|
||||
? "border-[var(--palette-brand)] bg-[var(--surface-reading)] text-slate-950 shadow-[0_3px_10px_rgba(37,99,235,0.14)] ring-1 ring-inset ring-[var(--surface-border)]"
|
||||
: "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-xs"
|
||||
: cn(
|
||||
MAP_READABLE_SURFACE_CLASS_NAME,
|
||||
danger
|
||||
? "text-red-600 [@media(hover:hover)]:hover:border-red-200 [@media(hover:hover)]:hover:bg-red-50"
|
||||
: "text-slate-700 [@media(hover:hover)]:hover:border-[var(--surface-border)] [@media(hover:hover)]:hover:text-[var(--action-blue-hover)]"
|
||||
? "text-[var(--status-danger-foreground)]"
|
||||
: "text-slate-700"
|
||||
),
|
||||
action.disabled &&
|
||||
"cursor-not-allowed opacity-45 shadow-none active:scale-100 [@media(hover:hover)]:hover:border-[var(--surface-divider)] [@media(hover:hover)]:hover:bg-transparent [@media(hover:hover)]:hover:text-slate-700"
|
||||
mapOptionInteractionVariants({ state: danger ? "danger" : active ? "selected" : primary ? "primary" : "default" }),
|
||||
action.disabled && "shadow-none"
|
||||
)}
|
||||
>
|
||||
{primary ? (
|
||||
@@ -258,7 +261,7 @@ function MeasureActionButton({
|
||||
"grid h-8 w-8 shrink-0 place-items-center rounded-lg",
|
||||
active
|
||||
? "bg-[var(--action-blue)] text-white"
|
||||
: "bg-[var(--surface-reading)] text-slate-700 ring-1 ring-inset ring-[var(--surface-divider)]"
|
||||
: "bg-[var(--surface-reading)]/16 text-white"
|
||||
)}
|
||||
>
|
||||
<Icon size={15} strokeWidth={2.2} aria-hidden="true" />
|
||||
@@ -269,7 +272,7 @@ function MeasureActionButton({
|
||||
{primary ? (
|
||||
<span className="min-w-0 flex-1 text-left">
|
||||
<span className="block truncate text-sm font-semibold leading-4">{action.label}</span>
|
||||
<span className="mt-0.5 block truncate text-[10px] font-medium leading-3 text-[var(--ink-muted)]">
|
||||
<span className={cn("mt-0.5 block truncate text-[10px] font-medium leading-3", active ? "text-[var(--ink-muted)]" : "text-white/85")}>
|
||||
{action.description}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from "@/shared/ui/tooltip";
|
||||
import {
|
||||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
mapOptionInteractionVariants,
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME
|
||||
@@ -70,9 +71,9 @@ export function MapToolbar({
|
||||
"relative grid h-10 w-10 shrink-0 place-items-center border border-transparent text-slate-700",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
tool.active
|
||||
? "border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]"
|
||||
? cn("border-[var(--action-blue)] bg-[var(--action-blue)] text-white shadow-[var(--action-shadow-primary)]", mapOptionInteractionVariants({ state: "selected" }))
|
||||
: MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
tool.disabled && "text-[var(--ink-muted)] opacity-45 hover:bg-transparent hover:text-[var(--ink-muted)] hover:ring-0"
|
||||
tool.disabled && "text-[var(--ink-muted)] opacity-45"
|
||||
)}
|
||||
>
|
||||
<Icon size={20} strokeWidth={2.15} aria-hidden="true" />
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "./camera-actions";
|
||||
import {
|
||||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
mapOptionInteractionVariants,
|
||||
MAP_CONTROL_RADIUS_CLASS_NAME,
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
MAP_CONTROL_SURFACE_CLASS_NAME
|
||||
@@ -158,9 +159,9 @@ function ControlButton({
|
||||
"grid h-10 w-10 place-items-center border border-transparent",
|
||||
MAP_ICON_CELL_RADIUS_CLASS_NAME,
|
||||
"bg-transparent text-slate-700",
|
||||
MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
pressed ? mapOptionInteractionVariants({ state: "selected" }) : MAP_CONTROL_HOVER_CLASS_NAME,
|
||||
pressed && "bg-[var(--action-soft)] text-[var(--action-blue-hover)]",
|
||||
disabled && "text-[var(--ink-muted)] opacity-45 hover:bg-transparent hover:text-[var(--ink-muted)]"
|
||||
disabled && "text-[var(--ink-muted)] opacity-45"
|
||||
)}
|
||||
>
|
||||
{Icon ? <Icon size={18} strokeWidth={2.2} aria-hidden="true" /> : content}
|
||||
|
||||
Reference in New Issue
Block a user