GoodCase.aiGoodCase.aiAI提示语案例合集
排行榜案例Skills创作者收藏投稿早报
中文EN
Agent 接入 →
GoodCase.ai · AI提示语案例合集案例库加载中。
更新日志反馈
GoodCase.aiGoodCase.aiAI提示语案例合集
排行榜案例Skills创作者收藏投稿早报
中文EN
Agent 接入 →
AI 编程(UI)Aceternity UI证据 L1#source-aceternity#prompt-public#not-rerun#license-review

Notch

AU创作者Aceternity UI →

// components/ui/notch.tsx "use client"; import React, { useEffect, useId, useRef, useState } from "react"; import { AnimatePresence, motion } from "motion/react"; import { cn } from "@/lib/utils"; export type NotchOption = { /** Stable identifier passed back in callbacks. */ id: string; /** What renders for this option. Can be text or any node. */ label: React.ReactNode; /** Optional leading node (icon, swatch, etc.) shown before the label. */ icon?: React.ReactNode; }; export type NotchItem = { /** Stable identifier for the group. */ id: string; /** Trigger label shown in the bar. */ label: React.ReactNode; /** Optional leading icon for the trigger. */ icon?: React.ReactNode; /** The choices revealed when the group is opened. */ options: NotchOption[]; /** Uncontrolled initial selected option id. */ defaultValue?: string; /** Controlled selected option id. */ value?: string; /** Show …

查看原始来源 ↗
Notch

Prompt

// components/ui/notch.tsx
"use client";

import React, { useEffect, useId, useRef, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
import { cn } from "@/lib/utils";

export type NotchOption = {
  /** Stable identifier passed back in callbacks. */
  id: string;
  /** What renders for this option. Can be text or any node. */
  label: React.ReactNode;
  /** Optional leading node (icon, swatch, etc.) shown before the label. */
  icon?: React.ReactNode;
};

export type NotchItem = {
  /** Stable identifier for the group. */
  id: string;
  /** Trigger label shown in the bar. */
  label: React.ReactNode;
  /** Optional leading icon for the trigger. */
  icon?: React.ReactNode;
  /** The choices revealed when the group is opened. */
  options: NotchOption[];
  /** Uncontrolled initial selected option id. */
  defaultValue?: string;
  /** Controlled selected option id. */
  value?: string;
  /** Show the selected value next to the trigger label. Overrides `showSelectedValue`. */
  showValue?: boolean;
  /** Fires with the selected option whenever it changes. */
  onChange?: (optionId: string, option: NotchOption) => void;
};

export interface NotchProps {
  /** The groups shown inside the notch. Pass one or many. */
  items: NotchItem[];
  /** Pin the notch to the top or bottom of the viewport. */
  position?: "top" | "bottom";
  /** Horizontal alignment of the floating notch. */
  align?: "start" | "center" | "end";
  /** Fired for any group change, in addition to the per-item callback. */
  onItemChange?: (
    itemId: string,
    optionId: string,
    option: NotchOption,
  ) => void;
  /** Close the panel after selecting an option. */
  closeOnSelect?: boolean;
  /** Show each group's selected value next to its trigger label. */
  showSelectedValue?: boolean;
  /** Render dotted dividers between groups. */
  showDividers?: boolean;
  /** Highlight color for the selected option. Any CSS color or variable. */
  accentColor?: string;
  /** Distance from the pinned edge, in pixels. */
  offset?: number;
  /** Play the entrance animation on mount. */
  reveal?: boolean;
  /** Classes applied to the floating shell. */
  className?: string;
  /** Classes applied to every trigger. */
  itemClassName?: string;
  /** Classes applied to the options panel. */
  panelClassName?: string;
}

const SHELL_SPRING = { type: "spring" as const, stiffness: 380, damping: 34 };

const LIST_VARIANTS = {
  hidden: {},
  visible: { transition: { staggerChildren: 0.045, delayChildren: 0.08 } },
};

const OPTION_VARIANTS = {
  hidden: { opacity: 0, y: -10, filter: "blur(4px)" },
  visible: {
    opacity: 1,
    y: 0,
    filter: "blur(0px)",
    transition: { type: "spring" as const, stiffness: 420, damping: 30 },
  },
};

function NotchDivider() {
  return (
    <span
      aria-hidden
      className="mx-0.5 h-5 w-px shrink-0 self-center"
      style={{
        backgroundImage:
          "repeating-linear-gradient(180deg, rgba(255,255,255,0.35) 0px, rgba(255,255,255,0.35) 1px, transparent 1px, transparent 5px)",
        backgroundSize: "1px 4px",
        backgroundRepeat: "repeat-y",
      }}
    />
  );
}

export const Notch = ({
  items,
  position = "bottom",
  align = "center",
  onItemChange,
  closeOnSelect = true,
  showSelectedValue = true,
  showDividers = true,
  accentColor = "var(--color-blue-500, #3b82f6)",
  offset = 16,
  reveal = true,
  className,
  itemClassName,
  panelClassName,
}: NotchProps) => {
  const shellRef = useRef<HTMLDivElement>(null);
  const shellLayoutId = useId();
  const [openItemId, setOpenItemId] = useState<string | null>(null);
  const [internalSelected, setInternalSelected] = useState<
    Record<string, string>
  >(() => {
    const map: Record<string, string> = {};
    for (const item of items) {
      if (item.value === undefined) {
        map[item.id] = item.defaultValue ?? item.options[0]?.id ?? "";
      }
    }
    return map;
  });

  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === "Escape") setOpenItemId(null);
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  useEffect(() => {
    if (!openItemId) return;
    function onPointerDown(e: PointerEvent) {
      if (!shellRef.current?.contains(e.target as Node)) setOpenItemId(null);
    }
    document.addEventListener("pointerdown", onPointerDown);
    return () => document.removeEventListener("pointerdown", onPointerDown);
  }, [openItemId]);

  const getSelectedId = (item: NotchItem) =>
    item.value ?? internalSelected[item.id] ?? item.options[0]?.id;

  const getSelectedOption = (item: NotchItem) =>
    item.options.find((o) => o.id === getSelectedId(item));

  const handleSelect = (item: NotchItem, option: NotchOption) => {
    if (item.value === undefined) {
      setInternalSelected((prev) => ({ ...prev, [item.id]: option.id }));
    }
    item.onChange?.(option.id, option);
    onItemChange?.(item.id, option.id, option);
    if (closeOnSelect) setOpenItemId(null);
  };

  const alignClass =
    align === "start"
      ? "justify-start"
      : align === "end"
        ? "justify-end"
        : "justify-center";

  const edgeOffset = (offset + 20) * (position === "top" ? -1 : 1);
  const openItem = items.find((i) => i.id === openItemId) ?? null;

  const optionsPanel = openItem ? (
    <motion.div
      key={openItem.id}
      role="listbox"
      aria-label={
        typeof openItem.label === "string" ? openItem.label : openItem.id
      }
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.15 }}
      className={cn("w-fit", panelClassName)}
    >
      <motion.div
        className="flex flex-col gap-1.5 p-2"
        variants={LIST_VARIANTS}
        initial="hidden"
        animate="visible"
      >
        {openItem.options.map((option) => {
          const active = option.id === getSelectedId(openItem);
          return (
            <motion.button
              key={option.id}
              role="option"
              aria-selected={active}
              type="button"
              variants={OPTION_VARIANTS}
              onClick={() => handleSelect(openItem, option)}
              className={cn(
                "flex w-full items-center justify-between gap-6 rounded-md px-3 py-2 text-left text-xs font-medium whitespace-nowrap transition-colors",
                active
                  ? "text-white"
                  : "text-neutral-300 hover:bg-white/5 hover:text-white",
              )}
              style={
                active
                  ? {
                      background: `color-mix(in oklab, ${accentColor} 85%, transparent)`,
                      boxShadow: `inset 0 0 0 1px color-mix(in oklab, ${accentColor} 40%, transparent)`,
                    }
                  : undefined
              }
            >
              <span className="flex items-center gap-2.5">
                {option.icon ? (
                  <span className="flex shrink-0 items-center justify-center">
                    {option.icon}
                  </span>
                ) : null}
                <span>{option.label}</span>
              </span>
              {active ? (
                <span
                  className="size-1.5 shrink-0 rounded-full"
                  style={{ background: accentColor }}
                />
              ) : null}
            </motion.button>
          );
        })}
      </motion.div>
    </motion.div>
  ) : (
    <motion.div
      key="__notch-triggers"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      transition={{ duration: 0.15 }}
      className="flex w-fit items-center gap-1 p-1"
    >
      {items.map((item, index) => {
        const selected = getSelectedOption(item);
        const isLast = index === items.length - 1;

        return (
          <React.Fragment key={item.id}>
            <button
              type="button"
              aria-haspopup="listbox"
              aria-expanded={false}
              onClick={() => setOpenItemId(item.id)}
              className={cn(
                "group flex items-center gap-2 rounded-full px-3 py-2 text-xs font-medium whitespace-nowrap text-neutral-300 transition-colors hover:bg-white/6 hover:text-white",
                itemClassName,
              )}
            >
              {item.icon ? (
                <span className="flex shrink-0 items-center justify-center">
                  {item.icon}
                </span>
              ) : null}
              <span className="text-neutral-100">{item.label}</span>
              {(item.showValue ?? showSelectedValue) && selected ? (
                <span className="text-neutral-400">{selected.label}</span>
              ) : null}
            </button>
            {showDividers && !isLast ? <NotchDivider /> : null}
          </React.Fragment>
        );
      })}
    </motion.div>
  );

  return (
    <div
      className={cn(
        "pointer-events-none fixed inset-x-0 z-100 flex translate-z-0 px-4",
        position === "top" ? "top-0" : "bottom-0",
        alignClass,
      )}
      style={
        position === "top"
          ? { paddingTop: `max(${offset}px, env(safe-area-inset-top))` }
          : { paddingBottom: `max(${offset}px, env(safe-area-inset-bottom))` }
      }
    >
      <motion.div
        ref={shellRef}
        layoutId={shellLayoutId}
        layout
        initial={
          reveal ? { opacity: 0, y: edgeOffset, filter: "blur(6px)" } : false
        }
        animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
        transition={SHELL_SPRING}
        className={cn(
          "pointer-events-auto flex w-fit flex-col overflow-hidden rounded-xl border border-white/10 bg-neutral-950/95 shadow-[0_12px_40px_-8px_rgba(0,0,0,0.55)] ring-1 ring-neutral-800 backdrop-blur-2xl ring-inset",
          className,
        )}
      >
        <AnimatePresence mode="popLayout" initial={false}>
          {optionsPanel}
        </AnimatePresence>
      </motion.div>
    </div>
  );
};

复用方法

关键决定

观察最终结果里,页面信息层级、模块顺序和 CTA 是否一口气讲清楚 是否同时成立。

换到你的题材

把“目标结果 → 产品名称、目标用户、页面模块和主按钮 → 约束条件”抽成模板,后续可复用到同类任务。

容易翻车

围绕 模块顺序、价值主张写法与 CTA 位置 做单变量对照,并记录模型、成本和失败结果;重复成立后再沉淀为 Skill。

建议模型

待补充模型
稳定度
成本
中
这条 Case 的经验 Skill 还没有沉淀。去反馈区催更 →
AU

创作者

Aceternity UI

Aceternity UI 长期扎根Aceternity UI,交付可运行的界面,《Parallax Hero Images》配了完整实现思路。

值得学习AI 编程(UI)
查看创作者 →

继续探索

继续看相关 Case。

查看全部 AI 编程(UI) Case →
3D 超市包装式创意机构官网
AI 编程(UI)X@Oluwaphilemon1 →

3D 超市包装式创意机构官网

@Oluwaphilemon1 使用 Claude Fable 5 · Three.js · Blender · GSAP完成的网页案例,包含公开结果、完整 Prompt 与原始来源。

Skill · 3D / 动效 HeroSkill · 个人作品集叙事

提示语

Claude Fable 5 和 GPT-5.6 很强🥵🥵🥵 下面是我如何使用 Claude 构建的 👇 提示词:「设计一个机构网站,把服务以 3D 超市商品包装的形式呈现」 使用 Three.js 和 WebGL 在浏览器中实时渲染所有产品 使用 Blender 制作包装模型,然后导出为 GLTF 以加载到 Three.js 中 使用 GSAP 实现流畅的产品旋转和悬停交互 如果你想做一个让客户感觉像是在购买高级商品的作品集,请保存这条 🛒

来源热度
46
稳定度
查看 Case →
AI 编程(UI)Aceternity UIAceternity UI →

3D Animated Pin

// components/ui/3d-pin.tsx "use client"; import React, { useState } from "react"; import { motion } from "motion/react"; import { cn } from "@/lib/utils"; export const PinContainer = ({ children, title, href, className, containerClassName, }: { children: React.ReactNode; title?: string; href?: string; className?: string; containerClassName?: string; }) => { const [transform, setTransform] = useState( "translate(-50%,-50%) rotateX(0deg)" ); const onMouseEnter = () => { setTransform("translate(-50%,-50%) rotateX(40deg) scale(0.8)"); }; const onMouseLeave = () => { setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)"); }; return ( <a className={cn( "relative group/pin z-50 cursor-pointer", containerClassName )} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} href={href || "/"} > <div style={{ perspective: "1000px", transform: "rotateX(70deg) translateZ(0deg)", }} className="…

Skill · 3D / 动效 Hero

提示语

// components/ui/3d-pin.tsx "use client"; import React, { useState } from "react"; import { motion } from "motion/react"; import { cn } from "@/lib/utils"; export const PinContainer = ({ children, title, href, className, containerClassName…

来源热度
—
稳定度
查看 Case →
3D Collectible Hero
AI 编程(UI)MotionSitesMotionSites →

3D Collectible Hero

Build a single full-viewport hero section in React + TypeScript + Vite + Tailwind CSS, using `lucide-react` for icons. The component is a character-figurine carousel called "TOONHUB". **Fonts (load in `index.html` head):** ```html <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=Anton&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" /> ``` Body font: `'Inter', sans-serif`. Display font (huge ghost text + bottom-right link): `'Anton', sans-serif`. **Image data (4 items, exact URLs and colors):** ```ts const IMAGES = [ { src: 'https://fifth-gentle-45902158.figma.site/_components/v2/4de492f6d9cf8244ad5293233e5c6f52407d42fc/1.02464a56.png', bg: '#F4845F', panel: '#F79B7F' }, { src: 'https://fifth-gentle-45902158.figma.site/_components/v2/4d…

Skill · 3D / 动效 HeroSkill · 落地页信息结构

提示语

Build a single full-viewport hero section in React + TypeScript + Vite + Tailwind CSS, using `lucide-react` for icons. The component is a character-figurine carousel called "TOONHUB". **Fonts (load in `index.html` head):** ```html <link re…

来源热度
—
稳定度
查看 Case →
GoodCase.ai · AI提示语案例合集Case 详情页把作品、方法、作者与复测证据放在同一条阅读路径里。
更新日志反馈