feat(SW-3695): use svg icons instead of font icons * feat(icons): use svg instead of font icons * feat(icons): use webpack/svgr for inlined svgs. Now support for isFilled again * Merge master * Remove old font icon Approved-by: Joakim Jäderberg
324 lines
6.1 KiB
TypeScript
324 lines
6.1 KiB
TypeScript
import { writeFile } from "node:fs/promises"
|
|
import { resolve } from "node:path"
|
|
import { existsSync } from "node:fs"
|
|
|
|
// Your full list of Material Symbol icon names
|
|
const ICONS = [
|
|
"accessibility",
|
|
"accessible",
|
|
"acute",
|
|
"add_circle",
|
|
"add",
|
|
"air_purifier_gen",
|
|
"air",
|
|
"airline_seat_recline_normal",
|
|
"airplane_ticket",
|
|
"apartment",
|
|
"apparel",
|
|
"arrow_back_ios",
|
|
"arrow_back",
|
|
"arrow_forward_ios",
|
|
"arrow_forward",
|
|
"arrow_right",
|
|
"arrow_upward",
|
|
"assistant_navigation",
|
|
"asterisk",
|
|
"attractions",
|
|
"award_star",
|
|
"bakery_dining",
|
|
"balcony",
|
|
"bathroom",
|
|
"bathtub",
|
|
"beach_access",
|
|
"bed",
|
|
"bedroom_parent",
|
|
"box",
|
|
"brunch_dining",
|
|
"business_center",
|
|
"calendar_add_on",
|
|
"calendar_clock",
|
|
"calendar_month",
|
|
"calendar_today",
|
|
"call_quality",
|
|
"call",
|
|
"camera",
|
|
"cancel",
|
|
"chair",
|
|
"mobile_charge",
|
|
"check_box",
|
|
"check_circle",
|
|
"check",
|
|
"checked_bag",
|
|
"checkroom",
|
|
"chevron_left",
|
|
"chevron_right",
|
|
"close",
|
|
"coffee_maker",
|
|
"coffee",
|
|
"compare_arrows",
|
|
"computer",
|
|
"concierge",
|
|
"confirmation_number",
|
|
"connected_tv",
|
|
"content_copy",
|
|
"contract",
|
|
"cool_to_dry",
|
|
"countertops",
|
|
"credit_card_heart",
|
|
"credit_card",
|
|
"credit_score",
|
|
"curtains_closed",
|
|
"curtains",
|
|
"dangerous",
|
|
"deck",
|
|
"delete",
|
|
"desk",
|
|
"device_thermostat",
|
|
"diamond",
|
|
"dining",
|
|
"directions_run",
|
|
"directions_subway",
|
|
"directions",
|
|
"downhill_skiing",
|
|
"download",
|
|
"dresser",
|
|
"edit_calendar",
|
|
"edit_square",
|
|
"edit",
|
|
"electric_bike",
|
|
"electric_car",
|
|
"elevator",
|
|
"emoji_transportation",
|
|
"error",
|
|
"exercise",
|
|
"family_restroom",
|
|
"fastfood",
|
|
"favorite",
|
|
"fax",
|
|
"featured_seasonal_and_gifts",
|
|
"festival",
|
|
"filter_alt",
|
|
"filter",
|
|
"format_list_bulleted",
|
|
"floor_lamp",
|
|
"forest",
|
|
"garage",
|
|
"globe",
|
|
"golf_course",
|
|
"groups",
|
|
"health_and_beauty",
|
|
"heat",
|
|
"hiking",
|
|
"home",
|
|
"hot_tub",
|
|
"houseboat",
|
|
"hvac",
|
|
"id_card",
|
|
"imagesmode",
|
|
"info",
|
|
"iron",
|
|
"kayaking",
|
|
"kettle",
|
|
"keyboard_arrow_down",
|
|
"keyboard_arrow_right",
|
|
"keyboard_arrow_up",
|
|
"king_bed",
|
|
"kitchen",
|
|
"landscape",
|
|
"laundry",
|
|
"link",
|
|
"liquor",
|
|
"live_tv",
|
|
"local_bar",
|
|
"local_cafe",
|
|
"local_convenience_store",
|
|
"local_drink",
|
|
"local_laundry_service",
|
|
"local_parking",
|
|
"location_city",
|
|
"location_on",
|
|
"lock",
|
|
"lock_clock",
|
|
"loyalty",
|
|
"luggage",
|
|
"mail",
|
|
"map",
|
|
"meeting_room",
|
|
"microwave",
|
|
"mode_fan",
|
|
"museum",
|
|
"music_cast",
|
|
"music_note",
|
|
"nature",
|
|
"night_shelter",
|
|
"nightlife",
|
|
"open_in_new",
|
|
"pan_zoom",
|
|
"panorama",
|
|
"pause",
|
|
"pedal_bike",
|
|
"person",
|
|
"pets",
|
|
"phone_enabled",
|
|
"photo_camera",
|
|
"play_arrow",
|
|
"pool",
|
|
"print",
|
|
"radio",
|
|
"recommend",
|
|
"redeem",
|
|
"refresh",
|
|
"remove",
|
|
"restaurant",
|
|
"room_service",
|
|
"router",
|
|
"sailing",
|
|
"sauna",
|
|
"scene",
|
|
"search",
|
|
"sell",
|
|
"shopping_bag",
|
|
"shower",
|
|
"single_bed",
|
|
"skateboarding",
|
|
"smoke_free",
|
|
"smoking_rooms",
|
|
"spa",
|
|
"sports_esports",
|
|
"sports_golf",
|
|
"sports_handball",
|
|
"sports_tennis",
|
|
"stairs",
|
|
"star",
|
|
"sticky_note_2",
|
|
"straighten",
|
|
"styler",
|
|
"support_agent",
|
|
"swipe",
|
|
"sync_saved_locally",
|
|
"table_bar",
|
|
"theater_comedy",
|
|
"things_to_do",
|
|
"train",
|
|
"tram",
|
|
"transit_ticket",
|
|
"travel_luggage_and_bags",
|
|
"travel",
|
|
"trophy",
|
|
"tv_guide",
|
|
"tv_remote",
|
|
"upload",
|
|
"visibility_off",
|
|
"visibility",
|
|
"volume_off",
|
|
"volume_up",
|
|
"ward",
|
|
"warning",
|
|
"water_full",
|
|
"wifi",
|
|
"yard",
|
|
].sort()
|
|
|
|
const STYLES = ["outlined", "rounded", "sharp"] as const
|
|
|
|
const OUT = resolve(
|
|
__dirname,
|
|
"../packages/design-system/lib/components/Icons/MaterialIcon/generated.tsx"
|
|
)
|
|
|
|
const PACKAGE_BASE = resolve(
|
|
__dirname,
|
|
"../node_modules/@material-symbols/svg-400"
|
|
)
|
|
|
|
function camelCase(str: string) {
|
|
return str.replace(/[-_](\w)/g, (_, c: string) => c.toUpperCase())
|
|
}
|
|
|
|
async function main() {
|
|
const imports: string[] = []
|
|
const registryEntries: string[] = []
|
|
|
|
const missing: string[] = []
|
|
|
|
for (const icon of ICONS) {
|
|
const styleEntries: string[] = []
|
|
|
|
for (const style of STYLES) {
|
|
const parts: string[] = []
|
|
|
|
const fill0Path = resolve(PACKAGE_BASE, style, `${icon}.svg`)
|
|
const fill1Path = resolve(PACKAGE_BASE, style, `${icon}-fill.svg`)
|
|
|
|
let outlinedVar: string | undefined
|
|
let filledVar: string | undefined
|
|
|
|
if (existsSync(fill0Path)) {
|
|
outlinedVar = `${camelCase(icon)}${camelCase(style)}Outlined`
|
|
imports.push(
|
|
`import ${outlinedVar} from "@material-symbols/svg-400/${style}/${icon}.svg"`
|
|
)
|
|
parts.push(`outlined: ${outlinedVar}`)
|
|
} else {
|
|
missing.push(`${style}/${icon}.svg`)
|
|
}
|
|
|
|
if (existsSync(fill1Path)) {
|
|
filledVar = `${camelCase(icon)}${camelCase(style)}Filled`
|
|
imports.push(
|
|
`import ${filledVar} from "@material-symbols/svg-400/${style}/${icon}-fill.svg"`
|
|
)
|
|
parts.push(`filled: ${filledVar}`)
|
|
}
|
|
|
|
if (parts.length) {
|
|
styleEntries.push(`${style}: { ${parts.join(", ")} }`)
|
|
}
|
|
}
|
|
|
|
// ALWAYS emit the icon key (even if partial)
|
|
if (styleEntries.length) {
|
|
registryEntries.push(`"${icon}": { ${styleEntries.join(", ")} },`)
|
|
} else {
|
|
missing.push(`❌ no variants for "${icon}"`)
|
|
}
|
|
}
|
|
|
|
const content =
|
|
`
|
|
/* AUTO-GENERATED — DO NOT EDIT */
|
|
|
|
import type { FunctionComponent, SVGProps } from "react"
|
|
|
|
${imports.join("\n")}
|
|
|
|
type SvgIcon = FunctionComponent<SVGProps<SVGSVGElement>>
|
|
|
|
export const materialIcons: Record<
|
|
string,
|
|
Partial<{
|
|
outlined: { outlined: SvgIcon; filled?: SvgIcon }
|
|
rounded: { outlined: SvgIcon; filled?: SvgIcon }
|
|
sharp: { outlined: SvgIcon; filled?: SvgIcon }
|
|
}>
|
|
> = {
|
|
${registryEntries.join("\n")}
|
|
}
|
|
|
|
export type MaterialIconName = keyof typeof materialIcons
|
|
`.trim() + "\n"
|
|
|
|
await writeFile(OUT, content, "utf8")
|
|
|
|
console.log(`✅ Generated ${registryEntries.length} icons`)
|
|
if (missing.length) {
|
|
console.warn("⚠️ Missing SVGs:")
|
|
missing.slice(0, 20).forEach((m) => console.warn(" ", m))
|
|
if (missing.length > 20) {
|
|
console.warn(` …and ${missing.length - 20} more`)
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch(console.error)
|