From 41588563969b683fb7095ed2b677d5bf135ca19f Mon Sep 17 00:00:00 2001 From: Utsav Luintel Date: Tue, 6 Jan 2026 14:40:46 +0545 Subject: [PATCH 1/5] refactor(ui/CountryPicker): update and optimize component logic --- .../components/FormWidgets/CountryPicker.tsx | 34 +-- .../src/FormWidgets/CountryPicker/index.tsx | 257 +++++++++--------- 2 files changed, 151 insertions(+), 140 deletions(-) diff --git a/apps/demo/src/Views/Ui/components/FormWidgets/CountryPicker.tsx b/apps/demo/src/Views/Ui/components/FormWidgets/CountryPicker.tsx index 4d8594d6f..0c22b3e15 100644 --- a/apps/demo/src/Views/Ui/components/FormWidgets/CountryPicker.tsx +++ b/apps/demo/src/Views/Ui/components/FormWidgets/CountryPicker.tsx @@ -19,55 +19,55 @@ export const CountryPickerDemo = () => { const locale = i18n.language; const data = [ - { - default: "{ en: defaultEnCatalogue }", - description: t("countryPicker.propertiesDescription.i18n"), - id: 1, - prop: "i18n", - type: "Record>", - }, { default: '"en"', description: t("countryPicker.propertiesDescription.fallbackLocale"), - id: 2, + id: 1, prop: "fallbackLocale", type: "string", }, { default: "true", description: t("countryPicker.propertiesDescription.flags"), - id: 3, + id: 2, prop: "flags", type: "Boolean", }, { default: "-", description: t("countryPicker.propertiesDescription.flagsPath"), - id: 4, + id: 3, prop: "flagsPath", type: "(code: string) => string", }, { default: "left", description: t("countryPicker.propertiesDescription.flagsPosition"), - id: 5, + id: 4, prop: "flagsPosition", type: "left | right | right-edge", }, { default: "rectangular", description: t("countryPicker.propertiesDescription.flagsStyle"), - id: 6, + id: 5, prop: "flagsStyle", type: "circle | rectangular | square", }, { default: '"en"', description: t("countryPicker.propertiesDescription.locale"), - id: 7, + id: 6, prop: "locale", type: "string", }, + { + default: "{ en: defaultEnCatalogue }", + description: t("countryPicker.propertiesDescription.i18n"), + id: 7, + prop: "locales", + type: "Record>", + }, { default: "[]", description: t("countryPicker.propertiesDescription.include"), @@ -240,7 +240,7 @@ const selectedLocale = i18n.language;
setSingleSelectValue(value)} @@ -270,7 +270,7 @@ const selectedLocale = "np";
; -export type I18nData = Record; +export type LocalesData = Record; export type GroupData = Record; export type CountryPickerLabels = { @@ -27,7 +27,7 @@ export type CountryPickerProperties = Omit< flagsPosition?: "left" | "right" | "right-edge"; flagsStyle?: "circle" | "rectangular" | "square"; groups?: GroupData; - i18n?: I18nData; + locales?: LocalesData; include?: string[]; includeFavorites?: boolean; labels?: CountryPickerLabels; @@ -36,136 +36,158 @@ export type CountryPickerProperties = Omit< export { defaultGroups }; -const getAllCountryCodes = ( - i18n: I18nData | undefined, - locale: string, +const determineFallback = ( + locales: LocalesData | undefined, fallbackLocale: string, -): string[] => { - if (!i18n) return Object.keys(defaultEnglishCatalogue); - - const localeData = i18n[locale]; - const fallbackData = i18n[fallbackLocale]; - - if (!localeData && !fallbackData) { - return Object.keys(defaultEnglishCatalogue); +): TranslationCatalogue | null => { + if (locales?.[fallbackLocale]) { + return locales[fallbackLocale]; } - - return Array.from( - new Set([ - ...(fallbackData ? Object.keys(fallbackData) : []), - ...(localeData ? Object.keys(localeData) : []), - ]), - ); + if (fallbackLocale === "en") { + return defaultEnglishCatalogue; + } + return null; }; -const getFilteredCountryCodes = ( - codes: string[], - include?: string[], - exclude?: string[], -): string[] => { - const includeSet = include && include.length > 0 ? new Set(include) : null; - const excludeSet = exclude && exclude.length > 0 ? new Set(exclude) : null; +const generateBaseOptions = ( + locales: LocalesData | undefined, + locale: string, + fallbackLocale: string, + include: string[] | undefined, + exclude: string[] | undefined, +): Option[] => { + const fallbackData = determineFallback(locales, fallbackLocale); - if (!includeSet && !excludeSet) return codes; + if (!fallbackData) { + return []; + } - return codes.filter((code) => { - if (excludeSet && excludeSet.has(code)) { - return false; + const baseOptions: Option[] = []; + + Object.entries(fallbackData).forEach(([code, fallbackLabel]) => { + if (exclude && exclude.includes(code)) { + return; } - if (includeSet && !includeSet.has(code)) { - return false; + if (include && !include.includes(code)) { + return; } - return true; + const label = locales?.[locale]?.[code] || fallbackLabel || code; + baseOptions.push({ value: code as unknown as T, label }); }); -}; -const countryLabel = ( - code: string, - i18n: I18nData | undefined, - locale: string, - fallbackLocale: string, -): string => { - return ( - i18n?.[locale]?.[code] || - i18n?.[fallbackLocale]?.[code] || - defaultEnglishCatalogue[code as keyof typeof defaultEnglishCatalogue] || - code - ); + return baseOptions; }; -const getOptionsWithFavorites = ( +const splitFavoritesAndMain = ( baseOptions: Option[], - favorites: string[] | undefined, - labels: CountryPickerLabels | undefined, + favorites: string[], includeFavorites: boolean, ) => { - if (!favorites || favorites.length === 0) { - return baseOptions; - } + const favoriteOptions: Option[] = []; + const mainOptions: Option[] = []; + + baseOptions.forEach((option) => { + if (favorites.includes(String(option.value))) { + favoriteOptions.push(option); + if (includeFavorites) { + mainOptions.push(option); + } + } else { + mainOptions.push(option); + } + }); - const favoriteSet = new Set(favorites); - const favoriteList = baseOptions.filter((item) => - favoriteSet.has(String(item.value)), - ); + return { favoriteOptions, mainOptions }; +}; - if (favoriteList.length === 0) { - return baseOptions; - } +const divideListInGroups = ( + list: Option[], + groups: GroupData, +): { label: string; options: Option[] }[] => { + const groupedResult: { label: string; options: Option[] }[] = []; - const favoritesLabel = labels?.favorites || "Favorites"; - const allCountriesLabel = labels?.allCountries || "All countries"; + Object.entries(groups).forEach(([groupLabel, groupCodes]) => { + const groupOptions = list.filter((option) => + groupCodes.includes(String(option.value)), + ); - const remainingList = includeFavorites - ? baseOptions - : baseOptions.filter((item) => !favoriteSet.has(String(item.value))); + if (groupOptions.length > 0) { + groupedResult.push({ + label: groupLabel, + options: groupOptions, + }); + } + }); - return [ - { label: favoritesLabel, options: favoriteList }, - { label: allCountriesLabel, options: remainingList }, - ]; + return groupedResult; }; -const getOptionsWithGroups = ( - baseOptions: Option[], - groups: GroupData, - favorites: string[] | undefined, - labels: CountryPickerLabels | undefined, -) => { - const finalGroupedOptions: { label: string; options: Option[] }[] = []; +type GetCountryOptions = { + locales?: LocalesData; + locale: string; + fallbackLocale: string; + include?: string[]; + exclude?: string[]; + groups?: GroupData; + favorites?: string[]; + labels?: CountryPickerLabels; + includeFavorites?: boolean; +}; - const optionsMap = new Map( - baseOptions.map((option) => [String(option.value), option]), +export const getCountryOptions = ({ + locales, + locale, + fallbackLocale, + include, + exclude, + groups, + favorites, + labels, + includeFavorites = true, +}: GetCountryOptions) => { + const baseOptions = generateBaseOptions( + locales, + locale, + fallbackLocale, + include, + exclude, ); - if (favorites && favorites.length > 0) { - const favoriteList = favorites - .map((code) => optionsMap.get(code)) - .filter((option): option is Option => !!option); + const hasGroups = groups && Object.keys(groups).length > 0; + const hasFavorites = favorites && favorites.length > 0; - if (favoriteList.length > 0) { - finalGroupedOptions.push({ - label: labels?.favorites || "Favorites", - options: favoriteList, - }); + if (!hasFavorites) { + if (hasGroups) { + return divideListInGroups(baseOptions, groups!); } + return baseOptions; } - Object.entries(groups).forEach(([groupLabel, groupCodes]) => { - const groupOptions = groupCodes - .map((code) => optionsMap.get(code)) - .filter((option): option is Option => !!option); + const { favoriteOptions, mainOptions } = splitFavoritesAndMain( + baseOptions, + favorites, + includeFavorites, + ); - if (groupOptions.length > 0) { - finalGroupedOptions.push({ - label: groupLabel, - options: groupOptions, - }); - } - }); + const favoriteGroup = { + label: labels?.favorites || "Favorites", + options: favoriteOptions, + }; + + if (hasGroups) { + const groupedMain = divideListInGroups(mainOptions, groups!); + return [favoriteGroup, ...groupedMain]; + } - return finalGroupedOptions; + return [ + favoriteGroup, + { + label: labels?.allCountries || "All countries", + options: mainOptions, + }, + ]; }; export const CountryPicker = ({ @@ -177,7 +199,7 @@ export const CountryPicker = ({ flagsPosition = "left", flagsStyle = "rectangular", groups, - i18n, + locales, include, includeFavorites = true, labels, @@ -185,38 +207,27 @@ export const CountryPicker = ({ ...properties }: CountryPickerProperties) => { const options = useMemo(() => { - const countryCodes = getAllCountryCodes(i18n, locale, fallbackLocale); - - const filteredCountryCodes = getFilteredCountryCodes( - countryCodes, + return getCountryOptions({ + locales, + locale, + fallbackLocale, include, exclude, - ); - const baseOptions: Option[] = filteredCountryCodes.map((code) => ({ - value: code as unknown as T, - label: countryLabel(code, i18n, locale, fallbackLocale), - })); - - if (groups && Object.keys(groups).length > 0) { - return getOptionsWithGroups(baseOptions, groups, favorites, labels); - } - - return getOptionsWithFavorites( - baseOptions, + groups, favorites, labels, includeFavorites, - ); + }); }, [ - exclude, + locales, + locale, fallbackLocale, - favorites, - groups, - i18n, include, - includeFavorites, + exclude, + groups, + favorites, labels, - locale, + includeFavorites, ]); const getFlagClass = (code?: string) => From 7600dc99ce72e417f7b8d4bc6f08a7a4ef2bb959 Mon Sep 17 00:00:00 2001 From: Utsav Luintel Date: Wed, 7 Jan 2026 08:11:56 +0545 Subject: [PATCH 2/5] refactor(ui/countryPicker): update variables names and fucntion name --- .../ui/src/FormWidgets/CountryPicker/index.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/FormWidgets/CountryPicker/index.tsx b/packages/ui/src/FormWidgets/CountryPicker/index.tsx index a6309d0bc..2e9a308ad 100644 --- a/packages/ui/src/FormWidgets/CountryPicker/index.tsx +++ b/packages/ui/src/FormWidgets/CountryPicker/index.tsx @@ -6,8 +6,8 @@ import defaultGroups from "./groups.json"; import type { Option } from "../Select"; -export type TranslationCatalogue = Record; -export type LocalesData = Record; +export type Translation = Record; +export type LocalesData = Record; export type GroupData = Record; export type CountryPickerLabels = { @@ -36,16 +36,18 @@ export type CountryPickerProperties = Omit< export { defaultGroups }; -const determineFallback = ( +const getFallbackLocale = ( locales: LocalesData | undefined, fallbackLocale: string, -): TranslationCatalogue | null => { +): Translation | null => { if (locales?.[fallbackLocale]) { return locales[fallbackLocale]; } + if (fallbackLocale === "en") { return defaultEnglishCatalogue; } + return null; }; @@ -56,7 +58,7 @@ const generateBaseOptions = ( include: string[] | undefined, exclude: string[] | undefined, ): Option[] => { - const fallbackData = determineFallback(locales, fallbackLocale); + const fallbackData = getFallbackLocale(locales, fallbackLocale); if (!fallbackData) { return []; @@ -124,7 +126,7 @@ const divideListInGroups = ( return groupedResult; }; -type GetCountryOptions = { +type CountryOptions = { locales?: LocalesData; locale: string; fallbackLocale: string; @@ -146,7 +148,7 @@ export const getCountryOptions = ({ favorites, labels, includeFavorites = true, -}: GetCountryOptions) => { +}: CountryOptions) => { const baseOptions = generateBaseOptions( locales, locale, @@ -162,6 +164,7 @@ export const getCountryOptions = ({ if (hasGroups) { return divideListInGroups(baseOptions, groups!); } + return baseOptions; } From ddf2e8c789258d56ea8c34f1d4b11c29433bdd81 Mon Sep 17 00:00:00 2001 From: Utsav Luintel Date: Wed, 7 Jan 2026 14:03:46 +0545 Subject: [PATCH 3/5] refactor(ui/CountryPicker): update logic --- .../src/FormWidgets/CountryPicker/index.tsx | 308 +++++++++--------- packages/ui/src/utils/CountryPicker.ts | 17 + 2 files changed, 172 insertions(+), 153 deletions(-) create mode 100644 packages/ui/src/utils/CountryPicker.ts diff --git a/packages/ui/src/FormWidgets/CountryPicker/index.tsx b/packages/ui/src/FormWidgets/CountryPicker/index.tsx index 2e9a308ad..7fd01c325 100644 --- a/packages/ui/src/FormWidgets/CountryPicker/index.tsx +++ b/packages/ui/src/FormWidgets/CountryPicker/index.tsx @@ -1,14 +1,14 @@ -import React, { useMemo } from "react"; +import React, { useCallback, useMemo } from "react"; +import { getFallbackTranslation } from "../../utils/CountryPicker"; import { Select, ISelectProperties } from "../Select"; -import defaultEnglishCatalogue from "./en.json"; import defaultGroups from "./groups.json"; import type { Option } from "../Select"; export type Translation = Record; -export type LocalesData = Record; -export type GroupData = Record; +export type Locales = Record; +export type Groups = Record; export type CountryPickerLabels = { favorites?: string; @@ -26,39 +26,24 @@ export type CountryPickerProperties = Omit< flagsPath?: (code: string) => string; flagsPosition?: "left" | "right" | "right-edge"; flagsStyle?: "circle" | "rectangular" | "square"; - groups?: GroupData; - locales?: LocalesData; + groups?: Groups; include?: string[]; includeFavorites?: boolean; labels?: CountryPickerLabels; locale?: string; + locales?: Locales; }; export { defaultGroups }; -const getFallbackLocale = ( - locales: LocalesData | undefined, - fallbackLocale: string, -): Translation | null => { - if (locales?.[fallbackLocale]) { - return locales[fallbackLocale]; - } - - if (fallbackLocale === "en") { - return defaultEnglishCatalogue; - } - - return null; -}; - -const generateBaseOptions = ( - locales: LocalesData | undefined, - locale: string, +const getBaseOptions = ( + exclude: string[] | undefined, fallbackLocale: string, include: string[] | undefined, - exclude: string[] | undefined, + locale: string, + locales: Locales | undefined, ): Option[] => { - const fallbackData = getFallbackLocale(locales, fallbackLocale); + const fallbackData = getFallbackTranslation(fallbackLocale, locales); if (!fallbackData) { return []; @@ -82,31 +67,25 @@ const generateBaseOptions = ( return baseOptions; }; -const splitFavoritesAndMain = ( - baseOptions: Option[], - favorites: string[], - includeFavorites: boolean, -) => { - const favoriteOptions: Option[] = []; - const mainOptions: Option[] = []; - - baseOptions.forEach((option) => { - if (favorites.includes(String(option.value))) { - favoriteOptions.push(option); - if (includeFavorites) { - mainOptions.push(option); - } - } else { - mainOptions.push(option); - } - }); - - return { favoriteOptions, mainOptions }; -}; - -const divideListInGroups = ( +const getFlagClass = ( + code: string | undefined, + position: string, + style: string, +) => + [ + "flag-icon", + code && `flag-icon-${code.trim().toLowerCase()}`, + position === "right" && "flag-icon-right", + position === "right-edge" && "flag-icon-right-edge", + style === "circle" && "flag-icon-rounded", + style === "square" && "flag-icon-squared", + ] + .filter(Boolean) + .join(" "); + +const getGroups = ( + groups: Groups, list: Option[], - groups: GroupData, ): { label: string; options: Option[] }[] => { const groupedResult: { label: string; options: Option[] }[] = []; @@ -126,73 +105,115 @@ const divideListInGroups = ( return groupedResult; }; -type CountryOptions = { - locales?: LocalesData; - locale: string; - fallbackLocale: string; - include?: string[]; - exclude?: string[]; - groups?: GroupData; - favorites?: string[]; - labels?: CountryPickerLabels; - includeFavorites?: boolean; -}; - -export const getCountryOptions = ({ - locales, - locale, - fallbackLocale, - include, +const getOptions = ({ exclude, - groups, + fallbackLocale = "en", favorites, - labels, + groups, + include, includeFavorites = true, -}: CountryOptions) => { - const baseOptions = generateBaseOptions( - locales, - locale, + labels, + locale = "en", + locales, +}: Pick< + CountryPickerProperties, + | "exclude" + | "fallbackLocale" + | "favorites" + | "groups" + | "include" + | "includeFavorites" + | "labels" + | "locale" + | "locales" +>) => { + const baseOptions = getBaseOptions( + exclude, fallbackLocale, include, - exclude, + locale, + locales, ); - const hasGroups = groups && Object.keys(groups).length > 0; - const hasFavorites = favorites && favorites.length > 0; - - if (!hasFavorites) { - if (hasGroups) { - return divideListInGroups(baseOptions, groups!); - } + if (favorites && favorites.length > 0) { + return getOptionsWithFavorites( + baseOptions, + favorites, + includeFavorites, + groups, + labels, + ); + } - return baseOptions; + if (groups && Object.keys(groups).length > 0) { + return getGroups(groups, baseOptions); } - const { favoriteOptions, mainOptions } = splitFavoritesAndMain( - baseOptions, - favorites, - includeFavorites, + return baseOptions; +}; + +const getOptionsWithFavorites = ( + baseOptions: Option[], + favorites: string[], + includeFavorites: boolean, + groups?: Groups, + labels?: CountryPickerLabels, +) => { + const favoriteOptions = baseOptions.filter((option) => + favorites.includes(String(option.value)), ); - const favoriteGroup = { - label: labels?.favorites || "Favorites", - options: favoriteOptions, - }; + const mainOptions = includeFavorites + ? baseOptions + : baseOptions.filter((option) => !favorites.includes(String(option.value))); - if (hasGroups) { - const groupedMain = divideListInGroups(mainOptions, groups!); - return [favoriteGroup, ...groupedMain]; - } + const mainGroups = + groups && Object.keys(groups).length > 0 + ? getGroups(groups, mainOptions) + : [ + { + label: labels?.allCountries || "All countries", + options: mainOptions, + }, + ]; return [ - favoriteGroup, { - label: labels?.allCountries || "All countries", - options: mainOptions, + label: labels?.favorites || "Favorites", + options: favoriteOptions, }, + ...mainGroups, ]; }; +const renderCountryOption = ( + option: Option, + { + flags, + flagsPath, + flagsPosition = "left", + flagsStyle = "rectangular", + }: Pick< + CountryPickerProperties, + "flags" | "flagsPath" | "flagsPosition" | "flagsStyle" + >, +) => { + const code = String(option.value); + const flagClass = getFlagClass(code, flagsPosition, flagsStyle); + + return ( +
+ {flags && + (flagsPath ? ( + {option.label} + ) : ( + + ))} + {option.label} +
+ ); +}; + export const CountryPicker = ({ exclude, fallbackLocale = "en", @@ -210,78 +231,59 @@ export const CountryPicker = ({ ...properties }: CountryPickerProperties) => { const options = useMemo(() => { - return getCountryOptions({ - locales, - locale, - fallbackLocale, - include, + return getOptions({ exclude, - groups, + fallbackLocale, favorites, - labels, + groups, + include, includeFavorites, + labels, + locale, + locales, }); }, [ - locales, - locale, - fallbackLocale, - include, exclude, - groups, + fallbackLocale, favorites, - labels, + groups, + include, includeFavorites, + labels, + locale, + locales, ]); - const getFlagClass = (code?: string) => - [ - "flag-icon", - code && `flag-icon-${code.trim().toLowerCase()}`, - flagsPosition === "right" && "flag-icon-right", - flagsPosition === "right-edge" && "flag-icon-right-edge", - flagsStyle === "circle" && "flag-icon-rounded", - flagsStyle === "square" && "flag-icon-squared", - ] - .filter(Boolean) - .join(" "); - - const handleOnChange = (value: T | T[]) => { - if (!properties.onChange) return; - const result = Array.isArray(value) - ? (Array.from(new Set(value)) as T[]) - : value; - (properties.onChange as (value: T | T[]) => void)(result); - }; - - const renderOptionWithFlags = ( - option: Option & { - groupLabel?: string; + const handleOnChange = useCallback( + (value: T | T[]) => { + if (!properties.onChange) { + return; + } + + const result = Array.isArray(value) + ? (Array.from(new Set(value)) as T[]) + : value; + (properties.onChange as (value: T | T[]) => void)(result); }, - ) => { - const code = String(option.value); - - return ( -
- {flags && - (flagsPath ? ( - {option.label} - ) : ( - - ))} - {option.label} -
- ); - }; + [properties.onChange], + ); + + const handleRenderOption = useCallback( + (option: Option) => + renderCountryOption(option, { + flags, + flagsPath, + flagsPosition, + flagsStyle, + }), + [flags, flagsPath, flagsPosition, flagsStyle], + ); return (