Sustainable Design

Sort by

Relevance

Filter

(0)

Market Sectors

Civic & Government

Hospitality

Parks & Recreation

Public Works & Maintenance Facility

Science & Technology

Workplace

Building Types

Advanced Manufacturing

Airport Data Centers

Bioscience

Brewery

Cafe

Cafeteria/ Dining

Cleanrooms

Cold Vehicle Storage

Community Center

Controlled Environments

Emergency Response

Equipment Yard

Fleet Services

Fuel Islands

Government Workplace

Hazardous Materials / Controls

Health & Wellness

Historic Preservation

Household Hazardous Waste

Law Enforcement

Library

Material Recycling

Medical Office

Memorials

Outdoor Pavilions

Park Building

Public Lobby

R&D Labs

Restaurant

Retail

Show Room/ Demonstration Labs

Splash Pads & Pool

Stadium

Substations

Tenant Improvement

Test Environments

Training Facilities

Transportation / Transit

Trailhead

Warm Vehicle Storage

Wash Bays

Workplace

Adaptive Reuse

Filter

(0)

Market Sectors

Civic & Government

Hospitality

Parks & Recreation

Public Works & Maintenance Facility

Science & Technology

Workplace

Building Types

Advanced Manufacturing

Airport Data Centers

Bioscience

Brewery

Cafe

Cafeteria/ Dining

Cleanrooms

Cold Vehicle Storage

Community Center

Controlled Environments

Emergency Response

Equipment Yard

Fleet Services

Fuel Islands

Government Workplace

Hazardous Materials / Controls

Health & Wellness

Historic Preservation

Household Hazardous Waste

Law Enforcement

Library

Material Recycling

Medical Office

Memorials

Outdoor Pavilions

Park Building

Public Lobby

R&D Labs

Restaurant

Retail

Show Room/ Demonstration Labs

Splash Pads & Pool

Stadium

Substations

Tenant Improvement

Test Environments

Training Facilities

Transportation / Transit

Trailhead

Warm Vehicle Storage

Wash Bays

Workplace

Adaptive Reuse

Filter

(0)

Market Sectors

Civic & Government

Hospitality

Parks & Recreation

Public Works & Maintenance Facility

Science & Technology

Workplace

Services

Architecture

Branding

Digital Practice/ 3D Visualizations

Facility Assessments

Interior Design

Master Planning

Space Needs Analysis/ Fit Plan

Sustainable Design

Workplace Strategy

Filter

(0)

Market Sectors

Civic & Government

Hospitality

Parks & Recreation

Public Works & Maintenance Facility

Science & Technology

Workplace

Building Types

Advanced Manufacturing

Airport Data Centers

Bioscience

Brewery

Cafe

Cafeteria/ Dining

Cleanrooms

Cold Vehicle Storage

Community Center

Controlled Environments

Emergency Response

Equipment Yard

Fleet Services

Fuel Islands

Government Workplace

Hazardous Materials / Controls

Health & Wellness

Historic Preservation

Household Hazardous Waste

Law Enforcement

Library

Material Recycling

Medical Office

Memorials

Outdoor Pavilions

Park Building

Public Lobby

R&D Labs

Restaurant

Retail

Show Room/ Demonstration Labs

Splash Pads & Pool

Stadium

Substations

Tenant Improvement

Test Environments

Training Facilities

Transportation / Transit

Trailhead

Warm Vehicle Storage

Wash Bays

Workplace

Adaptive Reuse

Filter

(0)

Market Sectors

Civic & Government

Hospitality

Parks & Recreation

Public Works & Maintenance Facility

Science & Technology

Workplace

Services

Architecture

Branding

Digital Practice/ 3D Visualizations

Facility Assessments

Interior Design

Master Planning

Space Needs Analysis/ Fit Plan

Sustainable Design

Workplace Strategy

//v1


import * as React from "react"

import { addPropertyControls, ControlType, RenderTarget } from "framer"


// Constants for localStorage keys

const FILTER_STORAGE_KEY = "shopx_product_filters"

const SORT_STORAGE_KEY = "shopx_product_sort"


// Helper function to find 13-digit number

function findDeep13DigitNumber(obj: any): string | null {

if (typeof obj === "string") {

const match = obj.match(/\d{13}/)

if (match) return match[0]

return null

}

if (!obj || typeof obj !== "object") return null


for (const key in obj) {

if (Array.isArray(obj[key]) || typeof obj[key] === "object") {

const found = findDeep13DigitNumber(obj[key])

if (found) return found

} else if (typeof obj[key] === "string") {

const match = obj[key].match(/\d{13}/)

if (match) return match[0]

}

}

return null

}


export default function ProductSort(props) {

const { MarketSector } = props

const [sortedChildren, setSortedChildren] = React.useState(null)

const [products, setProducts] = React.useState([])

const [isTransitioning, setIsTransitioning] = React.useState(false)

const [isSettling, setIsSettling] = React.useState(false)

const transitionTimeoutRef = React.useRef(null)

const settlingTimeoutRef = React.useRef(null)

const lastUpdateRef = React.useRef(null)

const urlParams =

typeof window !== "undefined"

? new URLSearchParams(window.location.search)

: new URLSearchParams()


const [sortConfig, setSortConfig] = React.useState(() => {

const savedSort = urlParams.get("sortConfig")

return savedSort

? JSON.parse(savedSort)

: {

type: "relevancy",

sortBy: "relevancy",

sortDirection: null,

}

})


const [filters, setFilters] = React.useState(() => {

const savedFilters = urlParams.get("filters")

return savedFilters

? JSON.parse(savedFilters)

: {

"market sector": {

active: false,

values: [],

},

"building type": {

active: false,

values: [],

},

service: {

active: false,

values: [],

},

"on-sale": {

active: false,

value: true,

},

"in-stock": {

active: false,

value: true,

},

bundles: {

active: false,

value: true,

},

subscriptions: {

active: false,

value: true,

},

price: {

active: false,

ranges: [],

},

discount: {

active: false,

ranges: [],

},

variant: {

active: false,

values: [],

},

}

})


// Save filters to localStorage whenever they change

React.useEffect(() => {

try {

localStorage.setItem(FILTER_STORAGE_KEY, JSON.stringify(filters))

} catch (e) {

console.error("Error saving filters to localStorage:", e)

}

}, [filters])


// Save sort config to localStorage whenever it changes

React.useEffect(() => {

try {

localStorage.setItem(SORT_STORAGE_KEY, JSON.stringify(sortConfig))

} catch (e) {

console.error("Error saving sort config to localStorage:", e)

}

}, [sortConfig])


// Listen for products data

React.useEffect(() => {

const handleProductsReady = (e) => {

console.log("🚀 Full Product Data Structure:", {

source: "data__products-ready event",

sampleProduct: e.detail.products[0],

availableFields: e.detail.products[0]

? Object.keys(e.detail.products[0])

: [],

nodeFields: e.detail.products[0]?.node

? Object.keys(e.detail.products[0].node)

: [],

variants: e.detail.products[0]?.node?.variants?.edges?.[0]?.node

? Object.keys(

e.detail.products[0].node.variants.edges[0].node

)

: [],

rawData: e.detail.products[0]?.node,

})

if (Array.isArray(e.detail.products)) {

setProducts(e.detail.products)

}

}


// Initial check for existing products

if (window["shopXtools"]?.products) {

console.log("🚀 Full Product Data Structure:", {

source: "window.shopXtools",

sampleProduct: window["shopXtools"].products[0],

availableFields: window["shopXtools"].products[0]

? Object.keys(window["shopXtools"].products[0])

: [],

nodeFields: window["shopXtools"].products[0]?.node

? Object.keys(window["shopXtools"].products[0].node)

: [],

variants: window["shopXtools"].products[0]?.node?.variants

?.edges?.[0]?.node

? Object.keys(

window["shopXtools"].products[0].node.variants

.edges[0].node

)

: [],

rawData: window["shopXtools"].products[0]?.node,

})

if (Array.isArray(window["shopXtools"].products)) {

setProducts(window["shopXtools"].products)

}

}


// Add event listener

document.addEventListener("data__products-ready", handleProductsReady)


return () => {

document.removeEventListener(

"data__products-ready",

handleProductsReady

)

}

}, [])


// Listen for sort change events

React.useEffect(() => {

const handleSortChange = (e) => {

console.log("Sort changed:", e.detail)

setSortConfig(e.detail)

setSortedChildren(null) // Reset sorted children to trigger resort

}


document.addEventListener("product-sort-change", handleSortChange)

return () => {

document.removeEventListener(

"product-sort-change",

handleSortChange

)

}

}, [])


// Listen for filter change events

React.useEffect(() => {

const handleFilter = (e) => {

console.log("📥 Received filter event:", e.detail)


setFilters((prev) => {

const newFilters = { ...prev }

const { type, group, active, value, filterType } = e.detail


console.log("Filter update received:", {

type,

filterType,

group,

active,

value,

})


// If type is 'all', handle complete reset of a filter type

if (type === "all" && filterType) {

if (filterType === "price" || filterType === "discount") {

newFilters[filterType] = {

active: false,

ranges: [],

}

}

}


// Handle different filter types

switch (type) {

case "market sector":

case "building type":

case "service":

if (active) {

newFilters[type].values = [

...(newFilters[type].values || []),

value,

]

} else {

newFilters[type].values = newFilters[

type

].values.filter((v) => v !== value)

}

newFilters[type].active =

newFilters[type].values.length > 0

break


case "variant":

if (!newFilters[type].values) {

newFilters[type].values = []

}


console.log("Processing variant filter:", {

active,

group,

value,

currentValues: newFilters[type].values,

})


if (active) {

// Add the new variant filter

newFilters[type].values.push({

optionName: value.optionName,

optionValue: value.optionValue,

group: group || undefined,

})

} else {

// Remove the variant filter

if (group) {

// Remove all variants in this group

newFilters[type].values = newFilters[

type

].values.filter((v) => v.group !== group)

} else {

// Remove specific variant by matching name and value

newFilters[type].values = newFilters[

type

].values.filter((v) => {

const valueToMatch = value.value || value

return !(

v.optionName ===

valueToMatch.optionName &&

v.optionValue ===

valueToMatch.optionValue

)

})

}

}


newFilters[type].active =

newFilters[type].values.length > 0


console.log("Variant filter update result:", {

active,

group,

value,

newValues: newFilters[type].values,

isActive: newFilters[type].active,

})

break


case "price":

case "discount":

if (!newFilters[type].ranges) {

newFilters[type].ranges = []

}


console.log("Processing price/discount filter:", {

active,

group,

value,

currentRanges: newFilters[type].ranges,

})


if (active) {

newFilters[type].ranges.push(value)

} else {

// If deselecting a group or the value is false, remove all ranges

if (group || !value) {

console.log(`Removing all ranges for ${type}`)

newFilters[type].ranges = []

} else if (value && value.id) {

// Remove specific range by id if we have one

console.log(

`Removing range with id: ${value.id}`

)

newFilters[type].ranges = newFilters[

type

].ranges.filter((r) => r.id !== value.id)

}

}


// Update active state based on remaining ranges

newFilters[type].active =

newFilters[type].ranges.length > 0


console.log("Updated price/discount filter state:", {

remainingRanges: newFilters[type].ranges,

isActive: newFilters[type].active,

})

break


// Simple boolean filters

case "on-sale":

case "in-stock":

case "bundles":

case "subscriptions":

newFilters[type] = {

active,

value: true,

}

break

}


console.log("📤 Updated filters:", {

type,

active,

group,

newState: newFilters[type],

allFilters: newFilters,

})


return newFilters

})


// Force re-render of filtered items

setSortedChildren(null)

}


document.addEventListener("product-filter-change", handleFilter)

return () =>

document.removeEventListener("product-filter-change", handleFilter)

}, [])


// At the top of your component, add this debug log

React.useEffect(() => {

console.log("Products data structure:", {

totalProducts: products.length,

sampleProduct: products[0]?.node,

allFields: products[0]?.node ? Object.keys(products[0].node) : [],

})

}, [products])


// At the top of your component, add this debug log

React.useEffect(() => {

if (products.length > 0) {

console.log("🔍 Debugging Product Structure:", {

firstProduct: products[0]?.node,

hasSellingPlanGroups:

products[0]?.node?.sellingPlanGroups?.edges?.length > 0,

variants: products[0]?.node?.variants?.edges?.map((edge) => ({

title: edge.node.title,

hasSellingPlans:

edge.node.sellingPlanAllocations?.edges?.length > 0,

sellingPlans: edge.node.sellingPlanAllocations?.edges,

})),

})

}

}, [products])


// Helper to get product details

const getProductDetails = React.useCallback(

(productId) => {

const fullId = `gid://shopify/Product/${productId}`

const product = products.find(

({ node }) => node.id === fullId

)?.node


if (product) {

// Collections are now normalized to an array of collection objects

const collections = Array.isArray(product.collections)

? product.collections.map((c) => c.title)

: []


return {

id: product.id,

title: product.title || "",

price: product.priceRange?.minVariantPrice?.amount

? parseFloat(product.priceRange.minVariantPrice.amount)

: 0,

buildingType: product.buildingType || "",

tags: product.tags || [],

compareAtPrice: product.compareAtPriceRange?.minVariantPrice

?.amount

? parseFloat(

product.compareAtPriceRange.minVariantPrice.amount

)

: 0,

isOnSale: product.compareAtPriceRange?.minVariantPrice

?.amount

? parseFloat(

product.compareAtPriceRange.minVariantPrice.amount

) >

parseFloat(

product.priceRange?.minVariantPrice?.amount || "0"

)

: false,

collections,

options: product.options || [],

variants: product.variants?.edges || [],

sellingPlanGroups: product.sellingPlanGroups?.edges || [],

hasProductLevelPlans:

product.sellingPlanGroups?.edges?.length > 0,

hasVariantLevelPlans: product.variants?.edges?.some(

(edge) =>

edge.node.sellingPlanAllocations?.edges?.length > 0

),

}

}

return {

id: "",

title: "",

price: 0,

buildingType: "",

services: [],

compareAtPrice: 0,

isOnSale: false,

marketSector: [],

options: [],

variants: [],

sellingPlanGroups: [],

hasProductLevelPlans: false,

hasVariantLevelPlans: false,

}

},

[products]

)


// Enhanced helper function to handle transitions

const updateSortedChildrenWithTransition = (newChildren) => {

const now = Date.now()


// If we're getting rapid updates (within 100ms), delay the transition

if (lastUpdateRef.current && now - lastUpdateRef.current < 100) {

setIsSettling(true)

if (settlingTimeoutRef.current) {

clearTimeout(settlingTimeoutRef.current)

}


settlingTimeoutRef.current = setTimeout(() => {

setIsSettling(false)

performTransition(newChildren)

}, 150) // Wait for updates to settle

} else {

performTransition(newChildren)

}


lastUpdateRef.current = now

}


// Helper function to perform the actual transition

const performTransition = (newChildren) => {

if (transitionTimeoutRef.current) {

clearTimeout(transitionTimeoutRef.current)

}


setIsTransitioning(true)


transitionTimeoutRef.current = setTimeout(() => {

setSortedChildren(newChildren)

setTimeout(() => {

setIsTransitioning(false)

}, 200) // Fade in duration

}, 200) // Fade out duration

}


// Cleanup timeouts

React.useEffect(() => {

return () => {

if (transitionTimeoutRef.current) {

clearTimeout(transitionTimeoutRef.current)

}

if (settlingTimeoutRef.current) {

clearTimeout(settlingTimeoutRef.current)

}

}

}, [])


if (!MarketSector?.[0]) {

return <div style={{ height: "100%" }} />

}


const collectionInstance = MarketSector[0]

const emptyStateInstance = props.EmptyState?.[0]


const sizedInstance = React.cloneElement(MarketSectorInstance, {

style: {

...(collectionInstance.props?.style || {}),

width: "100%",

height: "100%",

},

})


if (RenderTarget.current() === RenderTarget.canvas) {

return sizedInstance

}


try {

console.log(

"Products data:",

products.map(({ node }) => ({

id: node.id,

title: node.title,

productType: node.productType,

// Log the entire node to see its structure

node: node,

}))

)


// First, let's add some debug logging to see the full product structure

console.log(

"Full products data:",

products.map(({ node }) => ({

id: node.id,

title: node.title,

type: node.buildingType, // see if this exists

product_type: node.building_type, // try alternative format

__typename: node.__typename, // might help identify the correct field

// Log all keys to see what's available

keys: Object.keys(node),

}))

)


return React.cloneElement(sizedInstance, {

...sizedInstance.props,

children: React.cloneElement(sizedInstance.props.children, {

...sizedInstance.props.children.props,

children: React.cloneElement(

sizedInstance.props.children.props.children,

{

...sizedInstance.props.children.props.children.props,

children: (marketsector) => {

const originalRender =

sizedInstance.props.children.props.children.props.children(

marketsector

)

if (!originalRender?.props?.children)

return originalRender


if (!sortedChildren && products.length > 0) {

const items = originalRender.props.children.map(

(child) => {

const item = marketsector.find(

(marketSectorItem) =>

marketSectorItem.id ===

child.key

)

const productId = item

? findDeep13DigitNumber(item)

: null

const details = productId

? getProductDetails(productId)

: {

title: "",

price: 0,

buildingType: "",

}


console.log("Processing item:", {

title: details.title,

price: details.price,

filters: filters["price-under"],

})


return {

child,

productId: productId || "0",

price: details.price,

title: details.title,

originalIndex:

originalRender.props.children.indexOf(

child

),

}

}

)


console.log(

"Before filtering:",

items.length,

"items"

)


// Apply filters

const filteredItems = items.filter((item) => {

const details = item.productId

? getProductDetails(item.productId)

: null


if (!details) return false


// Collection filter (OR within values)

if (

filters.marketsector.active &&

filters.marketSector.values.length > 0

) {

const matchesAnyCollection =

filters.marketSector.values.some(

(value) =>

details.marketSector?.some(

(marketsector) =>

marketsector

.toLowerCase()

.includes(

value.toLowerCase()

)

)

)

if (!matchesAnyCollection) return false

}


// Product Type filter (OR within values)

if (

filters["building-type"].active &&

filters["building-type"].values.length >

0

) {

const matchesAnyType = filters[

"building-type"

].values.some((value) =>

details.productType

?.toLowerCase()

.includes(value.toLowerCase())

)

if (!matchesAnyType) return false

}


// Product Tag filter (OR within values)

if (

filters["serivce"].active &&

filters["service"].values.length > 0

) {

const matchesAnyTag = filters[

"service"

].values.some((value) =>

details.tags?.some((tag) =>

tag

.toLowerCase()

.includes(

value.toLowerCase()

)

)

)

if (!matchesAnyTag) return false

}


// Price filter (OR within ranges)

if (

filters.price.active &&

filters.price.ranges.length > 0

) {

const matchesAnyRange =

filters.price.ranges.some(

(range) => {

if (

range.type === "Range"

) {

return (

details.price >=

range.low &&

details.price <=

range.high

)

} else {

// Under

return (

details.price <=

range.threshold

)

}

}

)

if (!matchesAnyRange) return false

}


// Discount filter (OR within ranges)

if (

filters.discount.active &&

filters.discount.ranges.length > 0

) {

const matchesAnyRange =

filters.discount.ranges.some(

(range) => {

// Skip if no compare price (not on sale)

if (

!details.compareAtPrice ||

details.compareAtPrice <=

0

) {

return false

}


const discount =

details.compareAtPrice -

details.price

// Skip if no actual discount

if (discount <= 0) {

return false

}


// Round all values to whole numbers to avoid decimal comparison issues

const discountPercentage =

Math.round(

(discount /

details.compareAtPrice) *

100

)

const discountAmount =

Math.round(discount)

const discountValue =

range.isPercentage

? discountPercentage

: discountAmount


console.log(

"🔍 Checking discount filter:",

{

productTitle:

details.title,

originalPrice:

details.compareAtPrice,

currentPrice:

details.price,

calculatedDiscount:

discountAmount,

calculatedPercentage:

discountPercentage +

"%",

usingValue:

discountValue,

usingType:

range.isPercentage

? "percentage"

: "amount",

filterType:

range.type,

filterRange:

range.type ===

"Range"

? `${range.low}-${range.high}`

: `over ${range.threshold}`,

rangeConfig: range,

}

)


let matches = false

if (

range.type === "Range"

) {

// Simple inclusive range check with rounded values

matches =

discountValue >=

Math.round(

range.low

) &&

discountValue <=

Math.round(

range.high

)

console.log(

`Range check for ${details.title}: ${discountValue} ${range.isPercentage ? "%" : "$"} should be between ${Math.round(range.low)} and ${Math.round(range.high)}: ${matches}`

)

} else {

// Over

matches =

discountValue >=

Math.round(

range.threshold

)

console.log(

`Over check for ${details.title}: ${discountValue} ${range.isPercentage ? "%" : "$"} should be over ${Math.round(range.threshold)}: ${matches}`

)

}

return matches

}

)


console.log("Final match result:", {

product: details.title,

matches: matchesAnyRange,

activeRanges:

filters.discount.ranges,

})


if (!matchesAnyRange) return false

}


// Simple boolean filters (no OR logic needed)

if (

filters["on-sale"].active &&

!details.isOnSale

) {

return false

}


if (filters["in-stock"].active) {

const hasInStockVariant =

details.variants.some(

(edge) =>

edge.node.availableForSale

)

if (!hasInStockVariant) return false

}


if (filters.bundles.active) {

const isBundle = details.tags.some(

(tag) =>

tag

.toLowerCase()

.includes("bundle")

)

if (!isBundle) return false

}


if (filters.subscriptions.active) {

if (

!details.hasProductLevelPlans &&

!details.hasVariantLevelPlans

) {

return false

}

}


// Variant filter (OR within values)

if (

filters.variant.active &&

filters.variant.values.length > 0

) {

console.log(

"🔍 Checking variant filter:",

{

productTitle: details.title,

filterValues:

filters.variant.values,

productOptions: details.options,

productVariants:

details.variants,

}

)


const matchesAnyVariant =

filters.variant.values.some(

({

optionName,

optionValue,

group,

}) => {

// Check if the option exists and has the value

const hasMatchingOption =

details.options?.some(

(option) =>

option.name.toLowerCase() ===

optionName.toLowerCase() &&

option.values.some(

(val) =>

val.toLowerCase() ===

optionValue.toLowerCase()

)

)


// Check if that specific variant is in stock

const hasInStockVariant =

details.variants?.some(

(edge) => {

const hasMatchingOption =

edge.node.selectedOptions?.some(

(opt) =>

opt.name.toLowerCase() ===

optionName.toLowerCase() &&

opt.value.toLowerCase() ===

optionValue.toLowerCase()

)

const isInStock =

edge.node

.availableForSale


console.log(

`Variant check for ${details.title}:`,

{

optionName,

optionValue,

hasMatchingOption,

isInStock,

selectedOptions:

edge

.node

.selectedOptions,

}

)


return (

hasMatchingOption &&

isInStock

)

}

)


const matches =

hasMatchingOption &&

hasInStockVariant

console.log(

`Final variant check for ${details.title}:`,

{

optionName,

optionValue,

group,

hasMatchingOption,

hasInStockVariant,

matches,

}

)

return matches

}

)


if (!matchesAnyVariant) {

console.log(

`${details.title} did not match any variants:`,

{

filterValues:

filters.variant.values,

productOptions:

details.options,

productVariants:

details.variants,

}

)

return false

}

}


return true

})


console.log(

"After filtering:",

filteredItems.length,

"items"

)


// Modify the child wrapper styles to include settling state

const getTransitionStyles = (baseStyles) => ({

...baseStyles,

opacity:

isTransitioning || isSettling ? 0 : 1,

transition: `opacity ${isSettling ? "0.3s" : "0.2s"} ease-in-out`,

pointerEvents:

isTransitioning || isSettling

? "none"

: "auto",

})


// Update the empty state wrapper

if (

filteredItems.length === 0 &&

emptyStateInstance

) {

updateSortedChildrenWithTransition([

<div

key="empty-state-wrapper"

style={getTransitionStyles({

width: "100%",

height: "100%",

display: "flex",

justifyContent: "center",

alignItems: "center",

gridColumn: "1 / -1",

})}

>

{React.cloneElement(

emptyStateInstance,

{

style: {

...(emptyStateInstance

.props?.style ||

{}),

width: "100%",

height: "100%",

},

key: "empty-state",

}

)}

</div>,

])

} else {

// Sort the filtered items

const sorted = [...filteredItems].sort(

(a, b) => {

if (

sortConfig.type === "relevancy"

) {

return (

a.originalIndex -

b.originalIndex

)

}


if (sortConfig.sortBy === "price") {

const result =

sortConfig.sortDirection ===

"highToLow"

? b.price - a.price

: a.price - b.price

return (

result ||

a.originalIndex -

b.originalIndex

)

} else {

// sort by name

const result =

sortConfig.sortDirection ===

"aToZ"

? a.title.localeCompare(

b.title

)

: b.title.localeCompare(

a.title

)

return (

result ||

a.originalIndex -

b.originalIndex

)

}

}

)


// Update the product items wrapper

const wrappedChildren = sorted.map((item) =>

React.cloneElement(item.child, {

style: getTransitionStyles(

item.child.props.style

),

})

)


updateSortedChildrenWithTransition(

wrappedChildren

)

}

}


return {

...originalRender,

props: {

...originalRender.props,

children:

sortedChildren ||

originalRender.props.children,

},

}

},

}

),

}),

})

} catch (error) {

console.error("Error in ProductSort:", error)

return sizedInstance

}


// Update URL parameters whenever sortConfig changes

React.useEffect(() => {

if (typeof window !== "undefined") {

const urlParams = new URLSearchParams(window.location.search)

urlParams.set("sortConfig", JSON.stringify(sortConfig))

window.history.replaceState(null, "", "?" + urlParams.toString())

}

}, [sortConfig])


// Update URL parameters whenever filters change

React.useEffect(() => {

if (typeof window !== "undefined") {

const urlParams = new URLSearchParams(window.location.search)

urlParams.set("filters", JSON.stringify(filters))

window.history.replaceState(null, "", "?" + urlParams.toString())

}

}, [filters])

}


addPropertyControls(ProductSort, {

Collection: {

type: ControlType.ComponentInstance,

title: "market sector",

},

EmptyState: {

type: ControlType.ComponentInstance,

title: "Empty State",

},

})