{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "scroll-button", "title": "Scroll Button", "description": "Buttons that scroll the window . Use for quick page navigation. | ウィンドウを上下左右にスクロールするボタン。ページ内ナビゲーションに。", "dependencies": [ "lucide-react" ], "registryDependencies": [ "button", "tooltip" ], "files": [ { "path": "components/button/scroll-button.tsx", "content": "\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useEffect,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport {\n ArrowDown,\n ArrowUp,\n ArrowLeft,\n ArrowRight,\n ArrowUpToLine,\n ArrowDownToLine,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\ntype ScrollDirection = \"up\" | \"down\" | \"left\" | \"right\" | \"top\" | \"bottom\";\n\ntype ButtonVariant =\n | \"outline\"\n | \"ghost\"\n | \"default\"\n | \"secondary\"\n | \"destructive\"\n | \"link\";\n\ninterface ScrollButtonState {\n scrollTop: number;\n scrollX: number;\n}\n\ninterface ScrollButtonActions {\n scrollTo: (direction: ScrollDirection) => void;\n}\n\ninterface ScrollButtonContextValue {\n state: ScrollButtonState;\n actions: ScrollButtonActions;\n}\n\nconst ScrollButtonContext = createContext(\n null,\n);\n\nfunction useScrollButton() {\n const ctx = useContext(ScrollButtonContext);\n if (!ctx) {\n throw new Error(\n \"ScrollButton components must be used within ScrollButton.Provider\",\n );\n }\n return ctx;\n}\n\n// Provider: holds scroll state, consumers compose around it\nfunction ScrollButtonProvider({ children }: { children: ReactNode }) {\n const [scrollTop, setScrollTop] = useState(0);\n const [scrollX, setScrollX] = useState(0);\n\n useEffect(() => {\n const handleScroll = () => {\n setScrollTop(window.scrollY);\n setScrollX(window.scrollX ?? window.pageXOffset);\n };\n window.addEventListener(\"scroll\", handleScroll);\n handleScroll();\n return () => window.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n const scrollTo = useCallback((direction: ScrollDirection) => {\n const vp = window.visualViewport;\n if (!vp) return;\n\n switch (direction) {\n case \"up\":\n window.scrollTo({\n top: window.scrollY - vp.height,\n behavior: \"smooth\",\n });\n break;\n case \"down\":\n window.scrollTo({\n top: window.scrollY + vp.height,\n behavior: \"smooth\",\n });\n break;\n case \"left\":\n window.scrollTo({\n left: window.scrollX - vp.width,\n behavior: \"smooth\",\n });\n break;\n case \"right\":\n window.scrollTo({\n left: window.scrollX + vp.width,\n behavior: \"smooth\",\n });\n break;\n case \"top\":\n window.scrollTo({ top: 0, behavior: \"smooth\" });\n break;\n case \"bottom\":\n window.scrollTo({\n top: document.body.scrollHeight,\n behavior: \"smooth\",\n });\n break;\n }\n }, []);\n\n const value: ScrollButtonContextValue = {\n state: { scrollTop, scrollX },\n actions: { scrollTo },\n };\n\n return (\n \n {children}\n \n );\n}\n\n// Internal trigger: receives direction and shared props\ninterface ScrollButtonTriggerProps {\n direction: ScrollDirection;\n variant?: ButtonVariant;\n className?: string;\n tooltip?: string;\n icon: ReactNode;\n children?: ReactNode;\n}\n\nfunction ScrollButtonTrigger({\n direction,\n variant = \"outline\",\n className = \"\",\n tooltip,\n icon,\n children,\n}: ScrollButtonTriggerProps) {\n const { actions } = useScrollButton();\n\n return (\n \n \n actions.scrollTo(direction)}\n variant={variant}\n className={cn(\"group cursor-pointer\", className)}\n >\n {icon}\n {children}\n \n \n {tooltip && (\n \n

{tooltip}

\n
\n )}\n
\n );\n}\n\nfunction getIconClassName(\n direction: ScrollDirection,\n iconClassName?: string\n): string {\n const iconVariants = {\n \"size-4 transition-transform duration-300\": true,\n \"group-hover:-translate-y-0.5\": direction === \"up\" || direction === \"top\",\n \"group-hover:translate-y-0.5\": direction === \"down\" || direction === \"bottom\",\n \"group-hover:-translate-x-0.5\": direction === \"left\",\n \"group-hover:translate-x-0.5\": direction === \"right\",\n };\n return cn(iconVariants, iconClassName);\n}\n\n// Explicit variant components – self-documenting, no mode props\ninterface VariantProps {\n variant?: ButtonVariant;\n className?: string;\n iconClassName?: string;\n tooltip?: string;\n children?: ReactNode;\n}\n\nfunction ScrollButtonDown({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll down\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\nfunction ScrollButtonUp({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll up\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\nfunction ScrollButtonLeft({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll left\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\nfunction ScrollButtonRight({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll right\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\nfunction ScrollButtonTop({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll to top\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\nfunction ScrollButtonBottom({\n variant,\n className,\n iconClassName,\n tooltip = \"Scroll to bottom\",\n children,\n}: VariantProps) {\n return (\n \n }\n >\n {children}\n \n \n );\n}\n\n// Compound component API\nexport const ScrollButton = {\n Provider: ScrollButtonProvider,\n Down: ScrollButtonDown,\n Up: ScrollButtonUp,\n Left: ScrollButtonLeft,\n Right: ScrollButtonRight,\n Top: ScrollButtonTop,\n Bottom: ScrollButtonBottom,\n};\n\n// Default export for backward compatibility – maps to ScrollButton.Down\nexport default ScrollButton.Down;\n", "type": "registry:ui" } ], "type": "registry:ui" }