Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add supporting code for Kolibri loaders #448

Merged
merged 4 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Releases are recorded as git tags in the [Github releases](https://github.com/learningequality/kolibri-design-system/releases) page.

## Develop (to become version 1.5.x)

- [#448] - `KCircularLoader`: Renames `show` prop to `shouldShow`
- [#448] - `KCircularLoader`: Adds `disableDefaultTransition` prop
- [#448] - Adds `useKShow` composable
- [#448] - Adds `KTransition` component
- [#426] - Adds `'click'` event to `KTabsList`
- [#425] - Adds `pinned` and `notPinned` icons. Updates `cloud` icon to outline
- [#424] - Adds `laptop` `cloud `and `wifi` icons to KDS
Expand Down Expand Up @@ -38,6 +43,7 @@ Releases are recorded as git tags in the [Github releases](https://github.com/le
[#426]: https://github.com/learningequality/kolibri-design-system/pull/426
[#427]: https://github.com/learningequality/kolibri-design-system/pull/427
[#433]: https://github.com/learningequality/kolibri-design-system/pull/433
[#448]: https://github.com/learningequality/kolibri-design-system/pull/448

## Version 1.4.x

Expand Down
7 changes: 7 additions & 0 deletions docs/pages/kcircularloader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
</ul>
</DocsPageSection>

<DocsPageSection title="Related" anchor="#related">
<ul>
<li>
<code>KCircularLoader</code>'s <code>minVisibleTime</code> isn't sufficient when switching between more components, for example as part of <code>v-if/v-else</code> blocks. In such situations, <DocsInternalLink text="useKShow" href="/usekshow" /> may come handy.
</li>
</ul>
</DocsPageSection>
</DocsPageTemplate>

</template>
79 changes: 79 additions & 0 deletions docs/pages/ktransition.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>

<DocsPageTemplate apiDocs>
<DocsPageSection title="Overview" anchor="#overview">
<p>Exposes predefined set of transitions built on top of Vue's <code>&lt;transition&gt;</code>.</p>
</DocsPageSection>

<DocsPageSection title="Usage" anchor="#usage">
<ul>
<li>Don't forget to define <code>key</code> on child elements of <code>KTransition</code> as described <DocsExternalLink text="in Vue documentation" href="https://v2.vuejs.org/v2/guide/transitions#Transitioning-Between-Elements" />.</li>
</ul>
</DocsPageSection>

<DocsPageSection title="Available transitions" anchor="#overview">
<section>
<h3><code>component-fade-out-in</code></h3>
<p>Suitable when switching between two elements/components.</p>

<DocsShowCode language="html">
<KTransition kind="component-fade-out-in">
<div v-if="isTruthy" key="key-1">
First component
</div>
<div v-else key="key-2">
Second component
</div>
</KTransition>
</DocsShowCode>

<p>Output:</p>
<DocsShow block language="html">
<KTransition kind="component-fade-out-in">
<div v-if="isTruthy" key="key-1">
First component
</div>
<div v-else key="key-2">
Second component
</div>
</KTransition>
</DocsShow>
</section>

</DocsPageSection>

<DocsPageSection title="Related" anchor="#related">
<ul>
<li>
<DocsExternalLink text="Vue's transitions documentation" href="https://v2.vuejs.org/v2/guide/transitions" />
</li>
</ul>
</DocsPageSection>
</DocsPageTemplate>

</template>


<script>

export default {
data() {
return {
isTruthy: true,
intervalId: null,
};
},
mounted() {
this.intervalId = setInterval(() => {
this.isTruthy = !this.isTruthy;
}, 2000);
},
beforeDestroy() {
clearInterval(this.intervalId);
},
};

</script>


<style lang="scss" scoped></style>
196 changes: 196 additions & 0 deletions docs/pages/usekshow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<template>

<DocsPageTemplate apiDocs>

<DocsPageSection title="Overview" anchor="#overview">
<p>A composable that offers the <code>show</code> reactive function. This function guarantees that something will be displayed at least for a specified duration after an initial trigger. This is typically used to prevent a jarring user experience when showing or hiding certain elements. For example, it can be used to ensure that a loader remains visible for a certain amount of time, even when the related data has already been loaded.</p>
</DocsPageSection>

<DocsPageSection title="Usage" anchor="#usage">
<code>show(key, shouldShow, minVisibleTime)</code>

<DocsShowCode language="html">
<div v-if="show('key-1', isLoading, minVisibleTime)">
Loading...
</div>
<div v-else>
Loaded!
</div>
</DocsShowCode>
<!-- eslint-disable -->
<!-- prevent prettier from changing indentation -->
<DocsShowCode language="javascript">
import useKShow from 'kolibri-design-system/lib/composables/useKShow';

export default {
setup() {
const { show } = useKShow();
return { show };
}
};
</DocsShowCode>
<!-- eslint-enable -->
</DocsPageSection>

<DocsPageSection title="Example" anchor="#example">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is excellent <3

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, playing around this was fun, glad that it feels useful

<p>This is a simulation of a typical use-case of showing a loader while fetching data. You can set your own fetch request length and minimum visible time, and then hit the fetch button to see the output.</p>

<DocsShowCode language="html">
<KTransition kind="component-fade-out-in">
<KCircularLoader
v-if="show('key-1', isFetching, minVisibleTime )"
/>
<div v-else>
Loaded!
</div>
</KTransition>
</DocsShowCode>

<div :style="{ marginTop: '24px', display: 'flex' }">
<div>
<span :style="{ marginLeft: '8px' }">Output:</span>
<DocsShow>
<div :style="{ width: '200px', height: '160px', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', padding: '12px 2px 4px 2px' }">
<div>
<KTransition kind="component-fade-out-in">
<KCircularLoader
v-if="show('key-1', isFetching, minVisibleTime )"
key="loader"
disableDefaultTransition
/>
<div v-else key="message" :style="{ textAlign: 'center' }">
Loaded!
</div>
</KTransition>
</div>
<div>
<code>isFetching: {{ isFetching }}</code>
<code>minVisibleTime: {{ minVisibleTime }}</code>
</div>
</div>
</DocsShow>
</div>
<div :style="{ marginTop: '28px', marginLeft: '24px' }">
<span>
<label :style="{ display: 'block' }">Fetch request length (ms)</label>
<input
v-model="fetchingTimeInput"
type="number"
:style="{ display: 'block' }"
>
</span>
<span>
<label :style="{ display: 'block', marginTop: '12px' }">
Minimum visible time (ms)
</label>
<input
v-model="minVisibleTimeInput"
type="number"
:style="{ display: 'block' }"
>
</span>
<KButton :style="{ marginTop: '24px' }" @click="fetchData">
Fetch data
</KButton>
</div>
</div>
</DocsPageSection>

<DocsPageSection title="Related" anchor="#related">
<ul>
<li>
Some components offer a simpler interfance to achieve the same effect when there is no need to be switching between more components. For example, see <DocsInternalLink href="/kcircularloader#prop:minVisibleTime">
KCircularLoader's <code>minVisibleTime</code>
</DocsInternalLink>.
</li>
</ul>
</DocsPageSection>

<DocsPageSection title="Parameters" anchor="#parameters">
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Parameters" and "Returns" would ideally get automatically generated from JSDoc comments of the composable function, similarly how "Props" are generated from components. I believe this would be helpful for the other two composables we have in KDS already. Will open an issue for this as soon as review of this PR is done.

<PropsTable :api="params" />
</DocsPageSection>

<DocsPageSection title="Returns" anchor="#returns">
<p><span style="font-weight: bold">Type:</span> <code>boolean</code></p>
<p><span style="font-weight: bold">Description:</span> Returns <code>true</code> after <code>shouldShow</code> becomes truthy and keeps returning <code>true</code> for the duration of <code>minVisibleTime</code> (even when <code>shouldShow</code> changes back to falsy meanwhile). After <code>minVisibleTime</code> elapses and when <code>shouldShow</code> is falsy already, it returns <code>false</code>.</p>
</DocsPageSection>
</DocsPageTemplate>

</template>


<script>

import { ref } from '@vue/composition-api';
import useKShow from '../../lib/composables/useKShow';
import PropsTable from '../common/DocsPageTemplate/jsdocs/PropsTable';

export default {
components: {
PropsTable,
},
setup() {
const { show } = useKShow();

let timeoutId;
const isFetching = ref(false);

const minVisibleTime = ref(5000);
const minVisibleTimeInput = ref(5000);

const fetchingTime = ref(1000);
const fetchingTimeInput = ref(1000);

function fetchData() {
clearTimeout(timeoutId);

fetchingTime.value = fetchingTimeInput.value;
minVisibleTime.value = minVisibleTimeInput.value;
isFetching.value = true;

timeoutId = setTimeout(function() {
isFetching.value = false;
}, fetchingTime.value);
}

return {
show,
isFetching,
fetchingTime,
minVisibleTime,
fetchingTimeInput,
minVisibleTimeInput,
fetchData,
};
},
data() {
return {
params: [
{
name: 'key',
required: true,
type: { name: 'number|string' },
description:
'Each `show` function instance has to pass a key unique in the context of a whole page to this attribute',
},
{
name: 'shouldShow',
required: false,
default: false,
type: { name: 'boolean' },
description:
'Accurate, real-time information on whether something should be shown. For example, it should be set to `false` for a loader immediately after related data have finished loading.',
},
{
name: 'minVisibleTime',
required: false,
default: 0,
type: { name: 'number' },
description: 'For how long should `show` return `true` after an initial trigger',
},
],
};
},
};

</script>
28 changes: 28 additions & 0 deletions docs/tableOfContents.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const textRelatedKeywords = ['text', 'area', 'field', 'box'];
const layoutRelatedKeywords = ['grid', 'layout', 'container', 'page'];
const responsiveComponentsRelatedKeywords = ['responsive', 'mixin', 'breakpoint'];
const tabsRelatedKeywords = ['tab', 'tabs', 'panel', 'tablist', 'tabpanel'];
const compositionRelatedKeywords = ['composable', 'composition'];

export default [
new Section({
Expand Down Expand Up @@ -170,6 +171,27 @@ export default [
}),
],
}),
new Section({
title: 'Composables',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine that we'd ideally move already existing composables (responsiveWindow and responsiveElement) to this section, rather than having them hidden in the components docs, and also move their code files to the same folder. In that way, we would have consistent imports in products.

I hope having this section above components will help with discoverability of KDS Composition API utilities (see learningequality/kolibri#11132 (comment)) cc @akolson

Can open follow-up.

autoSort: true,
pages: [
new Page({
path: '/usekshow',
title: 'useKShow',
isCode: true,
keywords: [
...compositionRelatedKeywords,
'if',
'show',
'time',
'minimum',
'visible',
'loader',
'loading',
],
}),
],
}),
new Section({
title: 'Code library components',
autoSort: true,
Expand Down Expand Up @@ -359,6 +381,12 @@ export default [
isCode: true,
keywords: tabsRelatedKeywords,
}),
new Page({
path: '/ktransition',
title: 'KTransition',
isCode: true,
keywords: ['transition'],
}),
],
}),
];
2 changes: 2 additions & 0 deletions lib/KThemePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import KTabsList from './tabs/KTabsList';
import KTabsPanel from './tabs/KTabsPanel';
import KTextbox from './KTextbox';
import KTooltip from './KTooltip';
import KTransition from './KTransition';

import { themeTokens, themeBrand, themePalette, themeOutlineStyle } from './styles/theme';
import globalThemeState from './styles/globalThemeState';
Expand Down Expand Up @@ -119,4 +120,5 @@ export default function KThemePlugin(Vue) {
Vue.component('KTabsPanel', KTabsPanel);
Vue.component('KTextbox', KTextbox);
Vue.component('KTooltip', KTooltip);
Vue.component('KTransition', KTransition);
}
Loading