fix(studio): align storage row icon weight (#48480)

## What kind of change does this PR introduce?

Bug fix stacked on #48478.

## What is the current behavior?

Storage Explorer renders the custom closed-folder icon at `1.5` but
leaves the Lucide open-folder icon at its default `2`. The duplicated
file-picker implementation also uses a different set of stroke-width
overrides.

## What is the new behavior?

A shared `StorageRowIcon` renders loading, open and closed folder,
image, audio, video and generic file icons at `1.5` across Storage
Explorer, row editing and the bucket file picker.

Test by comparing open and closed folders and file-type rows in Storage
Explorer and the bucket file picker.

| Before | After |
| --- | --- |
| <img width="524" height="336" alt="CleanShot 2026-08-03 at 18 38
06@2x"
src="https://github.com/user-attachments/assets/15eb8b9f-69fc-4eee-8428-d7ec26dce8dc"
/> | <img width="522" height="328" alt="CleanShot 2026-08-03 at 18 39
20@2x"
src="https://github.com/user-attachments/assets/17b7dddd-8d36-421c-8356-c9c1bd7456e8"
/> |
| _Thicker image and file icon compared to folder icon_ | _Every icon
has the same stroke thickness_ |

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Standardized file, folder, media, and loading icons across storage
views.
* Improved visual consistency with unified icon sizing, styling, and
stroke width.

* **Tests**
* Added coverage for loading, folder, media, and generic file icon
states.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Danny White authored and GitHub committed 2026-08-03 19:37:54 +10:00
1 parent fc69c45985
commit 76257f164b
5 files changed
+135 -94

No files matched your search

@@ -1,54 +1,12 @@
import { FilesBucket as FilesBucketIcon } from 'icons'
import { AlertCircle, File, Film, FolderOpen, Image, LoaderCircle, Music } from 'lucide-react'
import { AlertCircle, LoaderCircle } from 'lucide-react'
import type { CSSProperties, MouseEvent } from 'react'
import { Checkbox, cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES, STORAGE_VIEWS } from '../Storage.constants'
import { type StorageItem } from '../Storage.types'
import { StorageRowIcon } from '../StorageRowIcon'
import { formatBytes } from '@/lib/helpers'
const RowIcon = ({
view,
status,
fileType,
isOpened = false,
mimeType,
}: {
view: STORAGE_VIEWS
status: STORAGE_ROW_STATUS
fileType: string
isOpened?: boolean
mimeType: string | undefined
}) => {
if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) {
return (
<LoaderCircle size={14} strokeWidth={2} className="animate-spin text-foreground-lighter" />
)
}
if (fileType === STORAGE_ROW_TYPES.FOLDER) {
return isOpened ? (
<FolderOpen size={16} strokeWidth={2} className="text-foreground-lighter" />
) : (
<FilesBucketIcon size={16} strokeWidth={2} className="text-foreground-lighter" />
)
}
if (mimeType?.includes('image')) {
return <Image size={16} className="text-foreground-lighter" />
}
if (mimeType?.includes('audio')) {
return <Music size={16} strokeWidth={2} className="text-foreground-lighter" />
}
if (mimeType?.includes('video')) {
return <Film size={16} strokeWidth={2} className="text-foreground-lighter" />
}
return <File size={16} strokeWidth={2} className="text-foreground-lighter" />
}
interface BucketFilePickerRowProps {
item: StorageItem
view: STORAGE_VIEWS
@@ -114,7 +72,7 @@ export const BucketFilePickerRow = ({
hidden: isSelected,
})}
>
<RowIcon
<StorageRowIcon
view={view}
status={item.status}
fileType={item.type}
@@ -1,19 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { FilesBucket as FilesBucketIcon } from 'icons'
import { find, isEmpty, isEqual } from 'lodash'
import {
AlertCircle,
Copy,
Download,
Edit,
File,
Film,
FolderOpen,
Image,
LoaderCircle,
MoreVertical,
Move,
Music,
Trash2,
} from 'lucide-react'
import type { CSSProperties } from 'react'
@@ -41,6 +35,7 @@ import {
URL_EXPIRY_DURATION,
} from '../Storage.constants'
import { StorageItemWithColumn, type StorageItem } from '../Storage.types'
import { StorageRowIcon } from '../StorageRowIcon'
import { useFileExplorerContextMenu } from './FileExplorerRowContextMenu'
import { FileExplorerRowEditing } from './FileExplorerRowEditing'
import { copyPathToFolder } from './StorageExplorer.utils'
@@ -49,46 +44,6 @@ import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { formatBytes } from '@/lib/helpers'
import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
export const RowIcon = ({
view,
status,
fileType,
isOpened = false,
mimeType,
}: {
view: STORAGE_VIEWS
status: STORAGE_ROW_STATUS
fileType: string
isOpened?: boolean
mimeType: string | undefined
}) => {
if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) {
return <LoaderCircle size={14} className="animate-spin text-foreground-lighter" />
}
if (fileType === STORAGE_ROW_TYPES.FOLDER) {
return isOpened ? (
<FolderOpen size={16} className="text-foreground-lighter" />
) : (
<FilesBucketIcon size={16} className="text-foreground-lighter" />
)
}
if (mimeType?.includes('image')) {
return <Image size={16} className="text-foreground-lighter" />
}
if (mimeType?.includes('audio')) {
return <Music size={16} className="text-foreground-lighter" />
}
if (mimeType?.includes('video')) {
return <Film size={16} className="text-foreground-lighter" />
}
return <File size={16} className="text-foreground-lighter" />
}
interface FileExplorerRowProps {
index: number
item: StorageItem
@@ -329,7 +284,7 @@ export const FileExplorerRow = ({
)}
style={{ top: '2px' }}
>
<RowIcon
<StorageRowIcon
view={view}
status={item.status}
fileType={item.type}
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState, type CSSProperties } from 'react'
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES, STORAGE_VIEWS } from '../Storage.constants'
import { StorageItem } from '../Storage.types'
import { RowIcon } from './FileExplorerRow'
import { StorageRowIcon } from '../StorageRowIcon'
import { useStorageExplorerStateSnapshot } from '@/state/storage-explorer'
export interface FileExplorerRowEditingProps {
@@ -94,7 +94,7 @@ export const FileExplorerRowEditing = ({
>
<div className="flex h-full grow items-center px-2.5">
<div>
<RowIcon
<StorageRowIcon
view={view}
status={item.status}
fileType={item.type}
@@ -0,0 +1,68 @@
import { describe, expect, test } from 'vitest'
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES, STORAGE_VIEWS } from './Storage.constants'
import { StorageRowIcon } from './StorageRowIcon'
import { customRender } from '@/tests/lib/custom-render'
const CASES: {
name: string
view?: STORAGE_VIEWS
status?: STORAGE_ROW_STATUS
fileType?: STORAGE_ROW_TYPES
isOpened?: boolean
mimeType?: string
expectedIcon: string
}[] = [
{
name: 'loading list row',
view: STORAGE_VIEWS.LIST,
status: STORAGE_ROW_STATUS.LOADING,
expectedIcon: 'lucide-loader-circle',
},
{
name: 'loading column row',
view: STORAGE_VIEWS.COLUMNS,
status: STORAGE_ROW_STATUS.LOADING,
expectedIcon: 'lucide-file',
},
{
name: 'ready list row',
view: STORAGE_VIEWS.LIST,
status: STORAGE_ROW_STATUS.READY,
expectedIcon: 'lucide-file',
},
{
name: 'closed folder',
fileType: STORAGE_ROW_TYPES.FOLDER,
expectedIcon: 'lucide-files-bucket',
},
{
name: 'open folder',
fileType: STORAGE_ROW_TYPES.FOLDER,
isOpened: true,
expectedIcon: 'lucide-folder-open',
},
{ name: 'image', mimeType: 'image/png', expectedIcon: 'lucide-image' },
{ name: 'audio', mimeType: 'audio/mpeg', expectedIcon: 'lucide-music' },
{ name: 'video', mimeType: 'video/mp4', expectedIcon: 'lucide-film' },
{ name: 'generic file', expectedIcon: 'lucide-file' },
]
describe('StorageRowIcon', () => {
test.each(CASES)('renders $name at the canonical stroke width', (props) => {
const { container } = customRender(
<StorageRowIcon
view={props.view ?? STORAGE_VIEWS.COLUMNS}
status={props.status ?? STORAGE_ROW_STATUS.READY}
fileType={props.fileType ?? STORAGE_ROW_TYPES.FILE}
isOpened={props.isOpened}
mimeType={props.mimeType}
/>
)
const icon = container.querySelector('svg')
expect(icon).toHaveClass(props.expectedIcon)
expect(icon).toHaveAttribute('stroke-width', '1.5')
})
})
@@ -0,0 +1,60 @@
import { FilesBucket as FilesBucketIcon } from 'icons'
import { File, Film, FolderOpen, Image as ImageIcon, LoaderCircle, Music } from 'lucide-react'
import { STORAGE_ROW_STATUS, STORAGE_ROW_TYPES, STORAGE_VIEWS } from './Storage.constants'
const ICON_STROKE_WIDTH = 1.5
interface StorageRowIconProps {
view: STORAGE_VIEWS
status: STORAGE_ROW_STATUS
fileType: string
isOpened?: boolean
mimeType: string | undefined
}
export const StorageRowIcon = ({
view,
status,
fileType,
isOpened = false,
mimeType,
}: StorageRowIconProps) => {
if (view === STORAGE_VIEWS.LIST && status === STORAGE_ROW_STATUS.LOADING) {
return (
<LoaderCircle
size={14}
strokeWidth={ICON_STROKE_WIDTH}
className="animate-spin text-foreground-lighter"
/>
)
}
if (fileType === STORAGE_ROW_TYPES.FOLDER) {
return isOpened ? (
<FolderOpen size={16} strokeWidth={ICON_STROKE_WIDTH} className="text-foreground-lighter" />
) : (
<FilesBucketIcon
size={16}
strokeWidth={ICON_STROKE_WIDTH}
className="text-foreground-lighter"
/>
)
}
if (mimeType?.includes('image')) {
return (
<ImageIcon size={16} strokeWidth={ICON_STROKE_WIDTH} className="text-foreground-lighter" />
)
}
if (mimeType?.includes('audio')) {
return <Music size={16} strokeWidth={ICON_STROKE_WIDTH} className="text-foreground-lighter" />
}
if (mimeType?.includes('video')) {
return <Film size={16} strokeWidth={ICON_STROKE_WIDTH} className="text-foreground-lighter" />
}
return <File size={16} strokeWidth={ICON_STROKE_WIDTH} className="text-foreground-lighter" />
}