From 1d9c50aba6cd92567af50082daf5755969fcac5f Mon Sep 17 00:00:00 2001 From: Ravi <103035973+raviships@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:16:47 +0900 Subject: [PATCH 1/6] fix: repair website links and component setup flows --- .../content/docs/components/upload-button.mdx | 4 +- docs/public/r/avatar-dropzone-block.json | 4 +- docs/public/r/file-field-block.json | 4 +- docs/public/r/file-uploader-block.json | 4 +- docs/public/r/multi-image-uploader-block.json | 4 +- docs/public/r/progress-bar-block.json | 4 +- docs/public/r/progress-circle-block.json | 4 +- docs/public/r/registry.json | 32 ++++++------- .../public/r/single-image-dropzone-block.json | 4 +- docs/public/r/upload-button-block.json | 4 +- .../(main)/pricing/_components/pricing.tsx | 3 +- docs/src/components/ui/open-tabs.tsx | 45 ++++++++++++++++++- docs/src/lib/constants.ts | 4 +- registry.json | 32 ++++++------- 14 files changed, 96 insertions(+), 56 deletions(-) diff --git a/docs/content/docs/components/upload-button.mdx b/docs/content/docs/components/upload-button.mdx index 3ec36b89..38d6f82c 100644 --- a/docs/content/docs/components/upload-button.mdx +++ b/docs/content/docs/components/upload-button.mdx @@ -18,7 +18,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; -It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`, so you can pair them with a `FileList` or build your own list with `useUploader`. +It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`. To show a file list, also install [Multi-file](./multi-file) and add its `FileList`, or build your own list with `useUploader`. ## Installation @@ -87,7 +87,6 @@ Install or copy the component from [Installation](#installation) before using th 'use client'; import { UploadErrors } from '@/components/upload/dropzone'; -import { FileList } from '@/components/upload/multi-file'; import { UploadButton } from '@/components/upload/upload-button'; import { UploaderProvider, @@ -125,7 +124,6 @@ export function UploadButtonUsage() { Upload files setErrors([])} /> - ); } diff --git a/docs/public/r/avatar-dropzone-block.json b/docs/public/r/avatar-dropzone-block.json index d67a5e3c..7994f694 100644 --- a/docs/public/r/avatar-dropzone-block.json +++ b/docs/public/r/avatar-dropzone-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/avatar-block.tsx", "content": "'use client';\n\nimport { AvatarDropzone } from '@/components/upload/avatar-dropzone';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function AvatarDropzoneBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/avatar-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/file-field-block.json b/docs/public/r/file-field-block.json index 655e44b4..e93770a7 100644 --- a/docs/public/r/file-field-block.json +++ b/docs/public/r/file-field-block.json @@ -12,8 +12,8 @@ { "path": "examples/components/src/components/upload/blocks/file-field-block.tsx", "content": "'use client';\n\nimport { FileField } from '@/components/upload/file-field';\nimport { DOCUMENT_ACCEPT } from '@/components/upload/upload-utils';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function FileFieldBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n Resume\n \n \n \n \n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/file-field-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/file-uploader-block.json b/docs/public/r/file-uploader-block.json index fab6dd14..6cabbfec 100644 --- a/docs/public/r/file-uploader-block.json +++ b/docs/public/r/file-uploader-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/multi-file-block.tsx", "content": "'use client';\n\nimport { FileUploader } from '@/components/upload/multi-file';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function FileUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n \n
\n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/multi-file-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/multi-image-uploader-block.json b/docs/public/r/multi-image-uploader-block.json index 505f5299..d3e0c6cd 100644 --- a/docs/public/r/multi-image-uploader-block.json +++ b/docs/public/r/multi-image-uploader-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/multi-image-block.tsx", "content": "'use client';\n\nimport { ImageUploader } from '@/components/upload/multi-image';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function MultiImageUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n \n
\n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/multi-image-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/progress-bar-block.json b/docs/public/r/progress-bar-block.json index d0235832..65ac5e9d 100644 --- a/docs/public/r/progress-bar-block.json +++ b/docs/public/r/progress-bar-block.json @@ -10,8 +10,8 @@ { "path": "examples/components/src/components/upload/blocks/progress-bar-block.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { ProgressBar } from '../progress-bar';\n\nexport default function ProgressBarUsage() {\n const [progress, setProgress] = React.useState(0);\n\n React.useEffect(() => {\n const startAnimation = () => {\n const interval = setInterval(() => {\n setProgress((prevProgress) => {\n if (prevProgress >= 100) {\n clearInterval(interval);\n // Reset after 1 second when reaching 100%\n setTimeout(() => {\n setProgress(0);\n startAnimation(); // Restart the animation cycle\n }, 1000);\n return 100;\n }\n // Add a random value between 10-20 percent\n return Math.min(\n 100,\n prevProgress + Math.floor(Math.random() * 11) + 10,\n );\n });\n }, 300);\n\n return interval;\n };\n\n const interval = startAnimation();\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return (\n
\n \n
\n );\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/progress-bar-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/progress-circle-block.json b/docs/public/r/progress-circle-block.json index 86d11fd4..ea132b32 100644 --- a/docs/public/r/progress-circle-block.json +++ b/docs/public/r/progress-circle-block.json @@ -10,8 +10,8 @@ { "path": "examples/components/src/components/upload/blocks/progress-circle-block.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { ProgressCircle } from '../progress-circle';\n\nexport default function ProgressCircleUsage() {\n const [progress, setProgress] = React.useState(0);\n\n React.useEffect(() => {\n const startAnimation = () => {\n const interval = setInterval(() => {\n setProgress((prevProgress) => {\n if (prevProgress >= 100) {\n clearInterval(interval);\n // Reset after 1 second when reaching 100%\n setTimeout(() => {\n setProgress(0);\n startAnimation(); // Restart the animation cycle\n }, 1000);\n return 100;\n }\n // Add a random value between 10-20 percent\n return Math.min(\n 100,\n prevProgress + Math.floor(Math.random() * 11) + 10,\n );\n });\n }, 300);\n\n return interval;\n };\n\n const interval = startAnimation();\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return (\n
\n
\n \n
\n
\n );\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/progress-circle-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/registry.json b/docs/public/r/registry.json index 9a5189c4..2a4951af 100644 --- a/docs/public/r/registry.json +++ b/docs/public/r/registry.json @@ -208,8 +208,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-file-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/multi-file-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -225,8 +225,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/single-image-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/single-image-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -242,8 +242,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-image-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/multi-image-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -259,8 +259,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/avatar-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/avatar-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -277,8 +277,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/file-field-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/file-field-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -297,8 +297,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/upload-button-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/upload-button-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -311,8 +311,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-bar-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/progress-bar-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] }, @@ -325,8 +325,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-circle-block.tsx", - "type": "registry:block", - "target": "components/upload/blocks/progress-circle-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ] } diff --git a/docs/public/r/single-image-dropzone-block.json b/docs/public/r/single-image-dropzone-block.json index b8f7e699..30bca9d4 100644 --- a/docs/public/r/single-image-dropzone-block.json +++ b/docs/public/r/single-image-dropzone-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/single-image-block.tsx", "content": "'use client';\n\nimport { SingleImageDropzone } from '@/components/upload/single-image';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function SingleImageUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n \n \n \n \n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/single-image-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/upload-button-block.json b/docs/public/r/upload-button-block.json index 9cda7a90..8cbed335 100644 --- a/docs/public/r/upload-button-block.json +++ b/docs/public/r/upload-button-block.json @@ -16,8 +16,8 @@ { "path": "examples/components/src/components/upload/blocks/upload-button-block.tsx", "content": "'use client';\n\nimport { UploadErrors } from '@/components/upload/dropzone';\nimport { FileList } from '@/components/upload/multi-file';\nimport { UploadButton } from '@/components/upload/upload-button';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport { PaperclipIcon, UploadIcon } from 'lucide-react';\nimport * as React from 'react';\n\nexport default function UploadButtonBlock() {\n const [errors, setErrors] = React.useState([]);\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n
\n \n Upload files\n \n \n \n \n
\n setErrors([])} />\n \n
\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:block", - "target": "components/upload/blocks/upload-button-block.tsx" + "type": "registry:page", + "target": "app/page.tsx" } ], "type": "registry:block" diff --git a/docs/src/app/(main)/pricing/_components/pricing.tsx b/docs/src/app/(main)/pricing/_components/pricing.tsx index 9571eddf..8de41116 100644 --- a/docs/src/app/(main)/pricing/_components/pricing.tsx +++ b/docs/src/app/(main)/pricing/_components/pricing.tsx @@ -1,5 +1,6 @@ import { ActionButton } from '@/components/action-button'; import { env } from '@/env'; +import { SPONSOR_URL } from '@/lib/constants'; import { formatFileSize, formatNumber } from '@/lib/formatter'; import { cn } from '@/lib/utils'; import { CheckCircleIcon, HeartIcon } from 'lucide-react'; @@ -127,7 +128,7 @@ function PricingBlock(props: { function SponsorButton() { return ( - + Sponsor ); diff --git a/docs/src/components/ui/open-tabs.tsx b/docs/src/components/ui/open-tabs.tsx index 9cafdea3..78ea9140 100644 --- a/docs/src/components/ui/open-tabs.tsx +++ b/docs/src/components/ui/open-tabs.tsx @@ -4,7 +4,47 @@ import { cn } from '@/lib/utils'; import * as TabsPrimitive from '@radix-ui/react-tabs'; import * as React from 'react'; -const OpenTabs = TabsPrimitive.Root; +const OpenTabs = React.forwardRef< + React.ComponentRef, + React.ComponentPropsWithoutRef +>((props, ref) => { + const root = React.useRef(null); + + React.useEffect(() => { + let frame = 0; + function revealAnchor() { + const target = document.getElementById(window.location.hash.slice(1)); + if (!target || !root.current?.contains(target)) return; + const panel = target.closest('[role="tabpanel"]'); + if (panel?.getAttribute('data-state') !== 'inactive') return; + + const trigger = Array.from( + root.current.querySelectorAll('[role="tab"]'), + ).find((tab) => tab.getAttribute('aria-controls') === panel.id); + trigger?.focus(); + frame = requestAnimationFrame(() => target.scrollIntoView()); + } + + revealAnchor(); + window.addEventListener('hashchange', revealAnchor); + return () => { + window.removeEventListener('hashchange', revealAnchor); + cancelAnimationFrame(frame); + }; + }, []); + + return ( + { + root.current = element; + if (typeof ref === 'function') return ref(element); + if (ref) ref.current = element; + }} + /> + ); +}); +OpenTabs.displayName = TabsPrimitive.Root.displayName; const OpenTabsList = React.forwardRef< React.ComponentRef, @@ -42,8 +82,9 @@ const OpenTabsContent = React.forwardRef< >(({ className, ...props }, ref) => ( Date: Sun, 11 Oct 2026 18:20:30 +0900 Subject: [PATCH 2/6] fix: reopen manual instructions for repeated anchor clicks --- docs/src/components/ui/open-tabs.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/src/components/ui/open-tabs.tsx b/docs/src/components/ui/open-tabs.tsx index 78ea9140..1a60f32b 100644 --- a/docs/src/components/ui/open-tabs.tsx +++ b/docs/src/components/ui/open-tabs.tsx @@ -21,14 +21,34 @@ const OpenTabs = React.forwardRef< const trigger = Array.from( root.current.querySelectorAll('[role="tab"]'), ).find((tab) => tab.getAttribute('aria-controls') === panel.id); + // Radix activates a tab when its trigger receives focus. trigger?.focus(); frame = requestAnimationFrame(() => target.scrollIntoView()); } + function onAnchorClick(event: MouseEvent) { + if ( + event.defaultPrevented || + event.button !== 0 || + event.metaKey || + event.ctrlKey || + event.shiftKey || + event.altKey + ) { + return; + } + const anchor = + event.target instanceof Element ? event.target.closest('a') : null; + // Clicking the current anchor does not emit another hashchange. + if (anchor?.href === window.location.href) revealAnchor(); + } + revealAnchor(); window.addEventListener('hashchange', revealAnchor); + document.addEventListener('click', onAnchorClick); return () => { window.removeEventListener('hashchange', revealAnchor); + document.removeEventListener('click', onAnchorClick); cancelAnimationFrame(frame); }; }, []); From afbc8c7a32e4d7820a5383a870211fb1e779441c Mon Sep 17 00:00:00 2001 From: Ravi <103035973+raviships@users.noreply.github.com> Date: Sun, 11 Oct 2026 18:28:20 +0900 Subject: [PATCH 3/6] fix: keep the PR limited to verified website and docs fixes --- docs/public/r/avatar-dropzone-block.json | 4 +-- docs/public/r/file-field-block.json | 4 +-- docs/public/r/file-uploader-block.json | 4 +-- docs/public/r/multi-image-uploader-block.json | 4 +-- docs/public/r/progress-bar-block.json | 4 +-- docs/public/r/progress-circle-block.json | 4 +-- docs/public/r/registry.json | 32 +++++++++---------- .../public/r/single-image-dropzone-block.json | 4 +-- docs/public/r/upload-button-block.json | 4 +-- registry.json | 32 +++++++++---------- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/docs/public/r/avatar-dropzone-block.json b/docs/public/r/avatar-dropzone-block.json index 7994f694..d67a5e3c 100644 --- a/docs/public/r/avatar-dropzone-block.json +++ b/docs/public/r/avatar-dropzone-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/avatar-block.tsx", "content": "'use client';\n\nimport { AvatarDropzone } from '@/components/upload/avatar-dropzone';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function AvatarDropzoneBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/avatar-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/file-field-block.json b/docs/public/r/file-field-block.json index e93770a7..655e44b4 100644 --- a/docs/public/r/file-field-block.json +++ b/docs/public/r/file-field-block.json @@ -12,8 +12,8 @@ { "path": "examples/components/src/components/upload/blocks/file-field-block.tsx", "content": "'use client';\n\nimport { FileField } from '@/components/upload/file-field';\nimport { DOCUMENT_ACCEPT } from '@/components/upload/upload-utils';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function FileFieldBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n Resume\n \n \n \n \n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/file-field-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/file-uploader-block.json b/docs/public/r/file-uploader-block.json index 6cabbfec..fab6dd14 100644 --- a/docs/public/r/file-uploader-block.json +++ b/docs/public/r/file-uploader-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/multi-file-block.tsx", "content": "'use client';\n\nimport { FileUploader } from '@/components/upload/multi-file';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function FileUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n \n
\n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-file-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/multi-image-uploader-block.json b/docs/public/r/multi-image-uploader-block.json index d3e0c6cd..505f5299 100644 --- a/docs/public/r/multi-image-uploader-block.json +++ b/docs/public/r/multi-image-uploader-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/multi-image-block.tsx", "content": "'use client';\n\nimport { ImageUploader } from '@/components/upload/multi-image';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function MultiImageUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n \n \n \n
\n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-image-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/progress-bar-block.json b/docs/public/r/progress-bar-block.json index 65ac5e9d..d0235832 100644 --- a/docs/public/r/progress-bar-block.json +++ b/docs/public/r/progress-bar-block.json @@ -10,8 +10,8 @@ { "path": "examples/components/src/components/upload/blocks/progress-bar-block.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { ProgressBar } from '../progress-bar';\n\nexport default function ProgressBarUsage() {\n const [progress, setProgress] = React.useState(0);\n\n React.useEffect(() => {\n const startAnimation = () => {\n const interval = setInterval(() => {\n setProgress((prevProgress) => {\n if (prevProgress >= 100) {\n clearInterval(interval);\n // Reset after 1 second when reaching 100%\n setTimeout(() => {\n setProgress(0);\n startAnimation(); // Restart the animation cycle\n }, 1000);\n return 100;\n }\n // Add a random value between 10-20 percent\n return Math.min(\n 100,\n prevProgress + Math.floor(Math.random() * 11) + 10,\n );\n });\n }, 300);\n\n return interval;\n };\n\n const interval = startAnimation();\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return (\n
\n \n
\n );\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-bar-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/progress-circle-block.json b/docs/public/r/progress-circle-block.json index ea132b32..86d11fd4 100644 --- a/docs/public/r/progress-circle-block.json +++ b/docs/public/r/progress-circle-block.json @@ -10,8 +10,8 @@ { "path": "examples/components/src/components/upload/blocks/progress-circle-block.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { ProgressCircle } from '../progress-circle';\n\nexport default function ProgressCircleUsage() {\n const [progress, setProgress] = React.useState(0);\n\n React.useEffect(() => {\n const startAnimation = () => {\n const interval = setInterval(() => {\n setProgress((prevProgress) => {\n if (prevProgress >= 100) {\n clearInterval(interval);\n // Reset after 1 second when reaching 100%\n setTimeout(() => {\n setProgress(0);\n startAnimation(); // Restart the animation cycle\n }, 1000);\n return 100;\n }\n // Add a random value between 10-20 percent\n return Math.min(\n 100,\n prevProgress + Math.floor(Math.random() * 11) + 10,\n );\n });\n }, 300);\n\n return interval;\n };\n\n const interval = startAnimation();\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return (\n
\n
\n \n
\n
\n );\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-circle-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/registry.json b/docs/public/r/registry.json index 2a4951af..9a5189c4 100644 --- a/docs/public/r/registry.json +++ b/docs/public/r/registry.json @@ -208,8 +208,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-file-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-file-block.tsx" } ] }, @@ -225,8 +225,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/single-image-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/single-image-block.tsx" } ] }, @@ -242,8 +242,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-image-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-image-block.tsx" } ] }, @@ -259,8 +259,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/avatar-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/avatar-block.tsx" } ] }, @@ -277,8 +277,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/file-field-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/file-field-block.tsx" } ] }, @@ -297,8 +297,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/upload-button-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/upload-button-block.tsx" } ] }, @@ -311,8 +311,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-bar-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-bar-block.tsx" } ] }, @@ -325,8 +325,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-circle-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-circle-block.tsx" } ] } diff --git a/docs/public/r/single-image-dropzone-block.json b/docs/public/r/single-image-dropzone-block.json index 30bca9d4..b8f7e699 100644 --- a/docs/public/r/single-image-dropzone-block.json +++ b/docs/public/r/single-image-dropzone-block.json @@ -11,8 +11,8 @@ { "path": "examples/components/src/components/upload/blocks/single-image-block.tsx", "content": "'use client';\n\nimport { SingleImageDropzone } from '@/components/upload/single-image';\nimport {\n UploaderProvider,\n useUploader,\n type CompletedFileState,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport * as React from 'react';\n\nexport default function SingleImageUploaderBlock() {\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicImages.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n \n \n \n \n
\n );\n}\n\nfunction CompletedFiles() {\n const { fileStates } = useUploader();\n\n const completedFiles = fileStates.filter(\n (fs): fs is CompletedFileState => fs.status === 'COMPLETE',\n );\n\n if (completedFiles.length === 0) {\n return null;\n }\n\n return (\n
\n

Uploaded files

\n
    \n {completedFiles.map((fs) => (\n
  • \n \n {fs.file.name}\n \n
  • \n ))}\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicImages: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/single-image-block.tsx" } ], "type": "registry:block" diff --git a/docs/public/r/upload-button-block.json b/docs/public/r/upload-button-block.json index 8cbed335..9cda7a90 100644 --- a/docs/public/r/upload-button-block.json +++ b/docs/public/r/upload-button-block.json @@ -16,8 +16,8 @@ { "path": "examples/components/src/components/upload/blocks/upload-button-block.tsx", "content": "'use client';\n\nimport { UploadErrors } from '@/components/upload/dropzone';\nimport { FileList } from '@/components/upload/multi-file';\nimport { UploadButton } from '@/components/upload/upload-button';\nimport {\n UploaderProvider,\n type UploadFn,\n} from '@/components/upload/uploader-provider';\nimport { PaperclipIcon, UploadIcon } from 'lucide-react';\nimport * as React from 'react';\n\nexport default function UploadButtonBlock() {\n const [errors, setErrors] = React.useState([]);\n const { edgestore } = useMockEdgeStore(); // Mock edgestore for easy v0 integration\n\n const uploadFn: UploadFn = React.useCallback(\n async ({ file, signal, onProgressChange }) => {\n const res = await edgestore.myPublicFiles.upload({\n file,\n signal,\n onProgressChange,\n });\n // you can run some server action or api here\n // to add the necessary data to your database\n return { url: res.url };\n },\n [edgestore],\n );\n\n return (\n
\n
\n \n
\n \n Upload files\n \n \n \n \n
\n setErrors([])} />\n \n
\n
\n
\n );\n}\n\n// Mock implementation of EdgeStore\nfunction useMockEdgeStore() {\n return {\n edgestore: {\n myPublicFiles: {\n upload: async ({\n signal,\n onProgressChange,\n }: {\n file: File;\n signal?: AbortSignal;\n onProgressChange?: (progress: number) => void | Promise;\n }) => {\n // Simulate upload progress. Rejects like EdgeStore when the upload is aborted.\n await new Promise((resolve, reject) => {\n let progress = 0;\n const interval = setInterval(() => {\n progress = Math.min(\n progress + Math.floor(Math.random() * 21) + 10,\n 100,\n );\n void onProgressChange?.(progress);\n if (progress >= 100) {\n clearInterval(interval);\n resolve();\n }\n }, 300);\n signal?.addEventListener('abort', () => {\n clearInterval(interval);\n reject(new DOMException('Upload aborted', 'AbortError'));\n });\n });\n\n return {\n url: 'https://edgestore.dev/img/upload-demo.webp',\n };\n },\n },\n },\n };\n}\n", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/upload-button-block.tsx" } ], "type": "registry:block" diff --git a/registry.json b/registry.json index 2a4951af..9a5189c4 100644 --- a/registry.json +++ b/registry.json @@ -208,8 +208,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-file-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-file-block.tsx" } ] }, @@ -225,8 +225,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/single-image-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/single-image-block.tsx" } ] }, @@ -242,8 +242,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/multi-image-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/multi-image-block.tsx" } ] }, @@ -259,8 +259,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/avatar-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/avatar-block.tsx" } ] }, @@ -277,8 +277,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/file-field-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/file-field-block.tsx" } ] }, @@ -297,8 +297,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/upload-button-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/upload-button-block.tsx" } ] }, @@ -311,8 +311,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-bar-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-bar-block.tsx" } ] }, @@ -325,8 +325,8 @@ "files": [ { "path": "examples/components/src/components/upload/blocks/progress-circle-block.tsx", - "type": "registry:page", - "target": "app/page.tsx" + "type": "registry:block", + "target": "components/upload/blocks/progress-circle-block.tsx" } ] } From 48a8834621252ee20872be3e86310b792ebe1d17 Mon Sep 17 00:00:00 2001 From: Ravi <103035973+raviships@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:42:05 +0900 Subject: [PATCH 4/6] fix: select documentation tabs directly and retain upload feedback --- .../content/docs/components/upload-button.mdx | 28 ++++++++++++++++++- docs/src/components/ui/open-tabs.tsx | 24 ++++++++++------ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/docs/content/docs/components/upload-button.mdx b/docs/content/docs/components/upload-button.mdx index 38d6f82c..5716fc56 100644 --- a/docs/content/docs/components/upload-button.mdx +++ b/docs/content/docs/components/upload-button.mdx @@ -18,7 +18,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; -It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`. To show a file list, also install [Multi-file](./multi-file) and add its `FileList`, or build your own list with `useUploader`. +It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`. The basic example below shows upload progress and results with `useUploader`, without extra components. For the richer file list used in the demo, also install [Multi-file](./multi-file) and add its `FileList`. ## Installation @@ -91,6 +91,7 @@ import { UploadButton } from '@/components/upload/upload-button'; import { UploaderProvider, type UploadFn, + useUploader, } from '@/components/upload/uploader-provider'; import { useEdgeStore } from '@/lib/edgestore'; import { UploadIcon } from 'lucide-react'; @@ -124,9 +125,34 @@ export function UploadButtonUsage() { Upload files setErrors([])} /> + ); } + +function UploadStatus() { + const { fileStates } = useUploader(); + + return ( +
    + {fileStates.map(({ key, file, status, progress, url, error }) => ( +
  • + {file.name}{' '} + {status === 'COMPLETE' ? ( + Uploaded — open file + ) : status === 'ERROR' ? ( + Upload failed: {error} + ) : ( + <> + + {status === 'PENDING' ? 'Waiting to upload' : `${progress}%`} + + )} +
  • + ))} +
+ ); +} ``` ## Props diff --git a/docs/src/components/ui/open-tabs.tsx b/docs/src/components/ui/open-tabs.tsx index 1a60f32b..b3cb024c 100644 --- a/docs/src/components/ui/open-tabs.tsx +++ b/docs/src/components/ui/open-tabs.tsx @@ -7,8 +7,16 @@ import * as React from 'react'; const OpenTabs = React.forwardRef< React.ComponentRef, React.ComponentPropsWithoutRef ->((props, ref) => { +>(({ value: controlledValue, defaultValue, onValueChange, ...props }, ref) => { const root = React.useRef(null); + const [value, setValue] = React.useState(defaultValue); + const changeValue = React.useCallback( + (nextValue: string) => { + setValue(nextValue); + onValueChange?.(nextValue); + }, + [onValueChange], + ); React.useEffect(() => { let frame = 0; @@ -18,17 +26,14 @@ const OpenTabs = React.forwardRef< const panel = target.closest('[role="tabpanel"]'); if (panel?.getAttribute('data-state') !== 'inactive') return; - const trigger = Array.from( - root.current.querySelectorAll('[role="tab"]'), - ).find((tab) => tab.getAttribute('aria-controls') === panel.id); - // Radix activates a tab when its trigger receives focus. - trigger?.focus(); + const tabValue = panel.getAttribute('data-value'); + if (!tabValue) return; + changeValue(tabValue); frame = requestAnimationFrame(() => target.scrollIntoView()); } function onAnchorClick(event: MouseEvent) { if ( - event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || @@ -51,11 +56,13 @@ const OpenTabs = React.forwardRef< document.removeEventListener('click', onAnchorClick); cancelAnimationFrame(frame); }; - }, []); + }, [changeValue]); return ( { root.current = element; if (typeof ref === 'function') return ref(element); @@ -102,6 +109,7 @@ const OpenTabsContent = React.forwardRef< >(({ className, ...props }, ref) => ( Date: Tue, 13 Oct 2026 19:42:45 +0900 Subject: [PATCH 5/6] fix: restore the upload button file list example --- .../content/docs/components/upload-button.mdx | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/docs/content/docs/components/upload-button.mdx b/docs/content/docs/components/upload-button.mdx index 5716fc56..3ec36b89 100644 --- a/docs/content/docs/components/upload-button.mdx +++ b/docs/content/docs/components/upload-button.mdx @@ -18,7 +18,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; -It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`. The basic example below shows upload progress and results with `useUploader`, without extra components. For the richer file list used in the demo, also install [Multi-file](./multi-file) and add its `FileList`. +It takes the same props as the shadcn/ui `Button`. Several buttons can feed the same `UploaderProvider`, so you can pair them with a `FileList` or build your own list with `useUploader`. ## Installation @@ -87,11 +87,11 @@ Install or copy the component from [Installation](#installation) before using th 'use client'; import { UploadErrors } from '@/components/upload/dropzone'; +import { FileList } from '@/components/upload/multi-file'; import { UploadButton } from '@/components/upload/upload-button'; import { UploaderProvider, type UploadFn, - useUploader, } from '@/components/upload/uploader-provider'; import { useEdgeStore } from '@/lib/edgestore'; import { UploadIcon } from 'lucide-react'; @@ -125,34 +125,10 @@ export function UploadButtonUsage() { Upload files setErrors([])} /> - + ); } - -function UploadStatus() { - const { fileStates } = useUploader(); - - return ( -
    - {fileStates.map(({ key, file, status, progress, url, error }) => ( -
  • - {file.name}{' '} - {status === 'COMPLETE' ? ( - Uploaded — open file - ) : status === 'ERROR' ? ( - Upload failed: {error} - ) : ( - <> - - {status === 'PENDING' ? 'Waiting to upload' : `${progress}%`} - - )} -
  • - ))} -
- ); -} ``` ## Props From 6c999d5f55f4832b36f0f57b4367e4ac62558d22 Mon Sep 17 00:00:00 2001 From: Ravi <103035973+raviships@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:47:20 +0900 Subject: [PATCH 6/6] fix: limit the PR to website link fixes --- docs/src/components/ui/open-tabs.tsx | 73 +--------------------------- 1 file changed, 2 insertions(+), 71 deletions(-) diff --git a/docs/src/components/ui/open-tabs.tsx b/docs/src/components/ui/open-tabs.tsx index b3cb024c..9cafdea3 100644 --- a/docs/src/components/ui/open-tabs.tsx +++ b/docs/src/components/ui/open-tabs.tsx @@ -4,74 +4,7 @@ import { cn } from '@/lib/utils'; import * as TabsPrimitive from '@radix-ui/react-tabs'; import * as React from 'react'; -const OpenTabs = React.forwardRef< - React.ComponentRef, - React.ComponentPropsWithoutRef ->(({ value: controlledValue, defaultValue, onValueChange, ...props }, ref) => { - const root = React.useRef(null); - const [value, setValue] = React.useState(defaultValue); - const changeValue = React.useCallback( - (nextValue: string) => { - setValue(nextValue); - onValueChange?.(nextValue); - }, - [onValueChange], - ); - - React.useEffect(() => { - let frame = 0; - function revealAnchor() { - const target = document.getElementById(window.location.hash.slice(1)); - if (!target || !root.current?.contains(target)) return; - const panel = target.closest('[role="tabpanel"]'); - if (panel?.getAttribute('data-state') !== 'inactive') return; - - const tabValue = panel.getAttribute('data-value'); - if (!tabValue) return; - changeValue(tabValue); - frame = requestAnimationFrame(() => target.scrollIntoView()); - } - - function onAnchorClick(event: MouseEvent) { - if ( - event.button !== 0 || - event.metaKey || - event.ctrlKey || - event.shiftKey || - event.altKey - ) { - return; - } - const anchor = - event.target instanceof Element ? event.target.closest('a') : null; - // Clicking the current anchor does not emit another hashchange. - if (anchor?.href === window.location.href) revealAnchor(); - } - - revealAnchor(); - window.addEventListener('hashchange', revealAnchor); - document.addEventListener('click', onAnchorClick); - return () => { - window.removeEventListener('hashchange', revealAnchor); - document.removeEventListener('click', onAnchorClick); - cancelAnimationFrame(frame); - }; - }, [changeValue]); - - return ( - { - root.current = element; - if (typeof ref === 'function') return ref(element); - if (ref) ref.current = element; - }} - /> - ); -}); -OpenTabs.displayName = TabsPrimitive.Root.displayName; +const OpenTabs = TabsPrimitive.Root; const OpenTabsList = React.forwardRef< React.ComponentRef, @@ -109,10 +42,8 @@ const OpenTabsContent = React.forwardRef< >(({ className, ...props }, ref) => (