Token导航 LogoToken导航

Agent Skill · 前端设计

mobile-components(移动组件)

mobile-components 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

mobile-components 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 6,551。

总安装 490周安装 20GitHub Stars 777下载 158核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:mobile-components(移动组件)
来源仓库:https://github.com/dadbodgeoff/drift
仓库路径:skills/mobile-components
安装命令:
npx skills add https://github.com/dadbodgeoff/drift --skill mobile-components
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dadbodgeoff/drift --skill mobile-components

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

用于处理 GitHub 仓库、Issue 和 Pull Request 协作信息。适合在代码变更或协作事项整理场景中使用。可结合来源仓库 README 核验具体用法和功能范围。安装前建议确认权限范围和是否会触发文件读写操作。需注意维护状态和网络访问限制。

mobile-components 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/mobile-components

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/dadbodgeoff/drift --skill mobile-components;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

mobile-components 怎么安装?

在终端执行 npx skills add https://github.com/dadbodgeoff/drift --skill mobile-components。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

mobile-components 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

mobile-components 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

mobile-components 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

mobile-components 用的人多吗?

公开数据是累计安装 490 次,近一周安装 20 次,下载 158 次,GitHub 777 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

mobile-components 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
mobile-components

移动组件 · 本页

490777需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Mobile Components

Touch-optimized UI components for mobile-first experiences.

When to Use This Skill

  • Building mobile-first or responsive web applications
  • Need native-feeling mobile interactions
  • Implementing bottom sheets, pull-to-refresh, or swipe actions
  • Desktop components feel awkward on mobile

Core Concepts

Mobile UX differs from desktop: bottom navigation is reachable, sheets slide up from bottom, touch targets need 44px minimum, and gestures replace clicks.

Implementation

TypeScript/React

// Bottom Navigation
interface NavItem {
  href: string;
  label: string;
  icon: string;
}

function MobileNav({ items }: { items: NavItem[] }) {
  const pathname = usePathname();

  return (
    <nav className="fixed bottom-0 left-0 right-0 bg-neutral-800 border-t border-neutral-700 z-30 md:hidden safe-area-bottom">
      <div className="flex items-center justify-around py-2">
        {items.map((item) => {
          const isActive = pathname.startsWith(item.href);
          return (
            <Link
              key={item.href}
              href={item.href}
              className={`flex flex-col items-center gap-1 px-4 py-2 min-w-[64px] ${
                isActive ? 'text-primary-400' : 'text-neutral-500'
              }`}
            >
              <span className="text-xl">{item.icon}</span>
              <span className="text-xs">{item.label}</span>
            </Link>
          );
        })}
      </div>
    </nav>
  );
}

// Bottom Sheet
interface BottomSheetProps {
  isOpen: boolean;
  onClose: () => void;
  children: ReactNode;
  title?: string;
}

function BottomSheet({ isOpen, onClose, children, title }: BottomSheetProps) {
  const [dragY, setDragY] = useState(0);
  const [isDragging, setIsDragging] = useState(false);
  const startY = useRef(0);

  useEffect(() => {
    document.body.style.overflow = isOpen ? 'hidden' : '';
    return () => { document.body.style.overflow = ''; };
  }, [isOpen]);

  const handleTouchStart = (e: React.TouchEvent) => {
    startY.current = e.touches[0].clientY;
    setIsDragging(true);
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    if (!isDragging) return;
    const diff = e.touches[0].clientY - startY.current;
    if (diff > 0) setDragY(diff);
  };

  const handleTouchEnd = () => {
    setIsDragging(false);
    if (dragY > 100) onClose();
    setDragY(0);
  };

  if (!isOpen) return null;

  return (
    <>
      <div className="fixed inset-0 bg-black/60 z-40" onClick={onClose} />
      <div
        className="fixed bottom-0 left-0 right-0 bg-neutral-800 rounded-t-2xl z-50 max-h-[90vh]"
        style={{
          transform: `translateY(${dragY}px)`,
          transition: isDragging ? 'none' : 'transform 0.3s ease-out',
        }}
      >
        <div
          className="flex justify-center py-3 cursor-grab touch-none"
          onTouchStart={handleTouchStart}
          onTouchMove={handleTouchMove}
          onTouchEnd={handleTouchEnd}
        >
          <div className="w-10 h-1 bg-neutral-600 rounded-full" />
        </div>
        {title && (
          <div className="px-4 pb-3 border-b border-neutral-700 flex justify-between">
            <h2 className="text-lg font-semibold">{title}</h2>
            <button onClick={onClose} aria-label="Close">✕</button>
          </div>
        )}
        <div className="overflow-y-auto p-4">{children}</div>
      </div>
    </>
  );
}

// Pull to Refresh
function PullToRefresh({
  onRefresh,
  children
}: {
  onRefresh: () => Promise<void>;
  children: ReactNode;
}) {
  const [isPulling, setIsPulling] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [pullDistance, setPullDistance] = useState(0);
  const startY = useRef(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const THRESHOLD = 80;

  const handleTouchStart = (e: React.TouchEvent) => {
    if (containerRef.current?.scrollTop === 0) {
      startY.current = e.touches[0].clientY;
      setIsPulling(true);
    }
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    if (!isPulling || isRefreshing) return;
    const diff = e.touches[0].clientY - startY.current;
    if (diff > 0) {
      setPullDistance(Math.min(diff * 0.5, THRESHOLD * 1.5));
    }
  };

  const handleTouchEnd = async () => {
    if (!isPulling) return;
    if (pullDistance >= THRESHOLD && !isRefreshing) {
      setIsRefreshing(true);
      setPullDistance(THRESHOLD);
      try { await onRefresh(); }
      finally { setIsRefreshing(false); }
    }
    setIsPulling(false);
    setPullDistance(0);
  };

  return (
    <div
      ref={containerRef}
      className="h-full overflow-y-auto"
      onTouchStart={handleTouchStart}
      onTouchMove={handleTouchMove}
      onTouchEnd={handleTouchEnd}
    >
      <div
        className="flex items-center justify-center overflow-hidden"
        style={{ height: pullDistance }}
      >
        {isRefreshing ? (
          <div className="animate-spin w-6 h-6 border-2 border-primary-500 border-t-transparent rounded-full" />
        ) : (
          <div style={{ transform: `rotate(${(pullDistance / THRESHOLD) * 180}deg)` }}>↓</div>
        )}
      </div>
      {children}
    </div>
  );
}

// Swipeable Row
function SwipeableRow({
  children,
  onSwipeLeft,
  onSwipeRight,
  leftAction,
  rightAction,
}: {
  children: ReactNode;
  onSwipeLeft?: () => void;
  onSwipeRight?: () => void;
  leftAction?: ReactNode;
  rightAction?: ReactNode;
}) {
  const [translateX, setTranslateX] = useState(0);
  const startX = useRef(0);
  const isDragging = useRef(false);
  const THRESHOLD = 80;

  const handleTouchStart = (e: React.TouchEvent) => {
    startX.current = e.touches[0].clientX;
    isDragging.current = true;
  };

  const handleTouchMove = (e: React.TouchEvent) => {
    if (!isDragging.current) return;
    const diff = e.touches[0].clientX - startX.current;
    setTranslateX(Math.max(-100, Math.min(100, diff)));
  };

  const handleTouchEnd = () => {
    isDragging.current = false;
    if (translateX > THRESHOLD) onSwipeRight?.();
    else if (translateX < -THRESHOLD) onSwipeLeft?.();
    setTranslateX(0);
  };

  return (
    <div className="relative overflow-hidden">
      {leftAction && (
        <div className="absolute left-0 top-0 bottom-0 flex items-center px-4 bg-green-600">
          {leftAction}
        </div>
      )}
      {rightAction && (
        <div className="absolute right-0 top-0 bottom-0 flex items-center px-4 bg-red-600">
          {rightAction}
        </div>
      )}
      <div
        className="relative bg-neutral-800"
        style={{
          transform: `translateX(${translateX}px)`,
          transition: isDragging.current ? 'none' : 'transform 0.2s ease-out',
        }}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
      >
        {children}
      </div>
    </div>
  );
}

Usage Examples

export default function DashboardPage() {
  const [sheetOpen, setSheetOpen] = useState(false);

  const handleRefresh = async () => {
    await fetchData();
  };

  return (
    <div className="min-h-screen pb-20 md:pb-0">
      <PullToRefresh onRefresh={handleRefresh}>
        <div className="p-4">
          {items.map((item) => (
            <SwipeableRow
              key={item.id}
              onSwipeLeft={() => deleteItem(item.id)}
              rightAction={<span>🗑️</span>}
            >
              <ListItem onClick={() => setSheetOpen(true)}>
                {item.title}
              </ListItem>
            </SwipeableRow>
          ))}
        </div>
      </PullToRefresh>

      <BottomSheet isOpen={sheetOpen} onClose={() => setSheetOpen(false)} title="Details">
        <p>Sheet content here</p>
      </BottomSheet>

      <MobileNav items={[
        { href: '/dashboard', label: 'Home', icon: '🏠' },
        { href: '/settings', label: 'Settings', icon: '⚙️' },
      ]} />
    </div>
  );
}

Best Practices

  1. Minimum touch target: 44x44px (Apple) / 48x48dp (Google)
  2. Use safe-area-bottom for bottom navigation on notched devices
  3. Always provide visual feedback on touch (active states)
  4. Lock body scroll when sheets are open
  5. Use touch-none on drag handles to prevent scroll interference

Common Mistakes

  • Touch targets too small (causes mis-taps)
  • Not handling safe areas (content hidden behind notch)
  • Missing active states (no touch feedback)
  • Forgetting to unlock body scroll on unmount
  • Not debouncing pull-to-refresh

Related Patterns

  • design-tokens (consistent spacing/sizing)
  • pwa-setup (full mobile experience)

平台分布

Claude

32.68%
按下载量换算52

Codex

31.67%
按下载量换算50

Cursor

17.24%
按下载量换算27

Gemini CLI

8.66%
按下载量换算14

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills