mirror of
https://github.com/supabase/supabase.git
synced 2026-09-06 18:11:51 +08:00
## What kind of change does this PR introduce? Bug fix and internal tooling update. Resolves FE-3472. ## What is the current behavior? Custom Studio icons use inconsistent source stroke widths, and some child-level styling prevents component props from overriding them. Mixed custom and Lucide icon sets can therefore appear uneven. ## What is the new behavior? Custom stroke icons use a root-level `stroke-width="1.5"`; fill-only logos use `stroke="none"`. The build validates that contract and regenerated components preserve existing exports and props. Studio applies the same `1.5` weight across Reports categories and uses one shared destination icon mapping in the replication selector, destination rows and diagram. | Before | After | | --- | --- | | <img width="418" height="516" alt="56398" src="https://github.com/user-attachments/assets/6afa7042-e6be-40e7-9911-af2f61238c9d" /> | <img width="390" height="550" alt="CleanShot 2026-07-30 at 17 12 37@2x" src="https://github.com/user-attachments/assets/870f49cf-c8fa-40db-8be8-2eb5f264ff4a" /> | | <img width="510" height="734" alt="CleanShot 2026-07-30 at 17 19 28@2x" src="https://github.com/user-attachments/assets/a5b2c088-dcd2-4907-976b-5820794d06e3" /> | <img width="554" height="742" alt="CleanShot 2026-07-30 at 17 16 06@2x" src="https://github.com/user-attachments/assets/ed3a77c4-5d94-4ca7-b9e4-1403b725a981" /> | ## Testing At 100% zoom, compare custom and Lucide icon weight in: - Reports: **Add your first chart** and **Add block** - Database > Replication: the destination selector, destination rows and replication diagram - Command menu (`⌘K`): **Search Database Tables**, **Search RLS Policies**, **Search Edge Functions** and **Search Storage** - Authentication > Users: right-click a user row and compare the context-menu icons - Database > Schema Visualizer: open a table node overflow menu - A paused project: **Export your data > Download backups** <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added consistent destination icons across replication panels, rows, and diagrams. * Updated instance health and metric icons for clearer identification. * Standardized icon stroke weight and reduced default icon stroke thickness. * **Documentation** * Clarified custom icon requirements, default properties, and validation guidance. * **Bug Fixes** * Improved consistency of icon rendering across replication destinations and reports. * **Tests** * Added coverage for icon SVG validation and replication destination icon rendering. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
97 lines
3.0 KiB
JavaScript
97 lines
3.0 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const CANONICAL_STROKE_WIDTH = '1.5'
|
|
const CHILD_STROKE_ATTRIBUTES = ['stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin']
|
|
|
|
const readAttributes = (element) =>
|
|
Object.fromEntries(
|
|
[...element.matchAll(/([\w:-]+)\s*=\s*["']([^"']*)["']/g)].map((match) => [match[1], match[2]])
|
|
)
|
|
|
|
export const validateIconSource = (source, filename = 'icon.svg') => {
|
|
const errors = []
|
|
const rootMatch = source.match(/<svg\b[^>]*>/s)
|
|
|
|
if (!rootMatch) return [`${filename}: missing root <svg> element`]
|
|
|
|
const rootAttributes = readAttributes(rootMatch[0])
|
|
const childElements = source
|
|
.slice((rootMatch.index ?? 0) + rootMatch[0].length)
|
|
.match(/<(?!\/)[a-z][^>]*>/gi)
|
|
const childAttributeViolations = []
|
|
let hasChildInlineStyle = false
|
|
|
|
if ('style' in rootAttributes) {
|
|
errors.push(`${filename}: inline style attributes are not allowed on the root <svg>`)
|
|
}
|
|
|
|
for (const element of childElements ?? []) {
|
|
const attributes = readAttributes(element)
|
|
if ('style' in attributes) hasChildInlineStyle = true
|
|
for (const attribute of CHILD_STROKE_ATTRIBUTES) {
|
|
if (attribute in attributes) childAttributeViolations.push(attribute)
|
|
}
|
|
}
|
|
|
|
if (hasChildInlineStyle) {
|
|
errors.push(`${filename}: inline style attributes are not allowed on child elements`)
|
|
}
|
|
|
|
if (childAttributeViolations.length > 0) {
|
|
errors.push(
|
|
`${filename}: move shared ${[...new Set(childAttributeViolations)].join(
|
|
', '
|
|
)} attributes to the root <svg>`
|
|
)
|
|
}
|
|
|
|
if (rootAttributes.stroke === 'none') {
|
|
if ('stroke-width' in rootAttributes) {
|
|
errors.push(`${filename}: fill-only icons must not declare stroke-width`)
|
|
}
|
|
return errors
|
|
}
|
|
|
|
if (rootAttributes.fill !== 'none') {
|
|
errors.push(`${filename}: stroke icons must use fill="none" on the root <svg>`)
|
|
}
|
|
if (rootAttributes.stroke !== 'currentColor') {
|
|
errors.push(
|
|
`${filename}: stroke icons must use stroke="currentColor"; use stroke="none" for fill-only icons`
|
|
)
|
|
}
|
|
if (rootAttributes['stroke-width'] !== CANONICAL_STROKE_WIDTH) {
|
|
errors.push(
|
|
`${filename}: stroke icons must use stroke-width="${CANONICAL_STROKE_WIDTH}" on the root <svg>`
|
|
)
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
export const validateIconDirectory = (directory) => {
|
|
const errors = fs
|
|
.readdirSync(directory)
|
|
.filter((filename) => filename.endsWith('.svg'))
|
|
.sort()
|
|
.flatMap((filename) =>
|
|
validateIconSource(fs.readFileSync(path.join(directory, filename), 'utf8'), filename)
|
|
)
|
|
|
|
if (errors.length > 0) {
|
|
throw new Error(
|
|
`Icon source validation failed:\n${errors.map((error) => `- ${error}`).join('\n')}`
|
|
)
|
|
}
|
|
}
|
|
|
|
const isExecutedDirectly =
|
|
process.argv[1] !== undefined && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
|
|
if (isExecutedDirectly) {
|
|
validateIconDirectory(path.resolve(process.cwd(), 'src/raw-icons'))
|
|
console.log('Validated icon sources.')
|
|
}
|