Created
October 29, 2023 04:51
-
-
Save L422Y/75d8adc30dc441742ecdb45f1bd9c4ad to your computer and use it in GitHub Desktop.
ResponsivePanes: Vue 3 Component which takes a "components" property for using different parent and child components based on viewport width
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 type { Component, Ref } from "vue" | |
import { defineComponent, h, onBeforeUnmount, onMounted, ref } from "vue" | |
/** | |
* ResponsivePanes is a component that renders its children in a responsive manner. | |
* | |
* @example | |
* <ResponsivePanes :components="[ | |
* { parent: BAccordion, child: BAccordionItem }, // Mobile/Small (default) | |
* { parent: BTabs, child: BTab, minWidth: 768 }, // Tablet/Medium | |
* { parent: "section", child: "article", minWidth: 1152 } // Desktop/Large | |
* ]"> | |
* | |
*/ | |
/** | |
* A ComponentPair is a pair of components that will be used to render the | |
* children of the ResponsivePanes component. The "parent" component in the pair | |
* will be used as the container component, and the "child" component will be | |
* used to render each child of the ResponsivePanes component. | |
* | |
* The "minWidth" property is optional, and if provided, the pair will be used | |
* when the viewport width is greater than or equal to the specified value. | |
* | |
* The "className" property is optional, and if provided, the class will be | |
* added to the container component. | |
* | |
*/ | |
export type ComponentPair = { | |
parent: Component | string; | |
child: Component | string; | |
minWidth?: number; // optional minWidth for breakpoints | |
className?: string; // Add this line | |
}; | |
export const ResponsivePanes = defineComponent({ | |
props: { | |
components: { | |
type: Array as PropType<ComponentPair[]>, | |
required: true, | |
default: () => [ | |
{ | |
parent: "section", | |
child: "article", | |
}, | |
], | |
}, | |
}, | |
setup(props) { | |
const sortedComponents = [...props.components].sort((a, b) => (b.minWidth || 0) - (a.minWidth || 0)) | |
const containerComponent: Ref<Component | string> = ref(props.components[0].parent) | |
const childComponent: Ref<Component | string> = ref(props.components[0].child) | |
const resizeHandler = () => { | |
const width = window.innerWidth | |
for (const conf of sortedComponents) { | |
if (!conf.minWidth || width >= conf.minWidth) { | |
containerComponent.value = conf.parent | |
childComponent.value = conf.child | |
return // We've found the right configuration; exit the loop | |
} | |
} | |
} | |
const mediaQueryLists: MediaQueryList[] = [] | |
onMounted(() => { | |
if (window.matchMedia) { | |
for (const conf of props.components.slice(1)) { | |
if (conf.minWidth) { | |
const queryList = window.matchMedia(`(min-width: ${conf.minWidth}px)`) | |
mediaQueryLists.push(queryList) | |
queryList.addEventListener("change", (e) => { | |
if (e.matches) { | |
containerComponent.value = conf.parent | |
childComponent.value = conf.child | |
} else { | |
resizeHandler() // Revert to suitable configuration | |
} | |
}) | |
if (queryList.matches) { | |
containerComponent.value = conf.parent | |
childComponent.value = conf.child | |
} | |
} | |
} | |
} else { | |
window.addEventListener("resize", resizeHandler) | |
} | |
resizeHandler() // Initial setup | |
}) | |
onBeforeUnmount(() => { | |
if (window.hasOwnProperty("matchMedia")) { | |
for (const queryList of mediaQueryLists) { | |
queryList.removeEventListener("change", resizeHandler) | |
} | |
} else { | |
window.removeEventListener("resize", resizeHandler) | |
} | |
}) | |
return { | |
containerComponent, | |
childComponent, | |
} | |
}, | |
render() { | |
const children = this.$slots.default ? this.$slots.default() : [] | |
return h( | |
this.containerComponent, | |
{}, | |
children.map((child, index) => { | |
return h( | |
this.childComponent, | |
{ | |
key: index, | |
...child.props || {}, | |
}, | |
child.children || [] | |
) | |
}) | |
) | |
}, | |
}) | |
export default ResponsivePanes |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment