web/lib/opal/scripts/README.md
Integrating @svgr/webpack into the TypeScript compiler was not working via the recommended route (Next.js webpack configuration).
The automatic SVG-to-React component conversion was causing compilation issues and import resolution problems.
Therefore, we manually convert each SVG into a TSX file using SVGR CLI with a custom template.
All scripts in this directory should be run from the opal package root (web/lib/opal/).
web/lib/opal/
├── scripts/ # SVG conversion tooling (this directory)
│ ├── convert-svg.sh # Converts SVGs into React components
│ └── icon-template.js # Shared SVGR template (used for icons, logos, and illustrations)
├── src/
│ ├── icons/ # Small, single-colour icons (stroke = currentColor)
│ ├── logos/ # Brand/vendor logos (original colours preserved)
│ └── illustrations/ # Larger, multi-colour illustrations (colours preserved)
└── package.json
| Icons | Logos | Illustrations | |
|---|---|---|---|
| Import path | @opal/icons | @opal/logos | @opal/illustrations |
| Location | src/icons/ | src/logos/ | src/illustrations/ |
| Colour | Overridable via currentColor | Fixed — original brand colours preserved | Fixed — original SVG colours preserved |
| Script flag | (none) | --logo | --illustration |
| Use case | UI elements, actions, navigation | Provider logos, platform logos, brand marks | Empty states, error pages, placeholders |
icon-template.jsA custom SVGR template that generates components with the following features:
IconProps from @opal/types for consistent typingsize prop for controlling icon dimensionswidth and height attributes bound to the size propconvert-svg.shConverts an SVG into a React component. Behaviour depends on the mode:
Icon mode (default):
stroke, stroke-opacity, width, and height attributeswidth={size}, height={size}, and stroke="currentColor"color propertyLogo mode (--logo):
width and height attributes (all colours preserved)width={size} and height={size}stroke="currentColor" — logos keep their original brand coloursIllustration mode (--illustration):
width and height attributes (all colours preserved)width={size} and height={size}stroke="currentColor" — illustrations keep their original coloursBoth --logo and --illustration produce the same output — the distinction is purely organizational (different directories, different barrel exports).
All modes automatically delete the source SVG file after successful conversion.
# From web/lib/opal/
./scripts/convert-svg.sh src/icons/my-icon.svg
Then add the export to src/icons/index.ts:
export { default as SvgMyIcon } from "@opal/icons/my-icon";
# From web/lib/opal/
./scripts/convert-svg.sh --logo src/logos/my-logo.svg
Then add the export to src/logos/index.ts:
export { default as SvgMyLogo } from "@opal/logos/my-logo";
# From web/lib/opal/
./scripts/convert-svg.sh --illustration src/illustrations/my-illustration.svg
Then add the export to src/illustrations/index.ts:
export { default as SvgMyIllustration } from "@opal/illustrations/my-illustration";
If you prefer to run the SVGR command directly:
For icons (strips colours):
bunx @svgr/cli <file>.svg --typescript --svgo-config '{"plugins":[{"name":"removeAttrs","params":{"attrs":["stroke","stroke-opacity","width","height"]}}]}' --template scripts/icon-template.js > <file>.tsx
For logos and illustrations (preserves colours):
bunx @svgr/cli <file>.svg --typescript --svgo-config '{"plugins":[{"name":"removeAttrs","params":{"attrs":["width","height"]}}]}' --template scripts/icon-template.js > <file>.tsx
After running either manual command, remember to delete the original SVG file.