Created
August 15, 2023 16:33
-
-
Save remy727/453a74579c648492771df5d0361a6f5b to your computer and use it in GitHub Desktop.
React Polaris multiple select component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
useCallback, | |
useState, | |
} from "react"; | |
import { | |
Button, | |
Checkbox, | |
Collapsible, | |
Icon, | |
Modal, | |
Scrollable, | |
TextField, | |
TextStyle, | |
} from "@shopify/polaris"; | |
import { | |
ChevronDownMinor, | |
ChevronUpMinor, | |
SearchMajor, | |
} from '@shopify/polaris-icons'; | |
import pluralize from 'pluralize'; | |
import Flag from 'react-world-flags' | |
import { isEmpty } from "../../utils"; | |
export function ShippingZoneRegionsModal({ | |
active, | |
onModalClose, | |
onModalSave, | |
shippingRegions: initialShippingRegions, | |
shippingZoneCountries: initialShippingZoneCountries, | |
}) { | |
const [shippingZoneCountries, setShippingZoneCountries] = useState(initialShippingZoneCountries || []); | |
const [shippingRegions, setShippingRegions] = useState(initialShippingRegions || []); | |
const [openRegions, setOpenRegions] = useState({}); | |
const [keyword, setKeyword] = useState(''); | |
const [checked, setChecked] = useState({}); | |
const handleToggle = useCallback((countryCode) => { | |
const openRegion = openRegions[countryCode] || false; | |
setOpenRegions({...openRegions, [countryCode]: !openRegion}); | |
}, [openRegions]); | |
const filterShippingZoneCountries = useCallback((keyword) => { | |
setKeyword(keyword); | |
let filteredShippingZoneCountries = []; | |
if (isEmpty(keyword)) { | |
filteredShippingZoneCountries = initialShippingZoneCountries; | |
} else { | |
filteredShippingZoneCountries = shippingZoneCountries.filter(zone => zone.name.toLowerCase().includes(keyword.toLowerCase())); | |
} | |
setShippingZoneCountries(filteredShippingZoneCountries); | |
}, [shippingZoneCountries]); | |
const handleSelectCountry = useCallback((newChecked, countryCode) => { | |
const shippingZone = shippingZoneCountries.find((zone) => zone.code == countryCode); | |
let regionsChecked = {} | |
if (newChecked) { | |
const regions = shippingZone.zones.map(zone => zone.name); | |
shippingZone.zones.forEach(zone => regionsChecked[`region_${countryCode}_${zone.code}`] = true ); | |
const shippingRegion = { | |
country_code: countryCode, | |
regions: regions, | |
}; | |
setShippingRegions([...shippingRegions, shippingRegion]); | |
} else { | |
shippingZone.zones.forEach(zone => regionsChecked[`region_${countryCode}_${zone.code}`] = false ); | |
const newShippingRegions = shippingRegions.filter(shippingRegion => shippingRegion.country_code !== countryCode); | |
setShippingRegions(newShippingRegions); | |
} | |
setChecked(Object.assign(checked, | |
{ [`country_${countryCode}`]: newChecked }, | |
regionsChecked, | |
)) | |
regionsChecked | |
}, [shippingRegions, checked]); | |
const handleSelectRegion = useCallback((newChecked, countryCode, zone) => { | |
const existingShippingRegion = shippingRegions.find((shippingRegion) => shippingRegion.country_code == countryCode); | |
const shippingZone = shippingZoneCountries.find((zone) => zone.code == countryCode); | |
const shippingZoneRegionsCount = shippingZone?.zones?.length; | |
let shippingRegionZonesCount, countryChecked; | |
if (existingShippingRegion !== undefined) { | |
let regions = []; | |
if (newChecked) { | |
regions = [...existingShippingRegion.regions, zone.name]; | |
} else { | |
regions = existingShippingRegion.regions.filter(region => region !== zone.name); | |
} | |
const newShippingRegions = shippingRegions.map((shippingRegion) => { | |
if (shippingRegion.country_code === countryCode) { | |
return { | |
country_code: countryCode, | |
regions: regions, | |
}; | |
} else { | |
return shippingRegion; | |
} | |
}); | |
setShippingRegions(newShippingRegions); | |
shippingRegionZonesCount = regions.length; | |
} else { | |
if (newChecked) { | |
setShippingRegions([...shippingRegions, { | |
country_code: countryCode, | |
regions: [zone.name], | |
}]); | |
shippingRegionZonesCount = 1; | |
} | |
} | |
if (shippingZoneRegionsCount === shippingRegionZonesCount) { | |
countryChecked = true; | |
} else if (shippingRegionZonesCount == 0) { | |
countryChecked = false; | |
} else { | |
countryChecked = "indeterminate"; | |
} | |
setChecked({...checked, | |
[`region_${countryCode}_${zone.code}`]: newChecked, | |
[`country_${countryCode}`]: countryChecked, | |
}); | |
}, [shippingRegions, checked]); | |
const handleModalSave = useCallback(() => { | |
onModalSave(shippingRegions); | |
}, [shippingRegions]); | |
const renderZone = (countryCode, zone) => ( | |
<div className="region" key={`zone-${zone.name}`}> | |
<Checkbox | |
label={zone.name} | |
checked={checked[`region_${countryCode}_${zone.code}`]} | |
onChange={(newChecked) => handleSelectRegion(newChecked, countryCode, zone)} | |
/> | |
</div> | |
); | |
const renderZonesLabel = (country) => { | |
let provinceKey = country.provinceKey.toLowerCase().replaceAll(/_/ig, ' '); | |
const iconSource = openRegions[country.code] ? ChevronUpMinor : ChevronDownMinor; | |
const shippingRegion = shippingRegions.find(shippingRegion => shippingRegion.country_code == country.code); | |
const selectedRegionsCount = shippingRegion?.regions?.length || 0; | |
return ( | |
<div className="collapse-regions"> | |
<Button | |
plain | |
onClick={() => handleToggle(country.code)} | |
ariaControls="basic-collapsible" | |
> | |
<TextStyle variation="subdued">{`${selectedRegionsCount} of ${country.zones.length} ${pluralize(provinceKey)}`}</TextStyle> | |
<Icon source={iconSource} /> | |
</Button> | |
</div> | |
); | |
} | |
const unknownFlag = <img src="https://cdn.shopify.com/shopifycloud/web/assets/v1/3d6a06718f3daff1.svg" />; | |
const renderCountry = (country) => { | |
const countryLabel = ( | |
<div className="country-label"> | |
<Flag | |
key={`flag-${country.code}`} | |
code={country.code} | |
fallback={unknownFlag} | |
/> | |
{country.name} | |
</div> | |
); | |
return ( | |
<> | |
<div className="country" key={`country_${country.code}`}> | |
<Checkbox | |
label={countryLabel} | |
checked={checked[`country_${country.code}`]} | |
onChange={(newChecked) => handleSelectCountry(newChecked, country.code)} | |
/> | |
{country.zones.length > 0 && renderZonesLabel(country)} | |
</div> | |
<Collapsible | |
id="basic-collapsible" | |
open={openRegions[country.code] || false} | |
> | |
{country.zones.map((zone) => renderZone(country.code, zone))} | |
</Collapsible> | |
</> | |
); | |
}; | |
return ( | |
<> | |
<Modal | |
open={active} | |
onClose={onModalClose} | |
title="Shipping Zone Countries/States/Regions" | |
primaryAction={{ | |
content: "Save", | |
onAction: () => handleModalSave(), | |
}} | |
> | |
<Modal.Section> | |
<TextField | |
value={keyword} | |
onChange={filterShippingZoneCountries} | |
prefix={<Icon source={SearchMajor} />} | |
placeholder="Search countries and regions by name or code" | |
/> | |
</Modal.Section> | |
<Modal.Section> | |
<Scrollable style={{height: '300px'}}> | |
{shippingZoneCountries.map((country) => renderCountry(country))} | |
</Scrollable> | |
</Modal.Section> | |
</Modal> | |
</> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment