diff --git a/Package.swift b/Package.swift index 8ce5a28..231d3f8 100644 --- a/Package.swift +++ b/Package.swift @@ -24,14 +24,26 @@ let package = Package( name: "NucleusIcons", targets: ["NucleusIcons"] ), + .library( + name: "NucleusTokens", + targets: ["NucleusTokens"] + ), ], targets: [ + // Zero-dependency protocol target. Each primitive conforms in its own module so consumers + // only link what they import — no target that depends on every module. + .target( + name: "NucleusTokens", + path: "ios/Sources/NucleusTokens" + ), .target( name: "NucleusColors", + dependencies: ["NucleusTokens"], path: "ios/Sources/NucleusColors" ), .target( name: "NucleusFonts", + dependencies: ["NucleusTokens"], path: "ios/Sources/NucleusFonts", resources: [ .process("Resources/Fonts"), @@ -39,11 +51,12 @@ let package = Package( ), .target( name: "NucleusButtons", - dependencies: ["NucleusColors", "NucleusFonts"], + dependencies: ["NucleusTokens", "NucleusColors", "NucleusFonts"], path: "ios/Sources/NucleusButtons" ), .target( name: "NucleusIcons", + dependencies: ["NucleusTokens"], path: "ios/Sources/NucleusIcons", resources: [ .process("Resources/Icons.xcassets"), diff --git a/android/nucleus/src/main/java/com/worldcoin/nucleus/tokens/NucleusTokenResolver.kt b/android/nucleus/src/main/java/com/worldcoin/nucleus/tokens/NucleusTokenResolver.kt new file mode 100644 index 0000000..d47f7bb --- /dev/null +++ b/android/nucleus/src/main/java/com/worldcoin/nucleus/tokens/NucleusTokenResolver.kt @@ -0,0 +1,707 @@ +// AUTOGENERATED by tokens/build — do not edit by hand. + +package com.worldcoin.nucleus.tokens + +import androidx.annotation.DrawableRes +import androidx.compose.ui.graphics.Color +import com.worldcoin.nucleus.R + +/** + * Resolves a canonical Nucleus token-path string — as emitted by the backend SDUI wire contract — + * to the corresponding compile-time Nucleus token. Forward-only; returns null for an unknown path + * so callers can fall back to a default and report the miss (server and client ship on different + * cadences, so an unknown token is expected and must not crash). + */ +object NucleusTokenResolver { + /** Resolves a color token path, e.g. `semantic.color.text.primary`, for the active theme. */ + fun color( + token: String, + isDark: Boolean, + ): Color? = if (isDark) colorDark(token) else colorLight(token) + + /** Resolves a typography token path, e.g. `typography.subtitle.s3`. */ + fun font(token: String): NucleusFontStyle? = + when (token) { + "b1" -> NucleusFonts.b1 + "b2" -> NucleusFonts.b2 + "b3" -> NucleusFonts.b3 + "b4" -> NucleusFonts.b4 + "d1" -> NucleusFonts.d1 + "h1" -> NucleusFonts.h1 + "h2" -> NucleusFonts.h2 + "h3" -> NucleusFonts.h3 + "h4" -> NucleusFonts.h4 + "l1" -> NucleusFonts.l1 + "l2" -> NucleusFonts.l2 + "l3" -> NucleusFonts.l3 + "n1" -> NucleusFonts.n1 + "n2" -> NucleusFonts.n2 + "n3" -> NucleusFonts.n3 + "n4" -> NucleusFonts.n4 + "n5" -> NucleusFonts.n5 + "s1" -> NucleusFonts.s1 + "s2" -> NucleusFonts.s2 + "s3" -> NucleusFonts.s3 + "s4" -> NucleusFonts.s4 + else -> null + } + + /** Resolves a button style token path, e.g. `component.button.inverse.32`. */ + fun button(token: String): NucleusButtonStyle? = + when (token) { + "disabled.32" -> NucleusButtons.disabled32 + "disabled.40" -> NucleusButtons.disabled40 + "disabled.48" -> NucleusButtons.disabled48 + "ghost.32" -> NucleusButtons.ghost32 + "ghost.40" -> NucleusButtons.ghost40 + "ghost.48" -> NucleusButtons.ghost48 + "inverse.32" -> NucleusButtons.inverse32 + "inverse.40" -> NucleusButtons.inverse40 + "inverse.48" -> NucleusButtons.inverse48 + "primary.32" -> NucleusButtons.primary32 + "primary.40" -> NucleusButtons.primary40 + "primary.48" -> NucleusButtons.primary48 + "secondary.32" -> NucleusButtons.secondary32 + "secondary.40" -> NucleusButtons.secondary40 + "secondary.48" -> NucleusButtons.secondary48 + "tertiary.32" -> NucleusButtons.tertiary32 + "tertiary.40" -> NucleusButtons.tertiary40 + "tertiary.48" -> NucleusButtons.tertiary48 + else -> null + } + + /** Resolves an icon token path, e.g. `icon.arrow-right.regular`, to a drawable resource id. */ + @DrawableRes + fun iconRes(token: String): Int? = + when (token) { + "airplane.outline" -> R.drawable.nucleus_icon_airplane_outline + "airplane.regular" -> R.drawable.nucleus_icon_airplane_regular + "airplane.solid" -> R.drawable.nucleus_icon_airplane_solid + "antenna-signal.outline" -> R.drawable.nucleus_icon_antenna_signal_outline + "antenna-signal.regular" -> R.drawable.nucleus_icon_antenna_signal_regular + "apple-mac.outline" -> R.drawable.nucleus_icon_apple_mac_outline + "apple-mac.regular" -> R.drawable.nucleus_icon_apple_mac_regular + "apple-mac.solid" -> R.drawable.nucleus_icon_apple_mac_solid + "arrow-down-left.outline" -> R.drawable.nucleus_icon_arrow_down_left_outline + "arrow-down-left.regular" -> R.drawable.nucleus_icon_arrow_down_left_regular + "arrow-down-left.solid" -> R.drawable.nucleus_icon_arrow_down_left_solid + "arrow-down-right.outline" -> R.drawable.nucleus_icon_arrow_down_right_outline + "arrow-down-right.regular" -> R.drawable.nucleus_icon_arrow_down_right_regular + "arrow-down-right.solid" -> R.drawable.nucleus_icon_arrow_down_right_solid + "arrow-down.outline" -> R.drawable.nucleus_icon_arrow_down_outline + "arrow-down.regular" -> R.drawable.nucleus_icon_arrow_down_regular + "arrow-down.solid" -> R.drawable.nucleus_icon_arrow_down_solid + "arrow-left.outline" -> R.drawable.nucleus_icon_arrow_left_outline + "arrow-left.regular" -> R.drawable.nucleus_icon_arrow_left_regular + "arrow-left.solid" -> R.drawable.nucleus_icon_arrow_left_solid + "arrow-right.outline" -> R.drawable.nucleus_icon_arrow_right_outline + "arrow-right.regular" -> R.drawable.nucleus_icon_arrow_right_regular + "arrow-right.solid" -> R.drawable.nucleus_icon_arrow_right_solid + "arrow-split.outline" -> R.drawable.nucleus_icon_arrow_split_outline + "arrow-split.regular" -> R.drawable.nucleus_icon_arrow_split_regular + "arrow-split.solid" -> R.drawable.nucleus_icon_arrow_split_solid + "arrow-up-left.outline" -> R.drawable.nucleus_icon_arrow_up_left_outline + "arrow-up-left.regular" -> R.drawable.nucleus_icon_arrow_up_left_regular + "arrow-up-left.solid" -> R.drawable.nucleus_icon_arrow_up_left_solid + "arrow-up-right.outline" -> R.drawable.nucleus_icon_arrow_up_right_outline + "arrow-up-right.regular" -> R.drawable.nucleus_icon_arrow_up_right_regular + "arrow-up.outline" -> R.drawable.nucleus_icon_arrow_up_outline + "arrow-up.regular" -> R.drawable.nucleus_icon_arrow_up_regular + "arrow-up.solid" -> R.drawable.nucleus_icon_arrow_up_solid + "at-sign.outline" -> R.drawable.nucleus_icon_at_sign_outline + "at-sign.regular" -> R.drawable.nucleus_icon_at_sign_regular + "at-sign.solid" -> R.drawable.nucleus_icon_at_sign_solid + "badge-check.outline" -> R.drawable.nucleus_icon_badge_check_outline + "badge-check.regular" -> R.drawable.nucleus_icon_badge_check_regular + "badge-check.solid" -> R.drawable.nucleus_icon_badge_check_solid + "badge-not-checked.outline" -> R.drawable.nucleus_icon_badge_not_checked_outline + "badge-not-checked.regular" -> R.drawable.nucleus_icon_badge_not_checked_regular + "badge-not-checked.solid" -> R.drawable.nucleus_icon_badge_not_checked_solid + "bag.outline" -> R.drawable.nucleus_icon_bag_outline + "bag.regular" -> R.drawable.nucleus_icon_bag_regular + "bag.solid" -> R.drawable.nucleus_icon_bag_solid + "bank.outline" -> R.drawable.nucleus_icon_bank_outline + "bank.regular" -> R.drawable.nucleus_icon_bank_regular + "bank.solid" -> R.drawable.nucleus_icon_bank_solid + "bell-notification.outline" -> R.drawable.nucleus_icon_bell_notification_outline + "bell-notification.regular" -> R.drawable.nucleus_icon_bell_notification_regular + "bell-notification.solid" -> R.drawable.nucleus_icon_bell_notification_solid + "bell-slash.outline" -> R.drawable.nucleus_icon_bell_slash_outline + "bell-slash.regular" -> R.drawable.nucleus_icon_bell_slash_regular + "bell-slash.solid" -> R.drawable.nucleus_icon_bell_slash_solid + "bell.outline" -> R.drawable.nucleus_icon_bell_outline + "bell.regular" -> R.drawable.nucleus_icon_bell_regular + "bell.solid" -> R.drawable.nucleus_icon_bell_solid + "bookmark.outline" -> R.drawable.nucleus_icon_bookmark_outline + "bookmark.regular" -> R.drawable.nucleus_icon_bookmark_regular + "bookmark.solid" -> R.drawable.nucleus_icon_bookmark_solid + "box-iso.outline" -> R.drawable.nucleus_icon_box_iso_outline + "box-iso.regular" -> R.drawable.nucleus_icon_box_iso_regular + "box-iso.solid" -> R.drawable.nucleus_icon_box_iso_solid + "calendar-plus.outline" -> R.drawable.nucleus_icon_calendar_plus_outline + "calendar-plus.regular" -> R.drawable.nucleus_icon_calendar_plus_regular + "calendar-plus.solid" -> R.drawable.nucleus_icon_calendar_plus_solid + "calendar.outline" -> R.drawable.nucleus_icon_calendar_outline + "calendar.regular" -> R.drawable.nucleus_icon_calendar_regular + "calendar.solid" -> R.drawable.nucleus_icon_calendar_solid + "camera.outline" -> R.drawable.nucleus_icon_camera_outline + "camera.regular" -> R.drawable.nucleus_icon_camera_regular + "camera.solid" -> R.drawable.nucleus_icon_camera_solid + "cash-multi.outline" -> R.drawable.nucleus_icon_cash_multi_outline + "cash-multi.regular" -> R.drawable.nucleus_icon_cash_multi_regular + "cash-multi.solid" -> R.drawable.nucleus_icon_cash_multi_solid + "cash.outline" -> R.drawable.nucleus_icon_cash_outline + "cash.regular" -> R.drawable.nucleus_icon_cash_regular + "cash.solid" -> R.drawable.nucleus_icon_cash_solid + "cellular-no-signal.outline" -> R.drawable.nucleus_icon_cellular_no_signal_outline + "cellular-no-signal.regular" -> R.drawable.nucleus_icon_cellular_no_signal_regular + "cellular-no-signal.solid" -> R.drawable.nucleus_icon_cellular_no_signal_solid + "cellular.outline" -> R.drawable.nucleus_icon_cellular_outline + "cellular.regular" -> R.drawable.nucleus_icon_cellular_regular + "cellular.solid" -> R.drawable.nucleus_icon_cellular_solid + "chat-bubble-empty.outline" -> R.drawable.nucleus_icon_chat_bubble_empty_outline + "chat-bubble-empty.regular" -> R.drawable.nucleus_icon_chat_bubble_empty_regular + "chat-bubble-empty.solid" -> R.drawable.nucleus_icon_chat_bubble_empty_solid + "chat-bubble-question.outline" -> R.drawable.nucleus_icon_chat_bubble_question_outline + "chat-bubble-question.regular" -> R.drawable.nucleus_icon_chat_bubble_question_regular + "chat-bubble-question.solid" -> R.drawable.nucleus_icon_chat_bubble_question_solid + "chat-bubble-translate.outline" -> R.drawable.nucleus_icon_chat_bubble_translate_outline + "chat-bubble-translate.regular" -> R.drawable.nucleus_icon_chat_bubble_translate_regular + "chat-bubble-translate.solid" -> R.drawable.nucleus_icon_chat_bubble_translate_solid + "chat-bubble-warning.outline" -> R.drawable.nucleus_icon_chat_bubble_warning_outline + "chat-bubble-warning.regular" -> R.drawable.nucleus_icon_chat_bubble_warning_regular + "chat-bubble-warning.solid" -> R.drawable.nucleus_icon_chat_bubble_warning_solid + "chat-bubble.outline" -> R.drawable.nucleus_icon_chat_bubble_outline + "chat-bubble.regular" -> R.drawable.nucleus_icon_chat_bubble_regular + "chat-bubble.solid" -> R.drawable.nucleus_icon_chat_bubble_solid + "chat-lines.outline" -> R.drawable.nucleus_icon_chat_lines_outline + "chat-lines.regular" -> R.drawable.nucleus_icon_chat_lines_regular + "chat-lines.solid" -> R.drawable.nucleus_icon_chat_lines_solid + "check-circle.outline" -> R.drawable.nucleus_icon_check_circle_outline + "check-circle.regular" -> R.drawable.nucleus_icon_check_circle_regular + "check-circle.solid" -> R.drawable.nucleus_icon_check_circle_solid + "check.outline" -> R.drawable.nucleus_icon_check_outline + "check.regular" -> R.drawable.nucleus_icon_check_regular + "check.solid" -> R.drawable.nucleus_icon_check_solid + "clock-rotate-right.outline" -> R.drawable.nucleus_icon_clock_rotate_right_outline + "clock-rotate-right.regular" -> R.drawable.nucleus_icon_clock_rotate_right_regular + "clock-rotate-right.solid" -> R.drawable.nucleus_icon_clock_rotate_right_solid + "clock.outline" -> R.drawable.nucleus_icon_clock_outline + "clock.regular" -> R.drawable.nucleus_icon_clock_regular + "clock.solid" -> R.drawable.nucleus_icon_clock_solid + "cloud-download.outline" -> R.drawable.nucleus_icon_cloud_download_outline + "cloud-download.regular" -> R.drawable.nucleus_icon_cloud_download_regular + "cloud-download.solid" -> R.drawable.nucleus_icon_cloud_download_solid + "cloud.outline" -> R.drawable.nucleus_icon_cloud_outline + "cloud.regular" -> R.drawable.nucleus_icon_cloud_regular + "cloud.solid" -> R.drawable.nucleus_icon_cloud_solid + "coins.outline" -> R.drawable.nucleus_icon_coins_outline + "coins.regular" -> R.drawable.nucleus_icon_coins_regular + "coins.solid" -> R.drawable.nucleus_icon_coins_solid + "compass.outline" -> R.drawable.nucleus_icon_compass_outline + "compass.regular" -> R.drawable.nucleus_icon_compass_regular + "compass.solid" -> R.drawable.nucleus_icon_compass_solid + "copy.outline" -> R.drawable.nucleus_icon_copy_outline + "copy.regular" -> R.drawable.nucleus_icon_copy_regular + "copy.solid" -> R.drawable.nucleus_icon_copy_solid + "coupon.outline" -> R.drawable.nucleus_icon_coupon_outline + "coupon.regular" -> R.drawable.nucleus_icon_coupon_regular + "coupon.solid" -> R.drawable.nucleus_icon_coupon_solid + "cube.outline" -> R.drawable.nucleus_icon_cube_outline + "cube.regular" -> R.drawable.nucleus_icon_cube_regular + "cube.solid" -> R.drawable.nucleus_icon_cube_solid + "delivery-check.outline" -> R.drawable.nucleus_icon_delivery_check_outline + "delivery-check.regular" -> R.drawable.nucleus_icon_delivery_check_regular + "delivery-check.solid" -> R.drawable.nucleus_icon_delivery_check_solid + "delivery-truck.outline" -> R.drawable.nucleus_icon_delivery_truck_outline + "delivery-truck.regular" -> R.drawable.nucleus_icon_delivery_truck_regular + "delivery-truck.solid" -> R.drawable.nucleus_icon_delivery_truck_solid + "double-check.outline" -> R.drawable.nucleus_icon_double_check_outline + "double-check.regular" -> R.drawable.nucleus_icon_double_check_regular + "double-check.solid" -> R.drawable.nucleus_icon_double_check_solid + "download.outline" -> R.drawable.nucleus_icon_download_outline + "download.regular" -> R.drawable.nucleus_icon_download_regular + "download.solid" -> R.drawable.nucleus_icon_download_solid + "edit-pencil.outline" -> R.drawable.nucleus_icon_edit_pencil_outline + "edit-pencil.regular" -> R.drawable.nucleus_icon_edit_pencil_regular + "edit-pencil.solid" -> R.drawable.nucleus_icon_edit_pencil_solid + "empty-page.outline" -> R.drawable.nucleus_icon_empty_page_outline + "empty-page.regular" -> R.drawable.nucleus_icon_empty_page_regular + "empty-page.solid" -> R.drawable.nucleus_icon_empty_page_solid + "eye-closed.outline" -> R.drawable.nucleus_icon_eye_closed_outline + "eye-closed.regular" -> R.drawable.nucleus_icon_eye_closed_regular + "eye-closed.solid" -> R.drawable.nucleus_icon_eye_closed_solid + "eye.outline" -> R.drawable.nucleus_icon_eye_outline + "eye.regular" -> R.drawable.nucleus_icon_eye_regular + "eye.solid" -> R.drawable.nucleus_icon_eye_solid + "face-id.outline" -> R.drawable.nucleus_icon_face_id_outline + "face-id.regular" -> R.drawable.nucleus_icon_face_id_regular + "face-id.solid" -> R.drawable.nucleus_icon_face_id_solid + "filter-list.outline" -> R.drawable.nucleus_icon_filter_list_outline + "filter-list.regular" -> R.drawable.nucleus_icon_filter_list_regular + "filter-list.solid" -> R.drawable.nucleus_icon_filter_list_solid + "flash.outline" -> R.drawable.nucleus_icon_flash_outline + "flash.regular" -> R.drawable.nucleus_icon_flash_regular + "flash.solid" -> R.drawable.nucleus_icon_flash_solid + "gif.outline" -> R.drawable.nucleus_icon_gif_outline + "gif.regular" -> R.drawable.nucleus_icon_gif_regular + "gif.solid" -> R.drawable.nucleus_icon_gif_solid + "gift.outline" -> R.drawable.nucleus_icon_gift_outline + "gift.solid" -> R.drawable.nucleus_icon_gift_solid + "globe.outline" -> R.drawable.nucleus_icon_globe_outline + "globe.regular" -> R.drawable.nucleus_icon_globe_regular + "globe.solid" -> R.drawable.nucleus_icon_globe_solid + "graduation-cap.outline" -> R.drawable.nucleus_icon_graduation_cap_outline + "graduation-cap.regular" -> R.drawable.nucleus_icon_graduation_cap_regular + "graduation-cap.solid" -> R.drawable.nucleus_icon_graduation_cap_solid + "graph-down.outline" -> R.drawable.nucleus_icon_graph_down_outline + "graph-down.regular" -> R.drawable.nucleus_icon_graph_down_regular + "graph-down.solid" -> R.drawable.nucleus_icon_graph_down_solid + "graph-up.outline" -> R.drawable.nucleus_icon_graph_up_outline + "graph-up.regular" -> R.drawable.nucleus_icon_graph_up_regular + "graph-up.solid" -> R.drawable.nucleus_icon_graph_up_solid + "group.outline" -> R.drawable.nucleus_icon_group_outline + "group.regular" -> R.drawable.nucleus_icon_group_regular + "group.solid" -> R.drawable.nucleus_icon_group_solid + "heart.outline" -> R.drawable.nucleus_icon_heart_outline + "heart.regular" -> R.drawable.nucleus_icon_heart_regular + "heart.solid" -> R.drawable.nucleus_icon_heart_solid + "help-circle.outline" -> R.drawable.nucleus_icon_help_circle_outline + "help-circle.regular" -> R.drawable.nucleus_icon_help_circle_regular + "help-circle.solid" -> R.drawable.nucleus_icon_help_circle_solid + "home.outline" -> R.drawable.nucleus_icon_home_outline + "home.regular" -> R.drawable.nucleus_icon_home_regular + "home.solid" -> R.drawable.nucleus_icon_home_solid + "info-circle.outline" -> R.drawable.nucleus_icon_info_circle_outline + "info-circle.regular" -> R.drawable.nucleus_icon_info_circle_regular + "info-circle.solid" -> R.drawable.nucleus_icon_info_circle_solid + "instagram.outline" -> R.drawable.nucleus_icon_instagram_outline + "instagram.regular" -> R.drawable.nucleus_icon_instagram_regular + "instagram.solid" -> R.drawable.nucleus_icon_instagram_solid + "key.outline" -> R.drawable.nucleus_icon_key_outline + "key.regular" -> R.drawable.nucleus_icon_key_regular + "key.solid" -> R.drawable.nucleus_icon_key_solid + "language.outline" -> R.drawable.nucleus_icon_language_outline + "language.regular" -> R.drawable.nucleus_icon_language_regular + "language.solid" -> R.drawable.nucleus_icon_language_solid + "link-slash.outline" -> R.drawable.nucleus_icon_link_slash_outline + "link-slash.regular" -> R.drawable.nucleus_icon_link_slash_regular + "link.outline" -> R.drawable.nucleus_icon_link_outline + "link.regular" -> R.drawable.nucleus_icon_link_regular + "link.solid" -> R.drawable.nucleus_icon_link_solid + "list.outline" -> R.drawable.nucleus_icon_list_outline + "list.regular" -> R.drawable.nucleus_icon_list_regular + "list.solid" -> R.drawable.nucleus_icon_list_solid + "lock.outline" -> R.drawable.nucleus_icon_lock_outline + "lock.regular" -> R.drawable.nucleus_icon_lock_regular + "lock.solid" -> R.drawable.nucleus_icon_lock_solid + "log-in.outline" -> R.drawable.nucleus_icon_log_in_outline + "log-in.regular" -> R.drawable.nucleus_icon_log_in_regular + "log-in.solid" -> R.drawable.nucleus_icon_log_in_solid + "log-out.outline" -> R.drawable.nucleus_icon_log_out_outline + "log-out.regular" -> R.drawable.nucleus_icon_log_out_regular + "log-out.solid" -> R.drawable.nucleus_icon_log_out_solid + "magic-wand.outline" -> R.drawable.nucleus_icon_magic_wand_outline + "magic-wand.regular" -> R.drawable.nucleus_icon_magic_wand_regular + "magic-wand.solid" -> R.drawable.nucleus_icon_magic_wand_solid + "mail.outline" -> R.drawable.nucleus_icon_mail_outline + "mail.regular" -> R.drawable.nucleus_icon_mail_regular + "mail.solid" -> R.drawable.nucleus_icon_mail_solid + "map-pin.outline" -> R.drawable.nucleus_icon_map_pin_outline + "map-pin.regular" -> R.drawable.nucleus_icon_map_pin_regular + "map-pin.solid" -> R.drawable.nucleus_icon_map_pin_solid + "map.outline" -> R.drawable.nucleus_icon_map_outline + "map.regular" -> R.drawable.nucleus_icon_map_regular + "map.solid" -> R.drawable.nucleus_icon_map_solid + "maps-arrow.outline" -> R.drawable.nucleus_icon_maps_arrow_outline + "maps-arrow.regular" -> R.drawable.nucleus_icon_maps_arrow_regular + "maps-arrow.solid" -> R.drawable.nucleus_icon_maps_arrow_solid + "media-image.outline" -> R.drawable.nucleus_icon_media_image_outline + "media-image.regular" -> R.drawable.nucleus_icon_media_image_regular + "media-image.solid" -> R.drawable.nucleus_icon_media_image_solid + "microphone.outline" -> R.drawable.nucleus_icon_microphone_outline + "microphone.regular" -> R.drawable.nucleus_icon_microphone_regular + "microphone.solid" -> R.drawable.nucleus_icon_microphone_solid + "minus.outline" -> R.drawable.nucleus_icon_minus_outline + "minus.regular" -> R.drawable.nucleus_icon_minus_regular + "minus.solid" -> R.drawable.nucleus_icon_minus_solid + "more-horiz-circle.outline" -> R.drawable.nucleus_icon_more_horiz_circle_outline + "more-horiz-circle.regular" -> R.drawable.nucleus_icon_more_horiz_circle_regular + "more-horiz-circle.solid" -> R.drawable.nucleus_icon_more_horiz_circle_solid + "more-horiz.outline" -> R.drawable.nucleus_icon_more_horiz_outline + "more-horiz.regular" -> R.drawable.nucleus_icon_more_horiz_regular + "more-horiz.solid" -> R.drawable.nucleus_icon_more_horiz_solid + "nav-arrow-down.outline" -> R.drawable.nucleus_icon_nav_arrow_down_outline + "nav-arrow-down.regular" -> R.drawable.nucleus_icon_nav_arrow_down_regular + "nav-arrow-down.solid" -> R.drawable.nucleus_icon_nav_arrow_down_solid + "nav-arrow-left.outline" -> R.drawable.nucleus_icon_nav_arrow_left_outline + "nav-arrow-left.regular" -> R.drawable.nucleus_icon_nav_arrow_left_regular + "nav-arrow-left.solid" -> R.drawable.nucleus_icon_nav_arrow_left_solid + "nav-arrow-right.outline" -> R.drawable.nucleus_icon_nav_arrow_right_outline + "nav-arrow-right.regular" -> R.drawable.nucleus_icon_nav_arrow_right_regular + "nav-arrow-right.solid" -> R.drawable.nucleus_icon_nav_arrow_right_solid + "nav-arrow-up.outline" -> R.drawable.nucleus_icon_nav_arrow_up_outline + "nav-arrow-up.regular" -> R.drawable.nucleus_icon_nav_arrow_up_regular + "nav-arrow-up.solid" -> R.drawable.nucleus_icon_nav_arrow_up_solid + "open-new-window.outline" -> R.drawable.nucleus_icon_open_new_window_outline + "open-new-window.regular" -> R.drawable.nucleus_icon_open_new_window_regular + "open-new-window.solid" -> R.drawable.nucleus_icon_open_new_window_solid + "orb.outline" -> R.drawable.nucleus_icon_orb_outline + "orb.regular" -> R.drawable.nucleus_icon_orb_regular + "orb.solid" -> R.drawable.nucleus_icon_orb_solid + "page.outline" -> R.drawable.nucleus_icon_page_outline + "page.regular" -> R.drawable.nucleus_icon_page_regular + "page.solid" -> R.drawable.nucleus_icon_page_solid + "passkey.outline" -> R.drawable.nucleus_icon_passkey_outline + "passkey.regular" -> R.drawable.nucleus_icon_passkey_regular + "passkey.solid" -> R.drawable.nucleus_icon_passkey_solid + "percentage.outline" -> R.drawable.nucleus_icon_percentage_outline + "percentage.regular" -> R.drawable.nucleus_icon_percentage_regular + "percentage.solid" -> R.drawable.nucleus_icon_percentage_solid + "pin.outline" -> R.drawable.nucleus_icon_pin_outline + "pin.regular" -> R.drawable.nucleus_icon_pin_regular + "pin.solid" -> R.drawable.nucleus_icon_pin_solid + "play.outline" -> R.drawable.nucleus_icon_play_outline + "play.regular" -> R.drawable.nucleus_icon_play_regular + "play.solid" -> R.drawable.nucleus_icon_play_solid + "plus.outline" -> R.drawable.nucleus_icon_plus_outline + "plus.regular" -> R.drawable.nucleus_icon_plus_regular + "plus.solid" -> R.drawable.nucleus_icon_plus_solid + "post.outline" -> R.drawable.nucleus_icon_post_outline + "post.regular" -> R.drawable.nucleus_icon_post_regular + "post.solid" -> R.drawable.nucleus_icon_post_solid + "profile-circle.outline" -> R.drawable.nucleus_icon_profile_circle_outline + "profile-circle.regular" -> R.drawable.nucleus_icon_profile_circle_regular + "profile-circle.solid" -> R.drawable.nucleus_icon_profile_circle_solid + "prohibition.outline" -> R.drawable.nucleus_icon_prohibition_outline + "prohibition.regular" -> R.drawable.nucleus_icon_prohibition_regular + "prohibition.solid" -> R.drawable.nucleus_icon_prohibition_solid + "qr-code.outline" -> R.drawable.nucleus_icon_qr_code_outline + "qr-code.regular" -> R.drawable.nucleus_icon_qr_code_regular + "qr-code.solid" -> R.drawable.nucleus_icon_qr_code_solid + "refresh.outline" -> R.drawable.nucleus_icon_refresh_outline + "refresh.regular" -> R.drawable.nucleus_icon_refresh_regular + "refresh.solid" -> R.drawable.nucleus_icon_refresh_solid + "reports.outline" -> R.drawable.nucleus_icon_reports_outline + "reports.regular" -> R.drawable.nucleus_icon_reports_regular + "reports.solid" -> R.drawable.nucleus_icon_reports_solid + "safe.outline" -> R.drawable.nucleus_icon_safe_outline + "safe.regular" -> R.drawable.nucleus_icon_safe_regular + "safe.solid" -> R.drawable.nucleus_icon_safe_solid + "scan.outline" -> R.drawable.nucleus_icon_scan_outline + "scan.regular" -> R.drawable.nucleus_icon_scan_regular + "scan.solid" -> R.drawable.nucleus_icon_scan_solid + "search.outline" -> R.drawable.nucleus_icon_search_outline + "search.regular" -> R.drawable.nucleus_icon_search_regular + "search.solid" -> R.drawable.nucleus_icon_search_solid + "send-mail.outline" -> R.drawable.nucleus_icon_send_mail_outline + "send-mail.regular" -> R.drawable.nucleus_icon_send_mail_regular + "send-mail.solid" -> R.drawable.nucleus_icon_send_mail_solid + "settings.outline" -> R.drawable.nucleus_icon_settings_outline + "settings.regular" -> R.drawable.nucleus_icon_settings_regular + "settings.solid" -> R.drawable.nucleus_icon_settings_solid + "share-ios.outline" -> R.drawable.nucleus_icon_share_ios_outline + "share-ios.regular" -> R.drawable.nucleus_icon_share_ios_regular + "share-ios.solid" -> R.drawable.nucleus_icon_share_ios_solid + "shield-2.outline" -> R.drawable.nucleus_icon_shield_2_outline + "shield-2.regular" -> R.drawable.nucleus_icon_shield_2_regular + "shield-2.solid" -> R.drawable.nucleus_icon_shield_2_solid + "shield-alert.outline" -> R.drawable.nucleus_icon_shield_alert_outline + "shield-alert.regular" -> R.drawable.nucleus_icon_shield_alert_regular + "shield-alert.solid" -> R.drawable.nucleus_icon_shield_alert_solid + "shield-check.outline" -> R.drawable.nucleus_icon_shield_check_outline + "shield-check.solid" -> R.drawable.nucleus_icon_shield_check_solid + "shield.outline" -> R.drawable.nucleus_icon_shield_outline + "shield.regular" -> R.drawable.nucleus_icon_shield_regular + "shield.solid" -> R.drawable.nucleus_icon_shield_solid + "smartphone-device.outline" -> R.drawable.nucleus_icon_smartphone_device_outline + "smartphone-device.regular" -> R.drawable.nucleus_icon_smartphone_device_regular + "smartphone-device.solid" -> R.drawable.nucleus_icon_smartphone_device_solid + "snow-flake.outline" -> R.drawable.nucleus_icon_snow_flake_outline + "snow-flake.regular" -> R.drawable.nucleus_icon_snow_flake_regular + "snow-flake.solid" -> R.drawable.nucleus_icon_snow_flake_solid + "software-update-setting.outline" -> R.drawable.nucleus_icon_software_update_setting_outline + "software-update-setting.regular" -> R.drawable.nucleus_icon_software_update_setting_regular + "software-update-setting.solid" -> R.drawable.nucleus_icon_software_update_setting_solid + "sort-down.outline" -> R.drawable.nucleus_icon_sort_down_outline + "sort-down.regular" -> R.drawable.nucleus_icon_sort_down_regular + "sort-down.solid" -> R.drawable.nucleus_icon_sort_down_solid + "sort-up.outline" -> R.drawable.nucleus_icon_sort_up_outline + "sort-up.regular" -> R.drawable.nucleus_icon_sort_up_regular + "sort-up.solid" -> R.drawable.nucleus_icon_sort_up_solid + "sort.outline" -> R.drawable.nucleus_icon_sort_outline + "sort.regular" -> R.drawable.nucleus_icon_sort_regular + "sort.solid" -> R.drawable.nucleus_icon_sort_solid + "spark.outline" -> R.drawable.nucleus_icon_spark_outline + "spark.regular" -> R.drawable.nucleus_icon_spark_regular + "spark.solid" -> R.drawable.nucleus_icon_spark_solid + "sparks.outline" -> R.drawable.nucleus_icon_sparks_outline + "sparks.regular" -> R.drawable.nucleus_icon_sparks_regular + "sparks.solid" -> R.drawable.nucleus_icon_sparks_solid + "star.outline" -> R.drawable.nucleus_icon_star_outline + "star.regular" -> R.drawable.nucleus_icon_star_regular + "star.solid" -> R.drawable.nucleus_icon_star_solid + "stats-up-square.outline" -> R.drawable.nucleus_icon_stats_up_square_outline + "stats-up-square.regular" -> R.drawable.nucleus_icon_stats_up_square_regular + "stats-up-square.solid" -> R.drawable.nucleus_icon_stats_up_square_solid + "suitcase.outline" -> R.drawable.nucleus_icon_suitcase_outline + "suitcase.regular" -> R.drawable.nucleus_icon_suitcase_regular + "suitcase.solid" -> R.drawable.nucleus_icon_suitcase_solid + "text.outline" -> R.drawable.nucleus_icon_text_outline + "text.solid" -> R.drawable.nucleus_icon_text_solid + "timer-dots.outline" -> R.drawable.nucleus_icon_timer_dots_outline + "timer-dots.regular" -> R.drawable.nucleus_icon_timer_dots_regular + "timer-dots.solid" -> R.drawable.nucleus_icon_timer_dots_solid + "trash.outline" -> R.drawable.nucleus_icon_trash_outline + "trash.regular" -> R.drawable.nucleus_icon_trash_regular + "trash.solid" -> R.drawable.nucleus_icon_trash_solid + "trophy.outline" -> R.drawable.nucleus_icon_trophy_outline + "trophy.regular" -> R.drawable.nucleus_icon_trophy_regular + "trophy.solid" -> R.drawable.nucleus_icon_trophy_solid + "user.outline" -> R.drawable.nucleus_icon_user_outline + "user.regular" -> R.drawable.nucleus_icon_user_regular + "user.solid" -> R.drawable.nucleus_icon_user_solid + "video-camera.outline" -> R.drawable.nucleus_icon_video_camera_outline + "video-camera.regular" -> R.drawable.nucleus_icon_video_camera_regular + "video-camera.solid" -> R.drawable.nucleus_icon_video_camera_solid + "view-grid.outline" -> R.drawable.nucleus_icon_view_grid_outline + "view-grid.regular" -> R.drawable.nucleus_icon_view_grid_regular + "view-grid.solid" -> R.drawable.nucleus_icon_view_grid_solid + "wallet.outline" -> R.drawable.nucleus_icon_wallet_outline + "wallet.regular" -> R.drawable.nucleus_icon_wallet_regular + "wallet.solid" -> R.drawable.nucleus_icon_wallet_solid + "warning-circle.outline" -> R.drawable.nucleus_icon_warning_circle_outline + "warning-circle.regular" -> R.drawable.nucleus_icon_warning_circle_regular + "warning-circle.solid" -> R.drawable.nucleus_icon_warning_circle_solid + "warning-hexagon.outline" -> R.drawable.nucleus_icon_warning_hexagon_outline + "warning-hexagon.regular" -> R.drawable.nucleus_icon_warning_hexagon_regular + "warning-hexagon.solid" -> R.drawable.nucleus_icon_warning_hexagon_solid + "warning-triangle-2.regular" -> R.drawable.nucleus_icon_warning_triangle_2_regular + "warning-triangle-2.solid" -> R.drawable.nucleus_icon_warning_triangle_2_solid + "warning-triangle.outline" -> R.drawable.nucleus_icon_warning_triangle_outline + "warning-triangle.regular" -> R.drawable.nucleus_icon_warning_triangle_regular + "warning-triangle.solid" -> R.drawable.nucleus_icon_warning_triangle_solid + "wifi-signal-none.outline" -> R.drawable.nucleus_icon_wifi_signal_none_outline + "wifi-signal-none.regular" -> R.drawable.nucleus_icon_wifi_signal_none_regular + "wifi-signal-none.solid" -> R.drawable.nucleus_icon_wifi_signal_none_solid + "wifi.outline" -> R.drawable.nucleus_icon_wifi_outline + "wifi.regular" -> R.drawable.nucleus_icon_wifi_regular + "wifi.solid" -> R.drawable.nucleus_icon_wifi_solid + "xmark.outline" -> R.drawable.nucleus_icon_xmark_outline + "xmark.regular" -> R.drawable.nucleus_icon_xmark_regular + "xmark.solid" -> R.drawable.nucleus_icon_xmark_solid + else -> null + } + + private fun colorLight(token: String): Color? = + when (token) { + "accent.content" -> NucleusSemanticColorsLight.accentContent + "accent.primary" -> NucleusSemanticColorsLight.accentPrimary + "action.destructive" -> NucleusSemanticColorsLight.actionDestructive + "action.destructiveContent" -> NucleusSemanticColorsLight.actionDestructiveContent + "action.disabled" -> NucleusSemanticColorsLight.actionDisabled + "action.disabledContent" -> NucleusSemanticColorsLight.actionDisabledContent + "action.ghost" -> NucleusSemanticColorsLight.actionGhost + "action.ghostContent" -> NucleusSemanticColorsLight.actionGhostContent + "action.primary" -> NucleusSemanticColorsLight.actionPrimary + "action.primaryContent" -> NucleusSemanticColorsLight.actionPrimaryContent + "action.secondary" -> NucleusSemanticColorsLight.actionSecondary + "action.secondaryContent" -> NucleusSemanticColorsLight.actionSecondaryContent + "action.tertiary" -> NucleusSemanticColorsLight.actionTertiary + "action.tertiaryContent" -> NucleusSemanticColorsLight.actionTertiaryContent + "black" -> NucleusPrimitiveColors.black + "border.default" -> NucleusSemanticColorsLight.borderDefault + "border.divider" -> NucleusSemanticColorsLight.borderDivider + "border.focus" -> NucleusSemanticColorsLight.borderFocus + "border.strong" -> NucleusSemanticColorsLight.borderStrong + "border.subtle" -> NucleusSemanticColorsLight.borderSubtle + "border.translucent" -> NucleusSemanticColorsLight.borderTranslucent + "error.100" -> NucleusPrimitiveColors.error100 + "error.200" -> NucleusPrimitiveColors.error200 + "error.300" -> NucleusPrimitiveColors.error300 + "error.400" -> NucleusPrimitiveColors.error400 + "error.500" -> NucleusPrimitiveColors.error500 + "error.600" -> NucleusPrimitiveColors.error600 + "error.700" -> NucleusPrimitiveColors.error700 + "error.800" -> NucleusPrimitiveColors.error800 + "error.900" -> NucleusPrimitiveColors.error900 + "error.950" -> NucleusPrimitiveColors.error950 + "grey.100" -> NucleusPrimitiveColors.grey100 + "grey.200" -> NucleusPrimitiveColors.grey200 + "grey.300" -> NucleusPrimitiveColors.grey300 + "grey.400" -> NucleusPrimitiveColors.grey400 + "grey.500" -> NucleusPrimitiveColors.grey500 + "grey.600" -> NucleusPrimitiveColors.grey600 + "grey.700" -> NucleusPrimitiveColors.grey700 + "grey.800" -> NucleusPrimitiveColors.grey800 + "grey.900" -> NucleusPrimitiveColors.grey900 + "grey.950" -> NucleusPrimitiveColors.grey950 + "icon.disabled" -> NucleusSemanticColorsLight.iconDisabled + "icon.inverse" -> NucleusSemanticColorsLight.iconInverse + "icon.primary" -> NucleusSemanticColorsLight.iconPrimary + "icon.secondary" -> NucleusSemanticColorsLight.iconSecondary + "icon.tertiary" -> NucleusSemanticColorsLight.iconTertiary + "info.100" -> NucleusPrimitiveColors.info100 + "info.200" -> NucleusPrimitiveColors.info200 + "info.300" -> NucleusPrimitiveColors.info300 + "info.400" -> NucleusPrimitiveColors.info400 + "info.500" -> NucleusPrimitiveColors.info500 + "info.600" -> NucleusPrimitiveColors.info600 + "info.700" -> NucleusPrimitiveColors.info700 + "info.800" -> NucleusPrimitiveColors.info800 + "info.900" -> NucleusPrimitiveColors.info900 + "info.950" -> NucleusPrimitiveColors.info950 + "input.background" -> NucleusSemanticColorsLight.inputBackground + "input.backgroundFocus" -> NucleusSemanticColorsLight.inputBackgroundFocus + "input.divider" -> NucleusSemanticColorsLight.inputDivider + "input.error" -> NucleusSemanticColorsLight.inputError + "input.placeholder" -> NucleusSemanticColorsLight.inputPlaceholder + "input.text" -> NucleusSemanticColorsLight.inputText + "status.error" -> NucleusSemanticColorsLight.statusError + "status.errorBackground" -> NucleusSemanticColorsLight.statusErrorBackground + "status.info" -> NucleusSemanticColorsLight.statusInfo + "status.infoBackground" -> NucleusSemanticColorsLight.statusInfoBackground + "status.success" -> NucleusSemanticColorsLight.statusSuccess + "status.successBackground" -> NucleusSemanticColorsLight.statusSuccessBackground + "status.warning" -> NucleusSemanticColorsLight.statusWarning + "status.warningBackground" -> NucleusSemanticColorsLight.statusWarningBackground + "success.100" -> NucleusPrimitiveColors.success100 + "success.200" -> NucleusPrimitiveColors.success200 + "success.300" -> NucleusPrimitiveColors.success300 + "success.400" -> NucleusPrimitiveColors.success400 + "success.500" -> NucleusPrimitiveColors.success500 + "success.600" -> NucleusPrimitiveColors.success600 + "success.700" -> NucleusPrimitiveColors.success700 + "success.800" -> NucleusPrimitiveColors.success800 + "success.900" -> NucleusPrimitiveColors.success900 + "success.950" -> NucleusPrimitiveColors.success950 + "surface.elevated" -> NucleusSemanticColorsLight.surfaceElevated + "surface.overlay" -> NucleusSemanticColorsLight.surfaceOverlay + "surface.primary" -> NucleusSemanticColorsLight.surfacePrimary + "surface.secondary" -> NucleusSemanticColorsLight.surfaceSecondary + "surface.tertiary" -> NucleusSemanticColorsLight.surfaceTertiary + "text.disabled" -> NucleusSemanticColorsLight.textDisabled + "text.inverse" -> NucleusSemanticColorsLight.textInverse + "text.primary" -> NucleusSemanticColorsLight.textPrimary + "text.secondary" -> NucleusSemanticColorsLight.textSecondary + "text.tertiary" -> NucleusSemanticColorsLight.textTertiary + "warning.100" -> NucleusPrimitiveColors.warning100 + "warning.200" -> NucleusPrimitiveColors.warning200 + "warning.300" -> NucleusPrimitiveColors.warning300 + "warning.400" -> NucleusPrimitiveColors.warning400 + "warning.500" -> NucleusPrimitiveColors.warning500 + "warning.600" -> NucleusPrimitiveColors.warning600 + "warning.700" -> NucleusPrimitiveColors.warning700 + "warning.800" -> NucleusPrimitiveColors.warning800 + "warning.900" -> NucleusPrimitiveColors.warning900 + "warning.950" -> NucleusPrimitiveColors.warning950 + "white" -> NucleusPrimitiveColors.white + else -> null + } + + private fun colorDark(token: String): Color? = + when (token) { + "accent.content" -> NucleusSemanticColorsDark.accentContent + "accent.primary" -> NucleusSemanticColorsDark.accentPrimary + "action.destructive" -> NucleusSemanticColorsDark.actionDestructive + "action.destructiveContent" -> NucleusSemanticColorsDark.actionDestructiveContent + "action.disabled" -> NucleusSemanticColorsDark.actionDisabled + "action.disabledContent" -> NucleusSemanticColorsDark.actionDisabledContent + "action.ghost" -> NucleusSemanticColorsDark.actionGhost + "action.ghostContent" -> NucleusSemanticColorsDark.actionGhostContent + "action.primary" -> NucleusSemanticColorsDark.actionPrimary + "action.primaryContent" -> NucleusSemanticColorsDark.actionPrimaryContent + "action.secondary" -> NucleusSemanticColorsDark.actionSecondary + "action.secondaryContent" -> NucleusSemanticColorsDark.actionSecondaryContent + "action.tertiary" -> NucleusSemanticColorsDark.actionTertiary + "action.tertiaryContent" -> NucleusSemanticColorsDark.actionTertiaryContent + "black" -> NucleusPrimitiveColors.black + "border.default" -> NucleusSemanticColorsDark.borderDefault + "border.divider" -> NucleusSemanticColorsDark.borderDivider + "border.focus" -> NucleusSemanticColorsDark.borderFocus + "border.strong" -> NucleusSemanticColorsDark.borderStrong + "border.subtle" -> NucleusSemanticColorsDark.borderSubtle + "border.translucent" -> NucleusSemanticColorsDark.borderTranslucent + "error.100" -> NucleusPrimitiveColors.error100 + "error.200" -> NucleusPrimitiveColors.error200 + "error.300" -> NucleusPrimitiveColors.error300 + "error.400" -> NucleusPrimitiveColors.error400 + "error.500" -> NucleusPrimitiveColors.error500 + "error.600" -> NucleusPrimitiveColors.error600 + "error.700" -> NucleusPrimitiveColors.error700 + "error.800" -> NucleusPrimitiveColors.error800 + "error.900" -> NucleusPrimitiveColors.error900 + "error.950" -> NucleusPrimitiveColors.error950 + "grey.100" -> NucleusPrimitiveColors.grey100 + "grey.200" -> NucleusPrimitiveColors.grey200 + "grey.300" -> NucleusPrimitiveColors.grey300 + "grey.400" -> NucleusPrimitiveColors.grey400 + "grey.500" -> NucleusPrimitiveColors.grey500 + "grey.600" -> NucleusPrimitiveColors.grey600 + "grey.700" -> NucleusPrimitiveColors.grey700 + "grey.800" -> NucleusPrimitiveColors.grey800 + "grey.900" -> NucleusPrimitiveColors.grey900 + "grey.950" -> NucleusPrimitiveColors.grey950 + "icon.disabled" -> NucleusSemanticColorsDark.iconDisabled + "icon.inverse" -> NucleusSemanticColorsDark.iconInverse + "icon.primary" -> NucleusSemanticColorsDark.iconPrimary + "icon.secondary" -> NucleusSemanticColorsDark.iconSecondary + "icon.tertiary" -> NucleusSemanticColorsDark.iconTertiary + "info.100" -> NucleusPrimitiveColors.info100 + "info.200" -> NucleusPrimitiveColors.info200 + "info.300" -> NucleusPrimitiveColors.info300 + "info.400" -> NucleusPrimitiveColors.info400 + "info.500" -> NucleusPrimitiveColors.info500 + "info.600" -> NucleusPrimitiveColors.info600 + "info.700" -> NucleusPrimitiveColors.info700 + "info.800" -> NucleusPrimitiveColors.info800 + "info.900" -> NucleusPrimitiveColors.info900 + "info.950" -> NucleusPrimitiveColors.info950 + "input.background" -> NucleusSemanticColorsDark.inputBackground + "input.backgroundFocus" -> NucleusSemanticColorsDark.inputBackgroundFocus + "input.divider" -> NucleusSemanticColorsDark.inputDivider + "input.error" -> NucleusSemanticColorsDark.inputError + "input.placeholder" -> NucleusSemanticColorsDark.inputPlaceholder + "input.text" -> NucleusSemanticColorsDark.inputText + "status.error" -> NucleusSemanticColorsDark.statusError + "status.errorBackground" -> NucleusSemanticColorsDark.statusErrorBackground + "status.info" -> NucleusSemanticColorsDark.statusInfo + "status.infoBackground" -> NucleusSemanticColorsDark.statusInfoBackground + "status.success" -> NucleusSemanticColorsDark.statusSuccess + "status.successBackground" -> NucleusSemanticColorsDark.statusSuccessBackground + "status.warning" -> NucleusSemanticColorsDark.statusWarning + "status.warningBackground" -> NucleusSemanticColorsDark.statusWarningBackground + "success.100" -> NucleusPrimitiveColors.success100 + "success.200" -> NucleusPrimitiveColors.success200 + "success.300" -> NucleusPrimitiveColors.success300 + "success.400" -> NucleusPrimitiveColors.success400 + "success.500" -> NucleusPrimitiveColors.success500 + "success.600" -> NucleusPrimitiveColors.success600 + "success.700" -> NucleusPrimitiveColors.success700 + "success.800" -> NucleusPrimitiveColors.success800 + "success.900" -> NucleusPrimitiveColors.success900 + "success.950" -> NucleusPrimitiveColors.success950 + "surface.elevated" -> NucleusSemanticColorsDark.surfaceElevated + "surface.overlay" -> NucleusSemanticColorsDark.surfaceOverlay + "surface.primary" -> NucleusSemanticColorsDark.surfacePrimary + "surface.secondary" -> NucleusSemanticColorsDark.surfaceSecondary + "surface.tertiary" -> NucleusSemanticColorsDark.surfaceTertiary + "text.disabled" -> NucleusSemanticColorsDark.textDisabled + "text.inverse" -> NucleusSemanticColorsDark.textInverse + "text.primary" -> NucleusSemanticColorsDark.textPrimary + "text.secondary" -> NucleusSemanticColorsDark.textSecondary + "text.tertiary" -> NucleusSemanticColorsDark.textTertiary + "warning.100" -> NucleusPrimitiveColors.warning100 + "warning.200" -> NucleusPrimitiveColors.warning200 + "warning.300" -> NucleusPrimitiveColors.warning300 + "warning.400" -> NucleusPrimitiveColors.warning400 + "warning.500" -> NucleusPrimitiveColors.warning500 + "warning.600" -> NucleusPrimitiveColors.warning600 + "warning.700" -> NucleusPrimitiveColors.warning700 + "warning.800" -> NucleusPrimitiveColors.warning800 + "warning.900" -> NucleusPrimitiveColors.warning900 + "warning.950" -> NucleusPrimitiveColors.warning950 + "white" -> NucleusPrimitiveColors.white + else -> null + } +} diff --git a/ios/Sources/NucleusButtons/NucleusButton+TokenResolvable.swift b/ios/Sources/NucleusButtons/NucleusButton+TokenResolvable.swift new file mode 100644 index 0000000..9dd2a57 --- /dev/null +++ b/ios/Sources/NucleusButtons/NucleusButton+TokenResolvable.swift @@ -0,0 +1,35 @@ +// AUTOGENERATED by tokens/build — do not edit by hand. + +import NucleusTokens + +private let buttonTokens: [String: NucleusButton] = [ + "disabled.32": .disabled32, + "disabled.40": .disabled40, + "disabled.48": .disabled48, + "ghost.32": .ghost32, + "ghost.40": .ghost40, + "ghost.48": .ghost48, + "inverse.32": .inverse32, + "inverse.40": .inverse40, + "inverse.48": .inverse48, + "primary.32": .primary32, + "primary.40": .primary40, + "primary.48": .primary48, + "secondary.32": .secondary32, + "secondary.40": .secondary40, + "secondary.48": .secondary48, + "tertiary.32": .tertiary32, + "tertiary.40": .tertiary40, + "tertiary.48": .tertiary48, +] + +extension NucleusButton: TokenResolvable { + /// Resolves a type-scoped button style token, e.g. `inverse.32`. + public static func resolve(token: String) -> NucleusButton? { + guard let value = buttonTokens[token] else { + assertionFailure("unknown NucleusButton token: \(token)") + return nil + } + return value + } +} diff --git a/ios/Sources/NucleusColors/NucleusColor+TokenResolvable.swift b/ios/Sources/NucleusColors/NucleusColor+TokenResolvable.swift new file mode 100644 index 0000000..38fe193 --- /dev/null +++ b/ios/Sources/NucleusColors/NucleusColor+TokenResolvable.swift @@ -0,0 +1,118 @@ +// AUTOGENERATED by tokens/build — do not edit by hand. + +import NucleusTokens + +private let colorTokens: [String: NucleusColor] = [ + "accent.content": .accentContent, + "accent.primary": .accentPrimary, + "action.destructive": .actionDestructive, + "action.destructiveContent": .actionDestructiveContent, + "action.disabled": .actionDisabled, + "action.disabledContent": .actionDisabledContent, + "action.ghost": .actionGhost, + "action.ghostContent": .actionGhostContent, + "action.primary": .actionPrimary, + "action.primaryContent": .actionPrimaryContent, + "action.secondary": .actionSecondary, + "action.secondaryContent": .actionSecondaryContent, + "action.tertiary": .actionTertiary, + "action.tertiaryContent": .actionTertiaryContent, + "black": .black, + "border.default": .borderDefault, + "border.divider": .borderDivider, + "border.focus": .borderFocus, + "border.strong": .borderStrong, + "border.subtle": .borderSubtle, + "border.translucent": .borderTranslucent, + "error.100": .error100, + "error.200": .error200, + "error.300": .error300, + "error.400": .error400, + "error.500": .error500, + "error.600": .error600, + "error.700": .error700, + "error.800": .error800, + "error.900": .error900, + "error.950": .error950, + "grey.100": .grey100, + "grey.200": .grey200, + "grey.300": .grey300, + "grey.400": .grey400, + "grey.500": .grey500, + "grey.600": .grey600, + "grey.700": .grey700, + "grey.800": .grey800, + "grey.900": .grey900, + "grey.950": .grey950, + "icon.disabled": .iconDisabled, + "icon.inverse": .iconInverse, + "icon.primary": .iconPrimary, + "icon.secondary": .iconSecondary, + "icon.tertiary": .iconTertiary, + "info.100": .info100, + "info.200": .info200, + "info.300": .info300, + "info.400": .info400, + "info.500": .info500, + "info.600": .info600, + "info.700": .info700, + "info.800": .info800, + "info.900": .info900, + "info.950": .info950, + "input.background": .inputBackground, + "input.backgroundFocus": .inputBackgroundFocus, + "input.divider": .inputDivider, + "input.error": .inputError, + "input.placeholder": .inputPlaceholder, + "input.text": .inputText, + "status.error": .statusError, + "status.errorBackground": .statusErrorBackground, + "status.info": .statusInfo, + "status.infoBackground": .statusInfoBackground, + "status.success": .statusSuccess, + "status.successBackground": .statusSuccessBackground, + "status.warning": .statusWarning, + "status.warningBackground": .statusWarningBackground, + "success.100": .success100, + "success.200": .success200, + "success.300": .success300, + "success.400": .success400, + "success.500": .success500, + "success.600": .success600, + "success.700": .success700, + "success.800": .success800, + "success.900": .success900, + "success.950": .success950, + "surface.elevated": .surfaceElevated, + "surface.overlay": .surfaceOverlay, + "surface.primary": .surfacePrimary, + "surface.secondary": .surfaceSecondary, + "surface.tertiary": .surfaceTertiary, + "text.disabled": .textDisabled, + "text.inverse": .textInverse, + "text.primary": .textPrimary, + "text.secondary": .textSecondary, + "text.tertiary": .textTertiary, + "warning.100": .warning100, + "warning.200": .warning200, + "warning.300": .warning300, + "warning.400": .warning400, + "warning.500": .warning500, + "warning.600": .warning600, + "warning.700": .warning700, + "warning.800": .warning800, + "warning.900": .warning900, + "warning.950": .warning950, + "white": .white, +] + +extension NucleusColor: TokenResolvable { + /// Resolves a type-scoped color token, e.g. `text.primary` or `grey.900`. + public static func resolve(token: String) -> NucleusColor? { + guard let value = colorTokens[token] else { + assertionFailure("unknown NucleusColor token: \(token)") + return nil + } + return value + } +} diff --git a/ios/Sources/NucleusFonts/NucleusFont+TokenResolvable.swift b/ios/Sources/NucleusFonts/NucleusFont+TokenResolvable.swift new file mode 100644 index 0000000..0498e38 --- /dev/null +++ b/ios/Sources/NucleusFonts/NucleusFont+TokenResolvable.swift @@ -0,0 +1,38 @@ +// AUTOGENERATED by tokens/build — do not edit by hand. + +import NucleusTokens + +private let fontTokens: [String: NucleusFont] = [ + "b1": .b1, + "b2": .b2, + "b3": .b3, + "b4": .b4, + "d1": .d1, + "h1": .h1, + "h2": .h2, + "h3": .h3, + "h4": .h4, + "l1": .l1, + "l2": .l2, + "l3": .l3, + "n1": .n1, + "n2": .n2, + "n3": .n3, + "n4": .n4, + "n5": .n5, + "s1": .s1, + "s2": .s2, + "s3": .s3, + "s4": .s4, +] + +extension NucleusFont: TokenResolvable { + /// Resolves a type-scoped typography token (bare id), e.g. `s3`. + public static func resolve(token: String) -> NucleusFont? { + guard let value = fontTokens[token] else { + assertionFailure("unknown NucleusFont token: \(token)") + return nil + } + return value + } +} diff --git a/ios/Sources/NucleusIcons/NucleusIcon+TokenResolvable.swift b/ios/Sources/NucleusIcons/NucleusIcon+TokenResolvable.swift new file mode 100644 index 0000000..bc377cb --- /dev/null +++ b/ios/Sources/NucleusIcons/NucleusIcon+TokenResolvable.swift @@ -0,0 +1,173 @@ +// AUTOGENERATED by tokens/build — do not edit by hand. + +import NucleusTokens + +private let iconTokens: [String: NucleusIcon] = [ + "airplane": .airplane, + "antenna-signal": .antennaSignal, + "apple-mac": .appleMac, + "arrow-down": .arrowDown, + "arrow-down-left": .arrowDownLeft, + "arrow-down-right": .arrowDownRight, + "arrow-left": .arrowLeft, + "arrow-right": .arrowRight, + "arrow-split": .arrowSplit, + "arrow-up": .arrowUp, + "arrow-up-left": .arrowUpLeft, + "arrow-up-right": .arrowUpRight, + "at-sign": .atSign, + "badge-check": .badgeCheck, + "badge-not-checked": .badgeNotChecked, + "bag": .bag, + "bank": .bank, + "bell": .bell, + "bell-notification": .bellNotification, + "bell-slash": .bellSlash, + "bookmark": .bookmark, + "box-iso": .boxIso, + "calendar": .calendar, + "calendar-plus": .calendarPlus, + "camera": .camera, + "cash": .cash, + "cash-multi": .cashMulti, + "cellular": .cellular, + "cellular-no-signal": .cellularNoSignal, + "chat-bubble": .chatBubble, + "chat-bubble-empty": .chatBubbleEmpty, + "chat-bubble-question": .chatBubbleQuestion, + "chat-bubble-translate": .chatBubbleTranslate, + "chat-bubble-warning": .chatBubbleWarning, + "chat-lines": .chatLines, + "check": .check, + "check-circle": .checkCircle, + "clock": .clock, + "clock-rotate-right": .clockRotateRight, + "cloud": .cloud, + "cloud-download": .cloudDownload, + "coins": .coins, + "compass": .compass, + "copy": .copy, + "coupon": .coupon, + "cube": .cube, + "delivery-check": .deliveryCheck, + "delivery-truck": .deliveryTruck, + "double-check": .doubleCheck, + "download": .download, + "edit-pencil": .editPencil, + "empty-page": .emptyPage, + "eye": .eye, + "eye-closed": .eyeClosed, + "face-id": .faceId, + "filter-list": .filterList, + "flash": .flash, + "gif": .gif, + "gift": .gift, + "globe": .globe, + "graduation-cap": .graduationCap, + "graph-down": .graphDown, + "graph-up": .graphUp, + "group": .group, + "heart": .heart, + "help-circle": .helpCircle, + "home": .home, + "info-circle": .infoCircle, + "instagram": .instagram, + "key": .key, + "language": .language, + "link": .link, + "link-slash": .linkSlash, + "list": .list, + "lock": .lock, + "log-in": .logIn, + "log-out": .logOut, + "magic-wand": .magicWand, + "mail": .mail, + "map": .map, + "map-pin": .mapPin, + "maps-arrow": .mapsArrow, + "media-image": .mediaImage, + "microphone": .microphone, + "minus": .minus, + "more-horiz": .moreHoriz, + "more-horiz-circle": .moreHorizCircle, + "nav-arrow-down": .navArrowDown, + "nav-arrow-left": .navArrowLeft, + "nav-arrow-right": .navArrowRight, + "nav-arrow-up": .navArrowUp, + "open-new-window": .openNewWindow, + "orb": .orb, + "page": .page, + "passkey": .passkey, + "percentage": .percentage, + "pin": .pin, + "play": .play, + "plus": .plus, + "post": .post, + "profile-circle": .profileCircle, + "prohibition": .prohibition, + "qr-code": .qrCode, + "refresh": .refresh, + "reports": .reports, + "safe": .safe, + "scan": .scan, + "search": .search, + "send-mail": .sendMail, + "settings": .settings, + "share-ios": .shareIos, + "shield": .shield, + "shield-2": .shield2, + "shield-alert": .shieldAlert, + "shield-check": .shieldCheck, + "smartphone-device": .smartphoneDevice, + "snow-flake": .snowFlake, + "software-update-setting": .softwareUpdateSetting, + "sort": .sort, + "sort-down": .sortDown, + "sort-up": .sortUp, + "spark": .spark, + "sparks": .sparks, + "star": .star, + "stats-up-square": .statsUpSquare, + "suitcase": .suitcase, + "text": .text, + "timer-dots": .timerDots, + "trash": .trash, + "trophy": .trophy, + "user": .user, + "video-camera": .videoCamera, + "view-grid": .viewGrid, + "wallet": .wallet, + "warning-circle": .warningCircle, + "warning-hexagon": .warningHexagon, + "warning-triangle": .warningTriangle, + "warning-triangle-2": .warningTriangle2, + "wifi": .wifi, + "wifi-signal-none": .wifiSignalNone, + "xmark": .xmark, +] + +private let variantTokens: [String: NucleusIcon.Variant] = [ + "outline": .outline, + "regular": .regular, + "solid": .solid, +] + +extension NucleusIcon: TokenResolvable { + /// Resolves a type-scoped icon token `name.variant`, e.g. `arrow-right.regular`. + public static func resolve(token: String) -> (icon: NucleusIcon, variant: NucleusIcon.Variant)? { + let components = token.components(separatedBy: ".") + guard components.count == 2 else { + assertionFailure("NucleusIcon token must be `name.variant`: \(token)") + return nil + } + guard + let icon = iconTokens[components[0]], + let variant = variantTokens[components[1]], + icon.availableVariants.contains(variant) + else { + assertionFailure("unknown NucleusIcon token: \(token)") + return nil + } + return (icon, variant) + } +} diff --git a/ios/Sources/NucleusTokens/TokenResolvable.swift b/ios/Sources/NucleusTokens/TokenResolvable.swift new file mode 100644 index 0000000..67f065b --- /dev/null +++ b/ios/Sources/NucleusTokens/TokenResolvable.swift @@ -0,0 +1,14 @@ +/// A Nucleus token type that can be resolved from its type-scoped wire token string. +/// +/// The SDUI backend emits type-scoped tokens (the namespace the resolver type already implies is +/// stripped): a typography token is `s3`, a color is `text.primary`, a button is `inverse.32`, and +/// an icon is `name.variant`. Each Nucleus primitive conforms in its own module (via a generated +/// `+TokenResolvable.swift`), so a consumer only links the modules it actually imports. +/// +/// `resolve(token:)` returns `nil` for an unknown token; conformances also trip `assertionFailure` +/// in debug builds so a stale or mistyped token surfaces during development rather than silently +/// falling through. +public protocol TokenResolvable { + associatedtype ResolvedType + static func resolve(token: String) -> ResolvedType? +} diff --git a/tokens/build/index.ts b/tokens/build/index.ts index f230a5f..5626bef 100644 --- a/tokens/build/index.ts +++ b/tokens/build/index.ts @@ -2,6 +2,7 @@ import { buildButtons } from './buttons.js'; import { buildColors } from './colors.js'; import { buildFonts } from './fonts.js'; import { buildIcons } from './icons.js'; +import { buildResolver } from './resolver.js'; import { buildWebTypes } from './types.js'; import { copyWebPackageTemplates } from './web-package.js'; @@ -13,6 +14,7 @@ function main(): void { buildIcons(); buildButtons(); buildWebTypes(); + buildResolver(); console.log('\n✓ Tokens built'); copyWebPackageTemplates(); diff --git a/tokens/build/resolver.ts b/tokens/build/resolver.ts new file mode 100644 index 0000000..c185a54 --- /dev/null +++ b/tokens/build/resolver.ts @@ -0,0 +1,72 @@ +import { mkdirSync, writeFileSync } from 'fs'; +import { resolve } from 'path'; + +import { generateAndroidResolver } from '../formats/resolver-android.js'; +import { + generateIOSButtonResolvable, + generateIOSColorResolvable, + generateIOSFontResolvable, + generateIOSIconResolvable, +} from '../formats/resolver-ios.js'; +import { buildTokenCatalog } from '../formats/resolver-shared.js'; +import { + generateWebTokenPathsDts, + generateWebTokenPathsJs, +} from '../formats/resolver-web.js'; +import { + ANDROID_TOKENS_OUT, + IOS_BUTTONS_OUT, + IOS_COLORS_OUT, + IOS_FONTS_OUT, + IOS_ICONS_OUT, + ROOT, + WEB_OUT, + logStage, +} from './shared.js'; + +function writeOut(relPath: string, content: string): void { + const absolute = resolve(ROOT, relPath); + mkdirSync(resolve(absolute, '..'), { recursive: true }); + writeFileSync(absolute, content); +} + +/** + * Generates the cross-platform token resolver from the same definitions that drive the static + * accessors: per-type iOS `TokenResolvable` conformances (each in its own target — no module that + * imports everything), the single-module Android `NucleusTokenResolver`, and the web `token-paths` + * constants the app-backend SDUI library imports instead of hand-maintaining token literals. + */ +export function buildResolver(): void { + const catalog = buildTokenCatalog(); + + const iosColor = `${IOS_COLORS_OUT}/NucleusColor+TokenResolvable.swift`; + writeOut(iosColor, generateIOSColorResolvable(catalog)); + + const iosFont = `${IOS_FONTS_OUT}/NucleusFont+TokenResolvable.swift`; + writeOut(iosFont, generateIOSFontResolvable(catalog)); + + const iosButton = `${IOS_BUTTONS_OUT}/NucleusButton+TokenResolvable.swift`; + writeOut(iosButton, generateIOSButtonResolvable(catalog)); + + const iosIcon = `${IOS_ICONS_OUT}/NucleusIcon+TokenResolvable.swift`; + writeOut(iosIcon, generateIOSIconResolvable(catalog)); + + const androidOut = `${ANDROID_TOKENS_OUT}/NucleusTokenResolver.kt`; + writeOut(androidOut, generateAndroidResolver(catalog)); + + const webJsOut = `${WEB_OUT}/token-paths.js`; + writeOut(webJsOut, generateWebTokenPathsJs(catalog)); + + const webDtsOut = `${WEB_OUT}/token-paths.d.ts`; + writeOut(webDtsOut, generateWebTokenPathsDts(catalog)); + + logStage('token resolver (type-scoped tokens → tokens)', [ + ['ios', iosColor], + ['ios', iosFont], + ['ios', iosButton], + ['ios', iosIcon], + ['android', androidOut], + ['web', webJsOut], + ['web', webDtsOut], + ]); +} diff --git a/tokens/build/types.ts b/tokens/build/types.ts index eb90c4f..2b4b72d 100644 --- a/tokens/build/types.ts +++ b/tokens/build/types.ts @@ -1,20 +1,10 @@ import { mkdirSync, writeFileSync } from 'fs'; import { resolve } from 'path'; -import { - loadButtonDefinition, - resolveButtonStyles, -} from '../formats/buttons.js'; -import { discoverIconTokens } from '../formats/icons-shared.js'; -import { loadColorTokens, loadFontDefinitions } from '../formats/loaders.js'; +import { buildTokenCatalog } from '../formats/resolver-shared.js'; import { generateWebTypes } from '../formats/types-web.js'; -import { BUTTON_SOURCE } from './buttons.js'; import { ROOT, WEB_OUT, logStage } from './shared.js'; -const PRIMITIVE_SOURCE = 'tokens/definitions/color/primitive.json'; -const SEMANTIC_LIGHT_SOURCE = 'tokens/definitions/color/semantic.light.json'; -const FONT_SOURCE = 'tokens/definitions/font/fonts.json'; - function writeOut(relPath: string, content: string): void { const absolute = resolve(ROOT, relPath); mkdirSync(resolve(absolute, '..'), { recursive: true }); @@ -22,27 +12,25 @@ function writeOut(relPath: string, content: string): void { } /** - * Generate the web package's `index.d.ts` from the token definitions. Light and dark - * semantic palettes share the same key paths, so the light source is sufficient for - * the color path set. + * Generate the web package's `index.d.ts` literal-union types. The members are sourced from the + * shared token catalog (`buildTokenCatalog`) — the exact same `wireToken` values that drive the + * `token-paths` constants and the native resolvers — so the union types stay in lockstep with the + * runtime constants and the resolver keys. */ export function buildWebTypes(): void { - const colorTokens = [ - ...loadColorTokens(PRIMITIVE_SOURCE), - ...loadColorTokens(SEMANTIC_LIGHT_SOURCE), - ]; - const { tokens: fontTokens } = loadFontDefinitions(FONT_SOURCE); - const buttonStyleTokens = resolveButtonStyles( - loadButtonDefinition(BUTTON_SOURCE), - ).map((style) => style.token); - const iconTokens = discoverIconTokens().flatMap((icon) => - icon.variants.map((variant) => `icon.${icon.name}.${variant}`), - ); + const catalog = buildTokenCatalog(); const out = `${WEB_OUT}/index.d.ts`; writeOut( out, - generateWebTypes({ colorTokens, fontTokens, buttonStyleTokens, iconTokens }), + generateWebTypes({ + colorTokens: [...catalog.semanticColors, ...catalog.primitiveColors].map( + (c) => c.wireToken, + ), + typographyTokens: catalog.fonts.map((f) => f.wireToken), + buttonStyleTokens: catalog.buttons.map((b) => b.wireToken), + iconTokens: catalog.icons.map((i) => i.wireToken), + }), ); logStage('token types (web)', [['web', out]]); diff --git a/tokens/formats/resolver-android.ts b/tokens/formats/resolver-android.ts new file mode 100644 index 0000000..7ba4953 --- /dev/null +++ b/tokens/formats/resolver-android.ts @@ -0,0 +1,124 @@ +import { ANDROID_DRAWABLE_PREFIX } from './icons-android.js'; +import type { TokenCatalog } from './resolver-shared.js'; + +/** + * Android token resolver generator. + * + * Emits `NucleusTokenResolver.kt` — a single object (Android ships one artifact, so there's no + * cross-module coupling to avoid) with `when`-based lookups mapping each type-scoped wire token to + * its compile-time Nucleus accessor. Colors are theme-aware (`isDark`); icons resolve straight to a + * `@DrawableRes` id (Android has no runtime icon `Variant` enum — variant availability is encoded in + * the type system). Tokens are type-scoped: font `s3`, color `text.primary`, button `inverse.32`, + * icon `name.variant`. Forward-only; returns null for unknown tokens so callers fall back + report. + */ + +const PACKAGE_NAME = 'com.worldcoin.nucleus.tokens'; + +interface Branch { + token: string; + value: string; +} + +function sortBranches(branches: Branch[]): Branch[] { + return [...branches].sort((a, b) => a.token.localeCompare(b.token)); +} + +function whenBody(branches: Branch[], indent: string): string { + return sortBranches(branches) + .map((branch) => `${indent}"${branch.token}" -> ${branch.value}`) + .join('\n'); +} + +export function generateAndroidResolver(catalog: TokenCatalog): string { + // Colors are split into single-param colorLight/colorDark `when`s (kept short and ktlint-clean) + // behind a one-line `color(token, isDark)` dispatcher; primitives resolve the same in both. + const colorLightBranches: Branch[] = [ + ...catalog.semanticColors.map((c) => ({ + token: c.wireToken, + value: `NucleusSemanticColorsLight.${c.accessor}`, + })), + ...catalog.primitiveColors.map((c) => ({ + token: c.wireToken, + value: `NucleusPrimitiveColors.${c.accessor}`, + })), + ]; + const colorDarkBranches: Branch[] = [ + ...catalog.semanticColors.map((c) => ({ + token: c.wireToken, + value: `NucleusSemanticColorsDark.${c.accessor}`, + })), + ...catalog.primitiveColors.map((c) => ({ + token: c.wireToken, + value: `NucleusPrimitiveColors.${c.accessor}`, + })), + ]; + const fontBranches: Branch[] = catalog.fonts.map((f) => ({ + token: f.wireToken, + value: `NucleusFonts.${f.wireToken}`, + })); + const buttonBranches: Branch[] = catalog.buttons.map((b) => ({ + token: b.wireToken, + value: `NucleusButtons.${b.accessor}`, + })); + const iconBranches: Branch[] = catalog.icons.map((i) => ({ + token: i.wireToken, + value: `R.drawable.${ANDROID_DRAWABLE_PREFIX}_${i.androidStem}_${i.variant}`, + })); + + return `// AUTOGENERATED by tokens/build — do not edit by hand. + +package ${PACKAGE_NAME} + +import androidx.annotation.DrawableRes +import androidx.compose.ui.graphics.Color +import com.worldcoin.nucleus.R + +/** + * Resolves a canonical Nucleus token-path string — as emitted by the backend SDUI wire contract — + * to the corresponding compile-time Nucleus token. Forward-only; returns null for an unknown path + * so callers can fall back to a default and report the miss (server and client ship on different + * cadences, so an unknown token is expected and must not crash). + */ +object NucleusTokenResolver { + /** Resolves a color token path, e.g. \`semantic.color.text.primary\`, for the active theme. */ + fun color( + token: String, + isDark: Boolean, + ): Color? = if (isDark) colorDark(token) else colorLight(token) + + /** Resolves a typography token path, e.g. \`typography.subtitle.s3\`. */ + fun font(token: String): NucleusFontStyle? = + when (token) { +${whenBody(fontBranches, ' ')} + else -> null + } + + /** Resolves a button style token path, e.g. \`component.button.inverse.32\`. */ + fun button(token: String): NucleusButtonStyle? = + when (token) { +${whenBody(buttonBranches, ' ')} + else -> null + } + + /** Resolves an icon token path, e.g. \`icon.arrow-right.regular\`, to a drawable resource id. */ + @DrawableRes + fun iconRes(token: String): Int? = + when (token) { +${whenBody(iconBranches, ' ')} + else -> null + } + + private fun colorLight(token: String): Color? = + when (token) { +${whenBody(colorLightBranches, ' ')} + else -> null + } + + private fun colorDark(token: String): Color? = + when (token) { +${whenBody(colorDarkBranches, ' ')} + else -> null + } +} +`; +} diff --git a/tokens/formats/resolver-ios.ts b/tokens/formats/resolver-ios.ts new file mode 100644 index 0000000..c6f46ff --- /dev/null +++ b/tokens/formats/resolver-ios.ts @@ -0,0 +1,128 @@ +import type { TokenCatalog } from './resolver-shared.js'; + +/** + * iOS token resolver generator. + * + * Emits one `+TokenResolvable.swift` conformance per primitive type (color/font/button/icon), + * each living in that type's own target so a consumer only pulls in what it imports — no + * monolithic target that depends on every module. Each conforms to the `TokenResolvable` + * protocol (hand-written in the zero-dependency `NucleusTokens` target). Forward-only; an + * unknown token returns `nil` and trips `assertionFailure` in debug builds. + * + * Tokens are type-scoped (the resolver type implies the namespace): font `s3`, color + * `text.primary`, button `inverse.32`, icon `name.variant`. + */ + +const HEADER = '// AUTOGENERATED by tokens/build — do not edit by hand.'; + +interface Row { + key: string; + value: string; +} + +function dictionary(name: string, type: string, rows: Row[]): string { + const body = [...rows] + .sort((a, b) => a.key.localeCompare(b.key)) + .map((row) => ` "${row.key}": ${row.value},`) + .join('\n'); + return `private let ${name}: [String: ${type}] = [\n${body}\n]`; +} + +function simpleResolvable(args: { + type: string; + mapName: string; + rows: Row[]; + doc: string; +}): string { + return `${HEADER} + +import NucleusTokens + +${dictionary(args.mapName, args.type, args.rows)} + +extension ${args.type}: TokenResolvable { + /// ${args.doc} + public static func resolve(token: String) -> ${args.type}? { + guard let value = ${args.mapName}[token] else { + assertionFailure("unknown ${args.type} token: \\(token)") + return nil + } + return value + } +} +`; +} + +export function generateIOSColorResolvable(catalog: TokenCatalog): string { + const rows: Row[] = [...catalog.semanticColors, ...catalog.primitiveColors].map((c) => ({ + key: c.wireToken, + value: `.${c.accessor}`, + })); + return simpleResolvable({ + type: 'NucleusColor', + mapName: 'colorTokens', + rows, + doc: 'Resolves a type-scoped color token, e.g. `text.primary` or `grey.900`.', + }); +} + +export function generateIOSFontResolvable(catalog: TokenCatalog): string { + const rows: Row[] = catalog.fonts.map((f) => ({ key: f.wireToken, value: `.${f.wireToken}` })); + return simpleResolvable({ + type: 'NucleusFont', + mapName: 'fontTokens', + rows, + doc: 'Resolves a type-scoped typography token (bare id), e.g. `s3`.', + }); +} + +export function generateIOSButtonResolvable(catalog: TokenCatalog): string { + const rows: Row[] = catalog.buttons.map((b) => ({ key: b.wireToken, value: `.${b.accessor}` })); + return simpleResolvable({ + type: 'NucleusButton', + mapName: 'buttonTokens', + rows, + doc: 'Resolves a type-scoped button style token, e.g. `inverse.32`.', + }); +} + +export function generateIOSIconResolvable(catalog: TokenCatalog): string { + const names = dictionary( + 'iconTokens', + 'NucleusIcon', + catalog.iconNames.map((i) => ({ key: i.name, value: `.${i.swiftCase}` })), + ); + const variants = dictionary( + 'variantTokens', + 'NucleusIcon.Variant', + catalog.iconVariants.map((v) => ({ key: v, value: `.${v}` })), + ); + return `${HEADER} + +import NucleusTokens + +${names} + +${variants} + +extension NucleusIcon: TokenResolvable { + /// Resolves a type-scoped icon token \`name.variant\`, e.g. \`arrow-right.regular\`. + public static func resolve(token: String) -> (icon: NucleusIcon, variant: NucleusIcon.Variant)? { + let components = token.components(separatedBy: ".") + guard components.count == 2 else { + assertionFailure("NucleusIcon token must be \`name.variant\`: \\(token)") + return nil + } + guard + let icon = iconTokens[components[0]], + let variant = variantTokens[components[1]], + icon.availableVariants.contains(variant) + else { + assertionFailure("unknown NucleusIcon token: \\(token)") + return nil + } + return (icon, variant) + } +} +`; +} diff --git a/tokens/formats/resolver-shared.ts b/tokens/formats/resolver-shared.ts new file mode 100644 index 0000000..9f845f9 --- /dev/null +++ b/tokens/formats/resolver-shared.ts @@ -0,0 +1,176 @@ +import { loadButtonDefinition, resolveButtonStyles } from './buttons.js'; +import { discoverIconTokens, type IconVariant } from './icons-shared.js'; +import { loadColorTokens, loadFontDefinitions } from './loaders.js'; +import { camelCasePath, publicColorPath, typographyTokenPath } from './shared.js'; + +/** + * Shared catalog for the token-path ↔ token resolvers. Enumerates every published token with both + * its canonical path (for reference) and the **type-scoped wire token** the backend emits and the + * resolvers key on — the namespace that the resolver type already implies is stripped: + * - color: `semantic.color.text.primary` → `text.primary` (and `primitive.color.grey.900` → `grey.900`) + * - typography: `typography.subtitle.s3` → `s3` + * - button: `component.button.inverse.32` → `inverse.32` + * - icon: `icon.arrow-right.regular` → `arrow-right.regular` + * Reuses the same accessor-naming the platform token generators use, so the resolver can't drift. + */ + +const PRIMITIVE_SOURCE = 'tokens/definitions/color/primitive.json'; +const SEMANTIC_LIGHT_SOURCE = 'tokens/definitions/color/semantic.light.json'; +const FONT_SOURCE = 'tokens/definitions/font/fonts.json'; +const BUTTON_SOURCE = 'tokens/definitions/component/button.json'; + +export interface ColorEntry { + /** Type-scoped wire token, e.g. `text.primary` / `grey.900`. */ + wireToken: string; + /** `NucleusColor` / `NucleusSemanticColors*` accessor, e.g. `textPrimary`. */ + accessor: string; +} + +export interface FontEntry { + /** Type-scoped wire token (bare id), e.g. `s3`. Also the `NucleusFont(s)` accessor. */ + wireToken: string; + /** camelCase web-constant key, e.g. `subtitleS3`. */ + key: string; +} + +export interface ButtonEntry { + /** Type-scoped wire token, e.g. `inverse.32`. */ + wireToken: string; + /** `NucleusButton(s)` accessor + web-constant key, e.g. `inverse32`. */ + accessor: string; +} + +export interface IconEntry { + /** Type-scoped wire token, e.g. `arrow-right.regular`. */ + wireToken: string; + /** camelCase web-constant key, e.g. `arrowRightRegular`. */ + key: string; + /** kebab-case icon name, e.g. `arrow-right`. */ + name: string; + /** `NucleusIcon` Swift case, e.g. `arrowRight`. */ + swiftCase: string; + /** `NucleusIcon` Kotlin object, e.g. `ArrowRight`. */ + kotlinCase: string; + /** snake_case stem in android drawable names, e.g. `arrow_right`. */ + androidStem: string; + variant: IconVariant; +} + +export interface IconNameEntry { + /** kebab-case wire name, e.g. `arrow-right`. */ + name: string; + swiftCase: string; + kotlinCase: string; + androidStem: string; +} + +export interface TokenCatalog { + semanticColors: ColorEntry[]; + primitiveColors: ColorEntry[]; + fonts: FontEntry[]; + buttons: ButtonEntry[]; + icons: IconEntry[]; + /** Distinct icon names (for the iOS name → NucleusIcon map). */ + iconNames: IconNameEntry[]; + /** Icon variants present across the set (for the iOS variant map). */ + iconVariants: IconVariant[]; +} + +function colorEntries(source: string): ColorEntry[] { + return loadColorTokens(source).map((leaf) => ({ + wireToken: publicColorPath(leaf.path).join('.'), + accessor: camelCasePath(publicColorPath(leaf.path)), + })); +} + +function capitalize(value: string): string { + return value.charAt(0).toUpperCase() + value.slice(1); +} + +export function buildTokenCatalog(): TokenCatalog { + // Light & dark semantic palettes share the same key paths; the light source is sufficient. + const semanticColors = colorEntries(SEMANTIC_LIGHT_SOURCE); + const primitiveColors = colorEntries(PRIMITIVE_SOURCE); + + const fonts: FontEntry[] = loadFontDefinitions(FONT_SOURCE).tokens.map((token) => { + const path = typographyTokenPath(token.name); // typography.subtitle.s3 + return { + wireToken: token.name, // bare id, e.g. s3 + key: camelCasePath(path.split('.').slice(1)), // subtitleS3 (web) + }; + }); + + const buttons: ButtonEntry[] = resolveButtonStyles( + loadButtonDefinition(BUTTON_SOURCE), + ).map((style) => ({ + wireToken: `${style.variant}.${style.size}`, // inverse.32 + accessor: `${style.variant}${style.size}`, // inverse32 + })); + + const discovered = discoverIconTokens(); + const icons: IconEntry[] = discovered.flatMap((icon) => + icon.variants.map((variant) => ({ + wireToken: `${icon.name}.${variant}`, // arrow-right.regular + key: `${icon.swiftCase}${capitalize(variant)}`, // arrowRightRegular + name: icon.name, + swiftCase: icon.swiftCase, + kotlinCase: icon.kotlinCase, + androidStem: icon.androidStem, + variant, + })), + ); + const iconNames: IconNameEntry[] = discovered.map((icon) => ({ + name: icon.name, + swiftCase: icon.swiftCase, + kotlinCase: icon.kotlinCase, + androidStem: icon.androidStem, + })); + const iconVariants = [...new Set(discovered.flatMap((icon) => icon.variants))].sort(); + + const catalog = { + semanticColors, + primitiveColors, + fonts, + buttons, + icons, + iconNames, + iconVariants, + }; + validateCatalog(catalog); + return catalog; +} + +/** + * Generation-time round-trip guard: the wire tokens a single resolver keys on must be unique + * (colors merge semantic + primitive into one `NucleusColor` map), and every per-family web key + * must be unique. (Wrong-accessor regressions are caught by the native builds in CI.) + */ +function validateCatalog(catalog: TokenCatalog): void { + const groups: Array<[string, string[]]> = [ + // NucleusColor.resolve keys on one merged map → must be unique across semantic + primitive. + [ + 'color wire tokens', + [...catalog.semanticColors, ...catalog.primitiveColors].map((e) => e.wireToken), + ], + ['font wire tokens', catalog.fonts.map((e) => e.wireToken)], + ['button wire tokens', catalog.buttons.map((e) => e.wireToken)], + ['icon wire tokens', catalog.icons.map((e) => e.wireToken)], + ['ColorTokens keys', catalog.semanticColors.map((e) => e.accessor)], + ['PrimitiveColorTokens keys', catalog.primitiveColors.map((e) => e.accessor)], + ['TypographyTokens keys', catalog.fonts.map((e) => e.key)], + ['ButtonTokens keys', catalog.buttons.map((e) => e.accessor)], + ['IconTokens keys', catalog.icons.map((e) => e.key)], + ]; + for (const [label, values] of groups) { + const seen = new Set(); + for (const value of values) { + if (!value) { + throw new Error(`resolver catalog: empty value in ${label}`); + } + if (seen.has(value)) { + throw new Error(`resolver catalog: duplicate "${value}" in ${label}`); + } + seen.add(value); + } + } +} diff --git a/tokens/formats/resolver-web.ts b/tokens/formats/resolver-web.ts new file mode 100644 index 0000000..f1a2f45 --- /dev/null +++ b/tokens/formats/resolver-web.ts @@ -0,0 +1,89 @@ +import type { TokenCatalog } from './resolver-shared.js'; + +/** + * Web token-path constants generator. + * + * Emits a runtime CommonJS module (`token-paths.js`) plus literal-typed declarations + * (`token-paths.d.ts`) exporting one frozen object per token family, keyed by descriptive + * camelCase, value = the type-scoped wire token (e.g. `text.primary`, `s3`, `inverse.32`, + * `arrow-right.regular`). This is the source of truth the app-backend SDUI library imports + * instead of hand-writing token literals — so the backend maintains no mapping of its own. + */ + +interface ConstObject { + name: string; + doc: string; + entries: Array<{ key: string; value: string }>; +} + +function constObjects(catalog: TokenCatalog): ConstObject[] { + const objects: ConstObject[] = [ + { + name: 'ColorTokens', + doc: 'Type-scoped semantic color tokens, e.g. `text.primary`.', + entries: catalog.semanticColors.map((c) => ({ key: c.accessor, value: c.wireToken })), + }, + { + name: 'PrimitiveColorTokens', + doc: 'Type-scoped primitive color tokens, e.g. `grey.900`.', + entries: catalog.primitiveColors.map((c) => ({ key: c.accessor, value: c.wireToken })), + }, + { + name: 'TypographyTokens', + doc: 'Type-scoped typography tokens (bare id), e.g. `s3`.', + entries: catalog.fonts.map((f) => ({ key: f.key, value: f.wireToken })), + }, + { + name: 'ButtonTokens', + doc: 'Type-scoped button style tokens, e.g. `inverse.32`.', + entries: catalog.buttons.map((b) => ({ key: b.accessor, value: b.wireToken })), + }, + { + name: 'IconTokens', + doc: 'Type-scoped icon tokens, e.g. `arrow-right.regular`.', + entries: catalog.icons.map((i) => ({ key: i.key, value: i.wireToken })), + }, + ]; + return objects.map((object) => ({ + ...object, + entries: [...object.entries].sort((a, b) => a.key.localeCompare(b.key)), + })); +} + +const HEADER = '// AUTOGENERATED by tokens/build — do not edit by hand.'; + +export function generateWebTokenPathsJs(catalog: TokenCatalog): string { + const lines: string[] = [ + "'use strict';", + HEADER, + "Object.defineProperty(exports, '__esModule', { value: true });", + '', + ]; + for (const object of constObjects(catalog)) { + lines.push(`exports.${object.name} = Object.freeze({`); + for (const { key, value } of object.entries) { + lines.push(` ${key}: '${value}',`); + } + lines.push('});'); + lines.push(''); + } + return lines.join('\n'); +} + +export function generateWebTokenPathsDts(catalog: TokenCatalog): string { + const lines: string[] = [ + HEADER, + '// Canonical Nucleus token-path constants — runtime values with literal types.', + '', + ]; + for (const object of constObjects(catalog)) { + lines.push(`/** ${object.doc} */`); + lines.push(`export declare const ${object.name}: {`); + for (const { key, value } of object.entries) { + lines.push(` readonly ${key}: '${value}';`); + } + lines.push('};'); + lines.push(''); + } + return lines.join('\n'); +} diff --git a/tokens/formats/types-web.ts b/tokens/formats/types-web.ts index ddf2346..73d5b5f 100644 --- a/tokens/formats/types-web.ts +++ b/tokens/formats/types-web.ts @@ -1,16 +1,15 @@ -import { typographyTokenPath } from './shared.js'; -import type { ColorLeaf, FontToken } from './loaders.js'; - /** * Web TypeScript types generator. * - * Emits literal-union types so consumers (e.g. the app-backend SDUI library) get - * compile-time safety instead of `Record` when referencing token - * paths. The wire value for each token is its canonical path: - * - colors: the full definition path, e.g. `semantic.color.text.primary` - * - typography: a grouped path `typography.{category}.{id}` (UI Kit 4.0 grouping) - * - buttons: a combined path `component.button.{variant}.{size}` - * - icons: a combined path `icon.{name}.{variant}` + * Emits literal-union types so consumers (e.g. the app-backend SDUI library) get compile-time + * safety instead of `Record` when referencing tokens. The members are the + * type-scoped wire tokens the backend emits and the resolvers key on (the same `wireToken` values + * that drive `token-paths.{js,d.ts}`, so the union types and the runtime constants can never + * diverge): + * - colors: `text.primary` (semantic) / `grey.900` (primitive) + * - typography: `s3` + * - buttons: `inverse.32` + * - icons: `arrow-right.regular` */ function unionType(name: string, values: string[]): string { @@ -22,40 +21,36 @@ function unionType(name: string, values: string[]): string { } export interface WebTypesInput { - /** Combined primitive + semantic color leaves. */ - colorTokens: ColorLeaf[]; - fontTokens: FontToken[]; - /** Combined button style token paths, e.g. `component.button.primary.48`. */ + /** Type-scoped color wire tokens (semantic + primitive). */ + colorTokens: string[]; + /** Type-scoped typography wire tokens (bare ids). */ + typographyTokens: string[]; + /** Type-scoped button style wire tokens. */ buttonStyleTokens: string[]; - /** Icon token paths, e.g. `icon.arrow-right.regular`. */ + /** Type-scoped icon wire tokens. */ iconTokens: string[]; } export function generateWebTypes({ colorTokens, - fontTokens, + typographyTokens, buttonStyleTokens, iconTokens, }: WebTypesInput): string { - const colorPaths = colorTokens.map((token) => token.path.join('.')); - const typographyPaths = fontTokens.map((token) => - typographyTokenPath(token.name), - ); - return [ '// AUTOGENERATED by tokens/build — do not edit by hand.', - '// Canonical Nucleus token paths as literal-union types.', + '// Type-scoped Nucleus wire tokens as literal-union types.', '', - '/** Canonical color token path, e.g. `semantic.color.text.primary`. */', - unionType('ColorToken', colorPaths), + '/** Type-scoped color token, e.g. `text.primary`. */', + unionType('ColorToken', colorTokens), '', - '/** Grouped typography token path, e.g. `typography.headline.h1`. */', - unionType('TypographyToken', typographyPaths), + '/** Type-scoped typography token (bare id), e.g. `s3`. */', + unionType('TypographyToken', typographyTokens), '', - '/** Combined button style token path, e.g. `component.button.primary.48`. */', + '/** Type-scoped button style token, e.g. `inverse.32`. */', unionType('ButtonStyleToken', buttonStyleTokens), '', - '/** Icon token path, e.g. `icon.arrow-right.regular`. */', + '/** Type-scoped icon token, e.g. `arrow-right.regular`. */', unionType('IconToken', iconTokens), '', '/** Runtime token maps are published as JSON modules (camelCase keys → values). */', diff --git a/tokens/templates/web/package.json b/tokens/templates/web/package.json index 975d079..0fa2740 100644 --- a/tokens/templates/web/package.json +++ b/tokens/templates/web/package.json @@ -9,7 +9,9 @@ "*.json", "fonts/*", "icons/*", - "index.d.ts" + "index.d.ts", + "token-paths.js", + "token-paths.d.ts" ], "publishConfig": { "registry": "https://npm.pkg.github.com"