MioUI

Status

状态圆点与胶囊:运行中带呼吸脉冲,离线、延迟、空闲各有语义色。

站点监控 实时
  • 主站
    mio.example
    42 ms运行中
  • 文档
    docs.mio.example
    1280 ms延迟
  • 旧版 API
    api-v1.mio.example
    —离线
  • 预发布
    staging.mio.example
    96 ms空闲

安装

components/ui/status.tsx
'use client';
import { motion } from 'motion/react';
import type { HTMLAttributes, ReactNode } from 'react';
import { twMerge } from 'tailwind-merge';
import { useReducedMotionPref } from '@/lib/use-reduced-motion';

export type Status = 'online' | 'offline' | 'degraded' | 'delay' | 'idle';

const dotColor: Record<Status, string> = {
  online: 'bg-success',
  offline: 'bg-danger',
  degraded: 'bg-warning',
  delay: 'bg-warning',
  idle: 'bg-text-muted',
};
const badgeTone: Record<Status, string> = {
  online: 'bg-success/12 text-success',
  offline: 'bg-danger/12 text-danger',
  degraded: 'bg-warning/14 text-warning',
  delay: 'bg-warning/14 text-warning',
  idle: 'bg-ghost-selected text-text-secondary',
};
const defaultLabel: Record<Status, string> = {
  online: 'Online',
  offline: 'Offline',
  degraded: 'Degraded',
  delay: 'Delayed',
  idle: 'Idle',
};
const dotSize = { sm: 'size-1.5', md: 'size-2', lg: 'size-2.5' };

export interface StatusDotProps {
  status: Status;
  size?: keyof typeof dotSize;
  /** 呼吸脉冲环;默认仅 online 开启。减弱动效时始终关闭 */
  pulse?: boolean;
  /** 提供后作为读屏文本(role="img"),否则 aria-hidden */
  label?: string;
  className?: string;
}

export function StatusDot({ status, size = 'md', pulse, label, className }: StatusDotProps) {
  const reduce = useReducedMotionPref();
  const showPulse = !reduce && (pulse ?? status === 'online');
  return (
    <span
      role={label ? 'img' : undefined}
      aria-label={label}
      aria-hidden={label ? undefined : true}
      className={twMerge('relative inline-flex shrink-0', dotSize[size], className)}
    >
      {showPulse && (
        <motion.span
          className={twMerge('absolute inset-0 rounded-full', dotColor[status])}
          initial={{ scale: 1, opacity: 0.55 }}
          animate={{ scale: 2.6, opacity: 0 }}
          transition={{ duration: 2.4, ease: [0.4, 0, 0.6, 1], repeat: Infinity }}
        />
      )}
      <span className={twMerge('relative inline-flex size-full rounded-full', dotColor[status])} />
    </span>
  );
}

export interface StatusBadgeProps extends HTMLAttributes<HTMLSpanElement> {
  status: Status;
  /** 文本;省略时使用英文默认值(Online / Offline / Degraded / Delayed / Idle) */
  children?: ReactNode;
  pulse?: boolean;
}

/** 胶囊:圆点 + 文本 */
export function StatusBadge({ status, children, pulse, className, ...p }: StatusBadgeProps) {
  return (
    <span
      className={twMerge(
        'inline-flex h-6 items-center gap-1.5 rounded-full px-2.5 text-[12px] font-medium whitespace-nowrap',
        badgeTone[status],
        className,
      )}
      {...p}
    >
      <StatusDot status={status} size="sm" pulse={pulse} />
      {children ?? defaultLabel[status]}
    </span>
  );
}

用法

import { StatusDot, StatusBadge } from "@/components/ui/status";

<StatusDot status="online" label="运行中" />
<StatusBadge status="delay">延迟</StatusBadge>
属性类型默认说明
statusonline offline degraded delay idle—语义状态
sizesm md lgmd圆点尺寸
pulseboolean仅 online呼吸环;减弱动效时关闭
labelstring—读屏文本

本页目录