diff --git a/lib/cards/KCardGrid.vue b/lib/cards/KCardGrid.vue index ea8ff563a..ee6777d47 100644 --- a/lib/cards/KCardGrid.vue +++ b/lib/cards/KCardGrid.vue @@ -38,6 +38,10 @@ watch( currentBreakpointConfig, newValue => { + if (!newValue) { + return; + } + const { cardsPerRow, columnGap, rowGap } = newValue; gridStyle.value = { @@ -76,6 +80,17 @@ return [LAYOUT_1_1_1, LAYOUT_1_2_2, LAYOUT_1_2_3].includes(value); }, }, + // eslint-enable-next-line kolibri/vue-no-unused-properties + /** + * Overrides the base grid `layout` for chosen breakpoints levels + */ + // eslint-disable-next-line kolibri/vue-no-unused-properties + layoutOverride: { + type: Object, + required: false, + default: null, + }, + // eslint-enable-next-line kolibri/vue-no-unused-properties }, }; diff --git a/lib/cards/useResponsiveGridLayout.js b/lib/cards/useResponsiveGridLayout.js index a1bc3dc81..69fa56cc9 100644 --- a/lib/cards/useResponsiveGridLayout.js +++ b/lib/cards/useResponsiveGridLayout.js @@ -1,3 +1,4 @@ +import cloneDeep from 'lodash/cloneDeep'; import { watch, ref } from '@vue/composition-api'; import useKResponsiveWindow from '../composables/useKResponsiveWindow'; @@ -14,8 +15,12 @@ export default function useResponsiveGridLayout(props) { const { windowBreakpoint } = useKResponsiveWindow(); function getBreakpointConfig(config, breakpoint) { - const breakpointConfig = config.find(subConfig => subConfig.breakpoints.includes(breakpoint)); - return breakpointConfig; + if (!config || !config.length) { + return undefined; + } + return config.find( + subConfig => subConfig.breakpoints && subConfig.breakpoints.includes(breakpoint) + ); } /** @@ -27,21 +32,37 @@ export default function useResponsiveGridLayout(props) { * for the `breakpoint` */ function getLayoutConfigForBreakpoint(props, breakpoint) { + if (breakpoint === null || breakpoint === undefined) { + return getLayoutConfigForBreakpoint(props, 0); + } const baseLayoutConfig = LAYOUT_CONFIGS[props.layout]; const baseBreakpointConfig = getBreakpointConfig(baseLayoutConfig, breakpoint); - return { ...baseBreakpointConfig }; + + // Deep clone to protect mutating LAYOUT_CONFIGS + const finalBreakpointConfig = cloneDeep(baseBreakpointConfig); + + // Remove `breakpoints` attribute as it's not needed + delete finalBreakpointConfig.breakpoints; + + // Override if `layoutOverride` contains + // settings for the current breakpoint level + const breakpointOverride = getBreakpointConfig(props.layoutOverride, breakpoint); + if (breakpointOverride) { + for (const key of ['cardsPerRow', 'columnGap', 'rowGap']) { + if (breakpointOverride[key]) { + finalBreakpointConfig[key] = breakpointOverride[key]; + } + } + } + + return baseBreakpointConfig; } + // TODO: reactive layoutOverride watch( windowBreakpoint, - (newBreakpoint, oldBreakpoint) => { - // can happen very briefly before the breakpoint value gets calculated - if (newBreakpoint === null) { - currentBreakpointConfig.value = getLayoutConfigForBreakpoint(props, 0); - } - if (newBreakpoint !== oldBreakpoint) { - currentBreakpointConfig.value = getLayoutConfigForBreakpoint(props, newBreakpoint); - } + newBreakpoint => { + currentBreakpointConfig.value = getLayoutConfigForBreakpoint(props, newBreakpoint); }, { immediate: true } );