mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
1131e3e2ce
## What kind of change does this PR introduce? Bug fix / design-system alignment for the legacy `Button` from `ui`. ## What is the current behavior? Omitting `variant` on the legacy `Button` falls back to brand-green `primary`. That makes accidental greens easy, and it is hard to spot the real main action on busy pages. ## What is the new behavior? - Legacy `Button` now defaults to neutral `default` - Intentional primary CTAs (create, save, submit, marketing CTAs, and matching `ButtonTooltip` usages) now set `variant="primary"` so their appearance is unchanged - Neutral actions that previously relied on the old fallback (cancel, close, back, dashboard nav, and similar) become grey/white - Design-system docs updated; regression tests cover the new default `Button_Shadcn_` is unchanged. It already uses its own CVA default. This is PR 1 of 2 in a stack. PR 2 drops now-redundant `variant="default"` props. ## To test Studio (http://localhost:8082): - `/sign-in`: Sign in stays green - Open a project → Database → Tables: New table stays green - Auth → Users → Invite: Invite user stays green; Cancel / dismiss controls stay neutral - Project Settings → General: edit a field so Cancel and Save appear. Cancel is neutral, Save is green Design system (http://localhost:3003): - Components → Button: default demo is neutral; primary demo is green; featured preview is the default variant Marketing (optional): - www header: Start your project stays green; logged-in Dashboard is neutral <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Style** - Buttons now default to a neutral style, while primary actions across Studio, documentation, marketing pages, forms, dialogs, and error states use prominent primary styling. - Updated button examples and previews clarify the distinction between default and primary variants. - Event registration now includes a directional arrow icon. - **Tests** - Added coverage confirming default button styling and explicit primary styling behave as expected. - Updated related test fixtures to use primary styling where appropriate. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
193 lines
6.4 KiB
TypeScript
193 lines
6.4 KiB
TypeScript
import { useParams } from 'common'
|
|
import { AlertCircle, LoaderCircle, X } from 'lucide-react'
|
|
import SVG from 'react-inlinesvg'
|
|
import { Button } from 'ui'
|
|
|
|
import { StorageItemWithColumn } from '../Storage.types'
|
|
import { useFetchFileUrlQuery } from '../StorageExplorer/useFetchFileUrlQuery'
|
|
import { useBucketFilePickerStateSnapshot } from './BucketFilePickerState'
|
|
import { BASE_PATH } from '@/lib/constants'
|
|
import { formatBytes } from '@/lib/helpers'
|
|
|
|
const PREVIEW_SIZE_LIMIT = 10 * 1024 * 1024 // 10MB
|
|
|
|
const PreviewFile = ({ item }: { item: StorageItemWithColumn }) => {
|
|
const { ref: projectRef } = useParams()
|
|
const { bucket, columns } = useBucketFilePickerStateSnapshot()
|
|
const path = columns.slice(0, item.columnIndex).concat(item.name).join('/')
|
|
|
|
const { data: previewUrl, isPending: isLoading } = useFetchFileUrlQuery({
|
|
path,
|
|
projectRef: projectRef!,
|
|
bucket,
|
|
})
|
|
|
|
// if the size is not available, we set it to be greater than the max size
|
|
const size = +(item.metadata?.size ?? PREVIEW_SIZE_LIMIT + 1)
|
|
const mimeType = item.metadata?.mimetype
|
|
|
|
const isSkipped = !!mimeType && !!size && size > PREVIEW_SIZE_LIMIT
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center text-foreground-lighter">
|
|
<LoaderCircle size={14} strokeWidth={2} className="animate-spin text-foreground-lighter" />
|
|
</div>
|
|
)
|
|
}
|
|
if (isSkipped) {
|
|
return (
|
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
|
<SVG
|
|
src={`${BASE_PATH}/img/file-filled.svg`}
|
|
preProcessor={(code) =>
|
|
code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
|
|
}
|
|
/>
|
|
<p className="mt-2 w-2/5 text-center text-sm">
|
|
File size is too large to preview in the explorer
|
|
</p>
|
|
</div>
|
|
)
|
|
}
|
|
if (!mimeType || !previewUrl) {
|
|
return (
|
|
<SVG
|
|
src={`${BASE_PATH}/img/file-filled.svg`}
|
|
preProcessor={(code) =>
|
|
code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
|
|
}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (mimeType.includes('image')) {
|
|
return (
|
|
<div
|
|
className="flex h-full w-full items-center justify-center bg-contain bg-center bg-no-repeat"
|
|
style={{ backgroundImage: `url('${previewUrl}')` }}
|
|
/>
|
|
)
|
|
}
|
|
if (mimeType.includes('audio')) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center px-10">
|
|
<audio key={previewUrl} controls style={{ width: 'inherit' }}>
|
|
<source src={previewUrl} type="audio/mpeg" />
|
|
<p className="text-sm text-foreground-light">
|
|
Your browser does not support the audio element.
|
|
</p>
|
|
</audio>
|
|
</div>
|
|
)
|
|
}
|
|
if (mimeType.includes('video')) {
|
|
return (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<video key={previewUrl} controls style={{ maxHeight: '100%' }}>
|
|
<source src={previewUrl} type="video/mp4" />
|
|
<p className="text-sm text-foreground-light">
|
|
Your browser does not support the video tag.
|
|
</p>
|
|
</video>
|
|
</div>
|
|
)
|
|
}
|
|
return (
|
|
<SVG
|
|
src={`${BASE_PATH}/img/file-filled.svg`}
|
|
preProcessor={(code) =>
|
|
code.replace(/svg/, 'svg class="mx-auto w-32 h-32 text-color-inherit opacity-75"')
|
|
}
|
|
/>
|
|
)
|
|
}
|
|
|
|
export const PreviewPane = ({ onSelect }: { onSelect: (url: string) => void }) => {
|
|
const { ref: projectRef } = useParams()
|
|
const {
|
|
selectedFilePreview: file,
|
|
setSelectedFilePreview,
|
|
bucket,
|
|
columns,
|
|
} = useBucketFilePickerStateSnapshot()
|
|
|
|
const path = file ? columns.slice(0, file.columnIndex).concat(file.name).join('/') : ''
|
|
const { data: previewUrl, isLoading } = useFetchFileUrlQuery(
|
|
{ path, projectRef: projectRef!, bucket },
|
|
{ enabled: !!file }
|
|
)
|
|
|
|
if (!file) return null
|
|
|
|
const size = file.metadata ? formatBytes(file.metadata.size) : null
|
|
const mimeType = file.metadata ? file.metadata.mimetype : undefined
|
|
const createdAt = file.created_at ? new Date(file.created_at).toLocaleString() : 'Unknown'
|
|
const updatedAt = file.updated_at ? new Date(file.updated_at).toLocaleString() : 'Unknown'
|
|
|
|
return (
|
|
<div className="h-full border-l border-overlay bg-surface-100 overflow-y-auto w-[450px] pb-4">
|
|
{/* Preview Header */}
|
|
<div className="flex w-full justify-end items-center gap-2 sticky top-0 bg-surface-100 p-4 border-b">
|
|
<Button
|
|
variant="primary"
|
|
size="tiny"
|
|
onClick={() => onSelect(previewUrl!)}
|
|
disabled={!previewUrl}
|
|
loading={isLoading}
|
|
>
|
|
Select
|
|
</Button>
|
|
<div className="text-foreground-lighter transition-colors hover:text-foreground">
|
|
<X
|
|
className="cursor-pointer"
|
|
size={14}
|
|
strokeWidth={2}
|
|
onClick={() => setSelectedFilePreview(undefined)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preview Thumbnail*/}
|
|
<div className="my-4 border border-overlay mx-4">
|
|
<div className="flex h-56 w-full items-center 2xl:h-72">
|
|
<PreviewFile item={file} />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-full space-y-6 px-4">
|
|
{/* Preview Information */}
|
|
<div className="space-y-1">
|
|
<h5 className="wrap-break-word text-base text-foreground">{file.name}</h5>
|
|
{file.isCorrupted && (
|
|
<div className="flex items-center space-x-2">
|
|
<AlertCircle size={14} strokeWidth={2} className="text-foreground-light" />
|
|
<p className="text-sm text-foreground-light">
|
|
File is corrupted, please delete and reupload this file again
|
|
</p>
|
|
</div>
|
|
)}
|
|
{mimeType && (
|
|
<p className="text-sm text-foreground-light">
|
|
{mimeType}
|
|
{size && <span> - {size}</span>}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Preview Metadata */}
|
|
<div className="space-y-2">
|
|
<div>
|
|
<label className="mb-1 text-xs text-foreground-lighter">Added on</label>
|
|
<p className="text-sm text-foreground-light">{createdAt}</p>
|
|
</div>
|
|
<div>
|
|
<label className="mb-1 text-xs text-foreground-lighter">Last modified</label>
|
|
<p className="text-sm text-foreground-light">{updatedAt}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|