Skip to content

Commit 76cccda

Browse files
I9527 Storybook guide extended (#317)
* pkp/pkp-lib#9527 Add translation related documentation and examples * pkp/pkp-lib#9527 Use prettier only for js/vue/mdx * pkp/pkp-lib#9527 Add available font usage examples * pkp/pkp-lib#9527 Update Readme to include storybook links * pkp/pkp-lib#9527 migrate remaining stories from /docs/data to /mocks * pkp/pkp-lib#9527 Documentation WIP * pkp/pkp-lib#9527 Documentation WIP * pkp/pkp-lib#9527 Update vite dependency & docs
1 parent 4015e95 commit 76cccda

30 files changed

+1337
-1489
lines changed

.storybook/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ const config = {
1515
docs: {
1616
autodocs: 'tag',
1717
},
18-
staticDirs: ['../public', './public'],
18+
staticDirs: ['../public', './public', '../src/docs'],
1919
};
2020
export default config;

README.md

+5-6
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ This library is intended to be included and used within PKP's applications. If y
1616
# install dependencies
1717
npm install
1818

19-
# serve with hot reload at localhost:8080
20-
npm run serve
19+
# serve with hot reload at localhost:6006
20+
npm run storybook
2121
```
2222

23-
Run the following command to build the library with a base path of `/dev/ui-library/<version>`, so it can be included in [PKP's documentation hub](https://github.com/pkp/pkp-docs).
23+
## Links
2424

25-
```bash
26-
PKP_DOCS_VERSION=<version> npm run build
27-
```
25+
- [Latest storybook build](https://main--6555d3db80418bb1681b8b17.chromatic.com/)
26+
- [Latest snapshots](https://www.chromatic.com/library?appId=6555d3db80418bb1681b8b17&branch=main) captured via Chromatic service for visual testing of our components and pages
2827

2928
## Issues
3029

package-lock.json

+866-1,386
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+12-12
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@
4242
},
4343
"devDependencies": {
4444
"@rushstack/eslint-patch": "^1.3.3",
45-
"@storybook/addon-essentials": "^7.6.5",
46-
"@storybook/addon-interactions": "^7.6.5",
47-
"@storybook/addon-links": "^7.6.5",
48-
"@storybook/addon-mdx-gfm": "^7.6.5",
49-
"@storybook/addon-themes": "^7.6.5",
50-
"@storybook/blocks": "^7.6.5",
45+
"@storybook/addon-essentials": "^7.6.10",
46+
"@storybook/addon-interactions": "^7.6.10",
47+
"@storybook/addon-links": "^7.6.10",
48+
"@storybook/addon-mdx-gfm": "^7.6.10",
49+
"@storybook/addon-themes": "^7.6.10",
50+
"@storybook/blocks": "^7.6.10",
5151
"@storybook/testing-library": "^0.2.2",
52-
"@storybook/vue3": "^7.6.5",
53-
"@storybook/vue3-vite": "^7.6.5",
54-
"@vitejs/plugin-vue": "^4.3.4",
52+
"@storybook/vue3": "^7.6.10",
53+
"@storybook/vue3-vite": "^7.6.10",
54+
"@vitejs/plugin-vue": "^5.0.3",
5555
"@vue/eslint-config-prettier": "^8.0.0",
5656
"autoprefixer": "^10.4.14",
5757
"chromatic": "^9.1.0",
@@ -68,10 +68,10 @@
6868
"prettier-plugin-tailwindcss": "^0.5.11",
6969
"react": "^18.2.0",
7070
"react-dom": "^18.2.0",
71-
"storybook": "^7.6.5",
71+
"storybook": "^7.6.10",
7272
"storybook-mock-date-decorator": "^1.0.1",
7373
"tailwindcss": "3.4",
74-
"vite": "^4.4.9",
74+
"vite": "^5.0.12",
7575
"vitest": "^1.0.4"
7676
},
7777
"postcss": {
@@ -98,7 +98,7 @@
9898
"*.{js,vue}": [
9999
"eslint --fix "
100100
],
101-
"src/**/*": [
101+
"src/**/*.{js,vue,mdx}": [
102102
"prettier --write"
103103
]
104104
},

src/components/Composer/Composer.stories.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {ref} from 'vue';
22

33
import Composer from './Composer.vue';
4-
import fileAttachers from '@/docs/data/fileAttachers';
5-
import insertContent from '@/docs/data/insertContent';
6-
import emailTemplate from '@/docs/data/emailTemplate';
4+
import fileAttachers from '@/mocks/fileAttachers';
5+
import insertContent from '@/mocks/insertContent';
6+
import emailTemplate from '@/mocks/emailTemplate';
77

88
export default {
99
title: 'Components/Composer',

src/components/FileAttacher/FileAttacher.stories.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {ref} from 'vue';
22
import {http, HttpResponse, delay} from 'msw';
33
import FileAttacher from './FileAttacher.vue';
4-
import fileAttachers from '@/docs/data/fileAttachers';
5-
import submissionFiles from '@/docs/data/submissionFiles';
4+
import fileAttachers from '@/mocks/fileAttachers';
5+
import submissionFiles from '@/mocks/submissionFiles';
66

77
export default {
88
title: 'Components/FileAttacher',

src/components/FileUploader/FileUploader.stories.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {ref} from 'vue';
22
import FileUploader from './FileUploader.vue';
33
import FileUploadProgress from '@/components/FileUploadProgress/FileUploadProgress.vue';
4-
import dropzoneOptions from '@/docs/data/dropzoneOptions';
4+
import dropzoneOptions from '@/mocks/dropzoneOptions';
55
import {http, HttpResponse} from 'msw';
66

77
import './FileUploader.stories.less';

src/composables/useApiUrl.mdx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {Meta} from '@storybook/blocks';
2+
3+
<Meta title="Composables/useApiUrl" />
4+
5+
# useApiUrl
6+
7+
Simple composable to generate API URL. It correctly generates API URL based on currect context (Journal) selected. It does not cover adding query params, as query params are passed separately when making the request with [useFetch](?path=/docs/composables-usefetch--docs).
8+
9+
```javascript
10+
import {useApiUrl} from '@/composables/useApiUrl';
11+
12+
const {apiUrl: submissionApiUrl} = useApiUrl(
13+
`submissions/${props.submissionId}`,
14+
);
15+
```

src/composables/useTranslation.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {t, replaceLocaleParams, localize} from '@/utils/i18n.js';
1+
import {t, localize} from '@/utils/i18n.js';
22

3+
/** Check detailed documentation in @/utils/i18n.js */
34
export function useTranslation() {
45
return {
56
t,
6-
replaceLocaleParams,
77
localize,
88
};
99
}

src/composables/useTranslation.mdx

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {Meta} from '@storybook/blocks';
2+
3+
<Meta title="Composables/useTranslation" />
4+
5+
# useTranslation
6+
7+
## t - translation
8+
9+
Get translation from the 'po' files, based on currently selected language.
10+
11+
```html
12+
<template>
13+
<div>
14+
<!-- use of translation directly in template -->
15+
{{ t('common.ok') }}
16+
</div>
17+
<div>{{ paginationLabel }}</div>
18+
</template>
19+
20+
<script setup>
21+
import {computed} from 'vue';
22+
import {useTranslation} from '@/composables/useTranslation';
23+
24+
const {t} = useTranslation();
25+
26+
const start = ref(5);
27+
const end = ref(15);
28+
const total = ref(35);
29+
30+
// more complex translation, dependant on state
31+
const paginationLabel = computed(() => {
32+
return t('common.pagination', {
33+
start: start.value,
34+
end: end.value
35+
total: total.value
36+
});
37+
});
38+
</script>
39+
```
40+
41+
## localize
42+
43+
Most metadata has support for multiple languages. Therefore if there is need to display given metadata based on currently selected locale. `localize` function can be used.
44+
45+
It will search for the current locale value. If there's no value for the current locale, it will revert to the primary locale. If there's still no match, it will return the first available value or an empty string.
46+
47+
This method mimics the DataObject::getLocalizedData() method from the PHP backend.
48+
49+
```html
50+
<template>
51+
<span>{{ localize(publication.title)}}</span>
52+
</template>
53+
54+
<script setup>
55+
import {defineProps} from 'vue';
56+
import {useTranslation} from '@/composables/useTranslation';
57+
58+
defineProps({
59+
publication: {
60+
type: Object,
61+
required: true,
62+
},
63+
});
64+
65+
const {localize} = useTranslation();
66+
</script>
67+
```

src/docs/guide/APIInteractions.mdx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {Meta} from '@storybook/blocks';
2+
3+
<Meta title="Guide/API interactions" />
4+
5+
# API Interactions
6+
7+
OJS/OMP/OPS already have substantial [Rest API interface](https://docs.pkp.sfu.ca/dev/api/ojs/3.4) well covered with documentation.
8+
9+
To interact with the API there are handy composables to simplify the process.
10+
11+
## Compose URL
12+
13+
Once you know which API endpoint you want to interact with, its necessary to create correct API URL based on current context. Use [useApiUrl](?path=/docs/composables-useapiurl--docs) composable to achieve that.
14+
15+
## Making HTTP requests
16+
17+
To make HTTP request to the API, best is use dedicated [useFetch](?path=/docs/composables-usefetch--docs) or [useFetchPaginated](?path=/docs/composables-usefetchpaginated--docs) composables. Refer to their documentation for examples.
18+
19+
For more custom use-cases its possible to use directly underlying [ofetch](https://github.com/unjs/ofetch) package.

src/docs/guide/CompositionAPI.mdx

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {Meta} from '@storybook/blocks';
2+
3+
<Meta title="Guide/Vue Composition API" />
4+
5+
# Vue Composition API
6+
7+
In version 3.5 we migrated our ui-library to new Vue3 version, which introduces new [Composition API](https://vuejs.org/guide/introduction.html#composition-api) as more flexible alternative to existing Options API.
8+
9+
## Composition API
10+
11+
Composition API builds on exactly same principles as the Options API that we are used to from Vue 2. State management still consist of **state** , **computed** properties and **methods** that are updating the **state**. Only the syntax is different.
12+
13+
It might take bit getting used to to new syntax, but it will reward us is with significantly better flexibility compared to mixins, which comes very handy in complex application like OJS/OPS/OMP.
14+
15+
Best learning source is Vue.js [documentation](https://vuejs.org/guide/introduction.html), where you can easily explore syntax differences between options API and composition API.
16+
17+
We follow Vue.js [recommendation](https://vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive) to use exclusively `ref` to define reactive state and avoiding `reactive` API due its limits.
18+
19+
## Composables
20+
21+
Composables are replacements for mixins. More details to come about the composables that we provide instead of existing mixins. In general these will be useful to handle all common tasks, like interacting with API, translation or manupulating forms.
22+
23+
## How to organise the code
24+
25+
Even though it seems that Composition API is lacking organizational structure, it actually gives possibility of organise the code by individual features. Every feature usually consist of some state, computed properties and methods. Good example is `SubmissionsPageStore.js`.
26+
27+
Also it should be easy to quickly understand how the Page works when reading the business logic in store. If its too long or/and contains long functions its indication that some of the features should be moved to individual feature composables. These than can be imported to the store and connected with rest of the features in the store. This helps to get good understanding how things works together and its always possible to dive in into details in individual composables if needed.
28+
29+
## Refs vs reactive
30+
31+
If you want to dive deep and understand all details between using `ref()` vs `reactive()` when creating reactive state in Composable API, you can check out [official documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals) or detailed [blog post](https://mokkapps.de/blog/ref-vs-reactive-what-to-choose-using-vue-3-composition-api).
32+
33+
But its not really necessary, as we follow [official recommendation](https://vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive), which aligns with my personal experience as well, which is just always use `ref()` when defining state..
34+
35+
```javascript
36+
import {ref} from 'vue';
37+
38+
// defining state
39+
const isLoading = ref(false);
40+
const items = ref([]);
41+
42+
// modifying the state, using the .value
43+
items.value.push({title: 'a'});
44+
isLoading.value = true;
45+
```
Loading
Loading
Loading
Loading
Loading
Loading

src/docs/guide/DesignSystem/Fonts.mdx

+58
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,61 @@ Some usage example can be checked [here](https://docs.google.com/spreadsheets/d/
1111
<Story of={FontsDisplayStories.Default} />
1212

1313
<br />
14+
15+
# Examples
16+
17+
## text-base-normal
18+
19+
**Usage:** The base font for the application and is used everywhere. Examples are in a non-exhaustive list as it’s the base font.
20+
21+
<img
22+
src="guide/DesignSystem/FontExamples/text-base-normal.png"
23+
alt="Font example"
24+
style={{width: '700px', height: 'auto'}}
25+
/>
26+
27+
<img
28+
src="guide/DesignSystem/FontExamples/text-base-normal-2.png"
29+
alt="Font example"
30+
style={{width: '700px', height: 'auto'}}
31+
/>
32+
33+
## text-base-light
34+
35+
**Usage:** Messages in discussion need to be placed
36+
37+
<img
38+
src="guide/DesignSystem/FontExamples/text-base-light.png"
39+
alt="Font example"
40+
style={{width: '700px', height: 'auto'}}
41+
/>
42+
43+
## text-base-bold
44+
45+
**Usage:** For emphasis e.g. review due date in review pop-up and author name in table along with table healdings
46+
47+
<img
48+
src="guide/DesignSystem/FontExamples/text-base-bold.png"
49+
alt="Font example"
50+
style={{width: '700px', height: 'auto'}}
51+
/>
52+
53+
## text-lg-medium
54+
55+
**Usage:** Text Buttons and primary menu items
56+
57+
<img
58+
src="guide/DesignSystem/FontExamples/text-lg-medium.png"
59+
alt="Font example"
60+
style={{width: '700px', height: 'auto'}}
61+
/>
62+
63+
## text-lg-semibold
64+
65+
**Usage:** Buttons
66+
67+
<img
68+
src="guide/DesignSystem/FontExamples/text-lg-semibold.png"
69+
alt="Font example"
70+
style={{width: '700px', height: 'auto'}}
71+
/>

src/docs/guide/PageArchitecture.mdx

+33-5
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,37 @@ import {Meta} from '@storybook/blocks';
66

77
These principles applies for version 3.5 and higher when building new pages or making significant changes to existing ones.
88

9-
- Using [pinia store](https://pinia.vuejs.org) for state management. Check [vue3 guide](..?path=/docs/guide-vue3--docs#pinia-store) for more details.
10-
- Page and page related components are located in `src/pages` folder
11-
- Not using Smarty template for page itself, template is no part of the vue.js page component. Check out [this guide](../?path=/docs/guide-smarty-vs-vue-js--docs) for more details.
12-
- For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).
9+
Since its recent change, we have limited number of examples in our code base. At this point best to follow [Example Page](?path=/docs/pages-example--docs) and [New Submission Page](?path=/docs/pages-submissions--docs). There is more to come soon.
1310

14-
Please checkout [Example Page](../?path=/docs/pages-example-examplepage--docs), which provides well documented boilerplate.
11+
## Folder structure
12+
13+
All Page related files are localed in `lib/ui-library/src/pages` folder. Check out [ExamplePage](?path=/docs/pages-example--docs#folder-structure) for per file breakdown.
14+
15+
## Page template included in Page component.
16+
17+
To [simplify](?path=/docs/guide-technical-roadmap--docs#vuejs--smarty---vuejs-35) the developer experience when creating Pages, we are moving from combination of Smarty and Vue.js to use pure Vue.js components, which have templates as part of the component file.
18+
19+
## State management
20+
21+
For managing Page business logic we are leveraging Pinia store. Main reason why we want to use Pinia stores instead of just managing state directly in the Page component, is that behind the scene its very extensible and plugin friendly. You can check out more detailed break down in our [Technical Roadmap](?path=/docs/guide-technical-roadmap--docs#pinia-stores-35). General UI components still contains interaction logic directly in component.
22+
23+
To understand how the Components should interact with the **Component Pinia Store**, which is our simple extension of the Pinia Store, check out dedicated [page](http://localhost:6006/?path=/docs/guide-pinia-store--docs#component-pinia-store) for Pinia store.
24+
25+
### Where to place business logic?
26+
27+
- **Page**: Every Page will have its own [Component Pinia Store](?path=/docs/guide-pinia-store--docs#component-pinia-store), where most of the business logic is handled and for simpler Pages that should be good enough.
28+
- **Complex Modals**: If Page contain Modal(s) that contain substantial functionality, which is reasonably well separable from the rest of the Page, than its good indication that the Modal should have its own Component Store. Good example is `SubmissionSummaryModal.vue`.
29+
- **Self-Contained Component**: We don't have good example yet of Self-Contained Component, which would have its own Pinia store. But good candidates in general are Components that can be used on multiple places, and their logic can be well separated from the rest of the page. Like managing Submission files in both Submission wizard and Workflow editor.
30+
31+
## Server side configuration
32+
33+
On initial page load, there is still opportunity to pass JS object from PHP to the Vue.js. Best is to express individual items as props, so it can be easily displayed in storybook and its well documented.
34+
35+
This might include things like:
36+
37+
- Form structure configuration, as we do configure forms on PHP side
38+
- Any configuration which needs to be passed just once on page load
39+
40+
## Styling
41+
42+
For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).

0 commit comments

Comments
 (0)