Skip to content

Instantly share code, notes, and snippets.

@remy727
Created August 15, 2023 16:33
Show Gist options
  • Save remy727/453a74579c648492771df5d0361a6f5b to your computer and use it in GitHub Desktop.
Save remy727/453a74579c648492771df5d0361a6f5b to your computer and use it in GitHub Desktop.
React Polaris multiple select component
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