@@ -270,6 +270,8 @@ variablesColors = new Common.VariablesColors {
270270icons = new Common.Icons {
271271 // [optional] Figma frame name. Default: "Icons"
272272 figmaFrameName = "Icons"
273+ // [optional] Figma page name to filter by (useful when multiple pages share the same frame name)
274+ // figmaPageName = "Outlined"
273275 // [optional] RegExp for icon name validation
274276 nameValidateRegexp = "^(ic)_(\\d\\d)_([a-z0-9_]+)$"
275277 // [optional] Replacement pattern
@@ -289,6 +291,8 @@ icons = new Common.Icons {
289291images = new Common.Images {
290292 // [optional] Figma frame name. Default: "Illustrations"
291293 figmaFrameName = "Illustrations"
294+ // [optional] Figma page name to filter by (useful when multiple pages share the same frame name)
295+ // figmaPageName = "Marketing"
292296 // [optional] RegExp for image name validation
293297 nameValidateRegexp = "^(img)_([a-z0-9_]+)$"
294298 // [optional] Replacement pattern
@@ -318,6 +322,7 @@ All Icons and Images entries across platforms extend `Common.FrameSource`, which
318322| Field | Type | Default | Description |
319323| -------------------- | --------- | ------- | ------------------------------------------------------- |
320324| ` figmaFrameName ` | ` String? ` | — | Override Figma frame name for this entry |
325+ | ` figmaPageName ` | ` String? ` | — | Filter by Figma page name for this entry |
321326| ` figmaFileId ` | ` String? ` | — | Override Figma file ID for this entry |
322327| ` rtlProperty ` | ` String? ` | ` "RTL" ` | Figma component property name for RTL variant detection |
323328| ` nameValidateRegexp ` | ` String? ` | — | Regex pattern for name validation |
@@ -431,7 +436,7 @@ icons = new iOS.IconsEntry {
431436}
432437```
433438
434- ` iOS.IconsEntry ` extends ` Common.FrameSource ` , inheriting ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` ,
439+ ` iOS.IconsEntry ` extends ` Common.FrameSource ` , inheriting ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` ,
435440` nameValidateRegexp ` , and ` nameReplaceRegexp ` .
436441
437442| Field | Type | Required | Description |
@@ -448,7 +453,7 @@ icons = new iOS.IconsEntry {
448453| ` renderModeOriginalSuffix ` | ` String? ` | No | Suffix for original render mode |
449454| ` renderModeTemplateSuffix ` | ` String? ` | No | Suffix for template render mode |
450455
451- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
456+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
452457
453458### iOS Images
454459
@@ -489,7 +494,7 @@ images = new iOS.ImagesEntry {
489494| ` renderModeOriginalSuffix ` | ` String? ` | No | Suffix for original render mode |
490495| ` renderModeTemplateSuffix ` | ` String? ` | No | Suffix for template render mode |
491496
492- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
497+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
493498
494499** HEIC Options:**
495500
@@ -647,7 +652,7 @@ icons = new Android.IconsEntry {
647652| ` pathPrecision ` | ` Int(1-6)? ` | No | Coordinate precision for pathData (default: 4) |
648653| ` strictPathValidation ` | ` Boolean? ` | No | Error on pathData > 32,767 bytes (default: false) |
649654
650- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
655+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
651656
652657### Android Images
653658
@@ -672,7 +677,7 @@ images = new Android.ImagesEntry {
672677| ` webpOptions ` | ` WebpOptions? ` | No | WebP encoding options (when format is ` "webp" ` ) |
673678| ` sourceFormat ` | ` SourceFormat? ` | No | Source from Figma: ` "png" ` (default) or ` "svg" ` |
674679
675- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
680+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
676681
677682** WebP Options:**
678683
@@ -759,7 +764,7 @@ icons = new Flutter.IconsEntry {
759764| ` className ` | ` String? ` | No | Class name (default: ` AppIcons ` ) |
760765| ` nameStyle ` | ` NameStyle? ` | No | Name style for generated names |
761766
762- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
767+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
763768
764769### Flutter Images
765770
@@ -790,7 +795,7 @@ images = new Flutter.ImagesEntry {
790795| ` sourceFormat ` | ` SourceFormat? ` | No | Source from Figma: ` "png" ` or ` "svg" ` |
791796| ` nameStyle ` | ` NameStyle? ` | No | Name style for generated names |
792797
793- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
798+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
794799
795800---
796801
@@ -862,7 +867,7 @@ icons = new Web.IconsEntry {
862867| ` iconSize ` | ` Int? ` | No | Icon size in pixels for viewBox (default: 24) |
863868| ` nameStyle ` | ` NameStyle? ` | No | Name style for generated names |
864869
865- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
870+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
866871
867872### Web Images
868873
@@ -880,7 +885,7 @@ images = new Web.ImagesEntry {
880885| ` assetsDirectory ` | ` String? ` | No | Directory for raw image assets |
881886| ` generateReactComponents ` | ` Boolean? ` | No | Generate React TSX components (default: true) |
882887
883- ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
888+ ** Inherited from ` FrameSource ` :** ` figmaFrameName ` , ` figmaPageName ` , ` figmaFileId ` , ` rtlProperty ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` .
884889
885890---
886891
@@ -982,7 +987,7 @@ For multi-entry icons and images, per-entry fields fall back to `common` setting
9829872 . ` common.icons.figmaFrameName ` or ` common.images.figmaFrameName `
9839883 . Default: ` "Icons" ` for icons, ` "Illustrations" ` for images
984989
985- The same fallback applies to ` nameValidateRegexp ` , ` nameReplaceRegexp ` , and ` nameStyle ` .
990+ The same fallback applies to ` figmaPageName ` , ` nameValidateRegexp ` , ` nameReplaceRegexp ` , and ` nameStyle ` .
986991
987992### Performance
988993
@@ -1104,6 +1109,100 @@ Without the typed constructor (e.g., `new { ... }` inside a Listing), PKL will r
11041109This pattern applies to all platforms: ` iOS.ColorsEntry ` , ` iOS.IconsEntry ` , ` iOS.ImagesEntry ` ,
11051110` Android.ColorsEntry ` , ` Android.IconsEntry ` , ` Android.ImagesEntry ` , ` Flutter.ColorsEntry ` , etc.
11061111
1112+ ### DRY Configs with ` for ` -Generators
1113+
1114+ When you have many entries that share the same structure (e.g., 15+ icon categories with identical settings except
1115+ ` figmaFrameName ` and ` assetsFolder ` ), use PKL ` local ` Mapping and ` for ` -generators to eliminate duplication.
1116+
1117+ ** Define categories as ` local ` Mapping** — ` local ` properties are not included in the output:
1118+
1119+ ``` pkl
1120+ // figmaFrameName → assetsFolder
1121+ local iconCategories: Mapping<String, String> = new {
1122+ ["Actions"] = "Actions"
1123+ ["Chart"] = "Chart"
1124+ ["Communication, Media, Art"] = "CommunicationMediaArt"
1125+ ["Text editor"] = "TextEditor"
1126+ // ... more categories
1127+ }
1128+ ```
1129+
1130+ ** Generate entries with ` for ` :**
1131+
1132+ ``` pkl
1133+ icons = new Listing {
1134+ for (frameName, folder in iconCategories) {
1135+ new iOS.IconsEntry {
1136+ figmaFrameName = frameName
1137+ format = "svg"
1138+ xcassetsPath = "./Resources/Icons.xcassets"
1139+ assetsFolder = folder
1140+ imageSwift = "./Generated/\(folder)Icons.generated.swift"
1141+ }
1142+ }
1143+ }
1144+ ```
1145+
1146+ You can mix manual entries and ` for ` -generators in the same ` Listing ` :
1147+
1148+ ``` pkl
1149+ icons = new Listing {
1150+ // Manual entries for special cases
1151+ new iOS.IconsEntry {
1152+ figmaFrameName = "Colored Icons"
1153+ renderMode = "default"
1154+ // ...
1155+ }
1156+
1157+ // Generated entries for categories with identical settings
1158+ for (frameName, folder in iconCategories) {
1159+ new iOS.IconsEntry {
1160+ figmaFrameName = frameName
1161+ assetsFolder = folder
1162+ // ...
1163+ }
1164+ }
1165+ }
1166+ ```
1167+
1168+ ** String interpolation** — use ` \(expr) ` to build paths from category data:
1169+
1170+ ``` pkl
1171+ imageSwift = "./Generated/\(folder)Icons.generated.swift"
1172+ assetsFolder = "\(folder)Dc" // e.g., "ActionsDc"
1173+ ```
1174+
1175+ ** Multiple Mappings** for different groups — define separate Mappings when groups need different settings:
1176+
1177+ ``` pkl
1178+ local allCategories: Mapping<String, String> = new { /* 17 items */ }
1179+ local dcCategories: Mapping<String, String> = new { /* 15 items — all except Logo, Template */ }
1180+
1181+ icons = new Listing {
1182+ // Template icons from allCategories
1183+ for (frameName, folder in allCategories) {
1184+ new iOS.IconsEntry { /* template settings */ }
1185+ }
1186+ // Double Color icons from dcCategories
1187+ for (frameName, folder in dcCategories) {
1188+ new iOS.IconsEntry { figmaFileId = "other-file"; renderMode = "default"; /* ... */ }
1189+ }
1190+ }
1191+ ```
1192+
1193+ ** Verification** — always verify that the refactored config produces the same output:
1194+
1195+ ``` bash
1196+ # Save output before refactoring
1197+ pkl eval --format json exfig.pkl > before.json
1198+
1199+ # After refactoring
1200+ pkl eval --format json exfig.pkl > after.json
1201+
1202+ # Compare (order of entries within Listing is preserved)
1203+ diff before.json after.json
1204+ ```
1205+
11071206---
11081207
11091208## Validation
0 commit comments