Skip to content

Commit

Permalink
Merge pull request learningequality#377 from akolson/implement-kRespo…
Browse files Browse the repository at this point in the history
…nsiveWindow-composable

Implement k responsive window composable
  • Loading branch information
akolson authored Oct 18, 2022
2 parents 115a19d + 84ef450 commit 29122c4
Show file tree
Hide file tree
Showing 11 changed files with 683 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ Releases are recorded as git tags in the [Github releases](https://github.com/le
- [#346] - `KButton`: The default slot doesn't take precedence over `text` prop anymore (the default slot content will be rendered above `text` if provided).
- [#361] - `KButton` exposes `hasDropdown` prop which will show the dropdown icon in a button.
- [#361] - Fixes 'Property or method "disabled" is not defined on the instance but referenced during render.' raised by `KDropdownMenu`
- [#377] - Implement `useKResponsiveWindow` composable.

<!-- Referenced PRs -->
[#351]: https://github.com/learningequality/kolibri-design-system/pull/351
[#355]: https://github.com/learningequality/kolibri-design-system/pull/355
[#346]: https://github.com/learningequality/kolibri-design-system/pull/346
[#361]: https://github.com/learningequality/kolibri-design-system/pull/361
[#377]: https://github.com/learningequality/kolibri-design-system/pull/377

## Version 1.4.x

Expand Down
137 changes: 118 additions & 19 deletions docs/pages/kresponsivewindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,44 +42,129 @@
<p>
Provided reactive properties can then be accessed on the component instance via <code>this</code>.
</p>

<h3>As composable</h3>
<p>It can be imported as <code>useKResponsiveWindow</code>. Register it in the script part of a component file:</p>

<!-- eslint-disable -->
<!-- prevent prettier from changing indentation -->
<DocsShowCode language="javascript">
import useKResponsiveWindow from 'kolibri-design-system/lib/useKResponsiveWindow';

export default {
setup() {
const {
windowWidth,
windowIsLarge,
windowBreakpoint,
...
} = useKResponsiveWindow();

//Peform some operations with the properties

return {
windowWidth,
windowIsLarge,
windowBreakpoint,
...
};
}
};
</DocsShowCode>
<!-- eslint-enable -->

<p><code>useKResponsiveWindow()</code> returns an object of reactive properties, that you could destruture.</p>


<h3>Mixin vs composable</h3>
<p>Mixins come with several drawbacks including namespace collision, tight coupling, and dificulties associated with developer debugging. It is recommended that composables are used instead as they attemp to resolve these drawbacks. Benefits among others, include better logic reusability and better code organization.</p>
</DocsPageSection>

<DocsPageSection title="Example" anchor="#example">
<DocsPageSection title="Mixin Example" anchor="#mixin-example">
<p>
Consider a Vue file with this in its template and script:
</p>
<!-- eslint-disable -->
<!-- prevent prettier from changing indentation -->
<DocsShowCode language="html">
<!-- prevent prettier from changing indentation -->
<DocsShowCode language="html">
<div class="box" :style="boxStyle">
Box 1
</div>
<div class="box" :style="boxStyle">
Box 2
</div>
</DocsShowCode>
<DocsShowCode language="javascript">
computed: {
boxStyle() {
if (this.windowIsLarge) {
return { display: 'inline-block' };
}
return { display: 'block' };
},
},
</DocsShowCode>
<!-- eslint-enable -->
<p>
This results in two boxes that stack vertically on small screens and otherwise display side-by-side:
</p>
<DocsShow>
<div>Breakpoint level: {{ windowBreakpoint }}</div>
<div>Window is large: {{ windowIsLarge }}</div>
<div>
<div class="box" :style="boxStyle">
Box 1
</div>
<div class="box" :style="boxStyle">
Box 2
</div>
</DocsShowCode>
<DocsShowCode language="javascript">
computed: {
boxStyle() {
if (this.windowIsLarge) {
return { display: 'inline-block' };
}
return { display: 'block' };
},
},
</DocsShowCode>
<!-- eslint-enable -->
</div>
</DocsShow>
<p>
Try adjusting your browser window size to see the example in action.
</p>
</DocsPageSection>

<DocsPageSection title="Composable Example" anchor="#composable-example">
<p>
Consider a Vue file with this in its template and script:
</p>
<!-- eslint-disable -->
<!-- prevent prettier from changing indentation -->
<DocsShowCode language="html">
<div class="box" :style="boxStyle">
Box 1
</div>
<div class="box" :style="boxStyle">
Box 2
</div>
</DocsShowCode>
<DocsShowCode language="javascript">
setup() {
...

const boxStyle = computed(function () {
return { display: windowIsLarge.value ? 'inline-block' : 'block' };
});

return {
...,
boxStyle,
};
}
</DocsShowCode>
<!-- eslint-enable -->
<p>
This results in two boxes that stack vertically on small screens and otherwise display side-by-side:
</p>
<DocsShow>
<div>Breakpoint level: {{ windowBreakpoint }}</div>
<div>Window is large: {{ windowIsLarge }}</div>
<div>Breakpoint level: {{ composableWindowBreakpoint }}</div>
<div>Window is large: {{ composableWindowIsLarge }}</div>
<div>
<div class="box" :style="boxStyle">
<div class="box" :style="composableBoxStyle">
Box 1
</div>
<div class="box" :style="boxStyle">
<div class="box" :style="composableBoxStyle">
Box 2
</div>
</div>
Expand All @@ -106,10 +191,24 @@

<script>
import { computed } from '@vue/composition-api';
import responsiveWindowMixin from '~~/lib/KResponsiveWindowMixin.js';
import useKResponsiveWindow from '~~/lib/useKResponsiveWindow';
export default {
mixins: [responsiveWindowMixin],
setup() {
const {
windowBreakpoint: composableWindowBreakpoint,
windowIsLarge: composableWindowIsLarge,
} = useKResponsiveWindow();
const composableBoxStyle = computed(() => {
return { display: composableWindowIsLarge.value ? 'inline-block' : 'block' };
});
return { composableWindowBreakpoint, composableWindowIsLarge, composableBoxStyle };
},
computed: {
boxStyle() {
return { display: this.windowIsLarge ? 'inline-block' : 'block' };
Expand Down
2 changes: 2 additions & 0 deletions docs/plugins/load-lib-components.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import KThemePlugin from '~~/lib/KThemePlugin';
import trackInputModality from '~~/lib/styles/trackInputModality';

Expand All @@ -7,4 +8,5 @@ import trackInputModality from '~~/lib/styles/trackInputModality';
// See `KThemePlugin` `$coreOutline` and `globalThemeState.inputModality`
trackInputModality({ disableFocusRingByDefault: false });

Vue.use(VueCompositionAPI);
Vue.use(KThemePlugin);
37 changes: 37 additions & 0 deletions lib/__tests__/useKWindowDimensions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { defineComponent } from '@vue/composition-api';
import { mount } from '@vue/test-utils';
import useKWindowDimensions from '../useKWindowDimensions';

const resizeWindow = (width, height) => {
window.innerWidth = width;
window.innerHeight = height;
window.dispatchEvent(new Event('resize'));
};

describe('useKWindowDimensions composable', () => {
let TestComponent;
beforeAll(() => {
TestComponent = defineComponent({
setup() {
return {
...useKWindowDimensions(),
};
},
});
});

it('check if windowWidth and windowHeight properties are initialized on mount', () => {
const wrapper = mount(TestComponent);
expect(wrapper.vm.windowWidth).toEqual(expect.any(Number));
expect(wrapper.vm.windowHeight).toEqual(expect.any(Number));
});

it('check if windowWidth and windowHeight change when window is resized', () => {
const wrapper = mount(TestComponent);
expect(wrapper.vm.windowWidth).not.toEqual(600);
expect(wrapper.vm.windowHeight).not.toEqual(400);
resizeWindow(600, 400);
expect(wrapper.vm.windowWidth).toEqual(600);
expect(wrapper.vm.windowHeight).toEqual(400);
});
});
4 changes: 4 additions & 0 deletions lib/composition-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';

Vue.use(VueCompositionAPI);
57 changes: 57 additions & 0 deletions lib/useKResponsiveWindow/MediaQuery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Class representing a MediaQuery
*/
export default class MediaQuery {
/**
*Create a media query
* @param {String} query - The query string
* @param {CallableFunction} eventHandler - The event callback function
*/
constructor(query, eventHandler) {
this.query = query;
this.eventHandler = eventHandler;
}

/**
* @returns {Object} Media query list
*/
get mediaQueryList() {
return window.matchMedia(this.query);
}

/**
* Check if Nuxt is server side rendering
* @returns {Boolean}
*/
isNuxtServerSideRendering() {
return process && process.server;
}

/**
* Start listening for media query events
* @returns {Object} Containing mediaQueryList, eventHandler, and stopListening
*/
startListening() {
//Prevent function execution if Nuxt is server side rendering
if (this.isNuxtServerSideRendering()) {
return;
}

if (this.mediaQueryList.addEventListener) {
this.mediaQueryList.addEventListener('change', this.eventHandler);
} else {
this.mediaQueryList.addListener(this.eventHandler);
}
}

/**
* Stop listening for media query events
*/
stopListening() {
if (this.mediaQueryList.removeEventListener) {
this.mediaQueryList.removeEventListener('change', this.eventHandler);
} else {
this.mediaQueryList.removeListener(this.eventHandler);
}
}
}
Loading

0 comments on commit 29122c4

Please sign in to comment.