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

I9527 Guide extended #317

Merged
merged 8 commits into from
Jan 29, 2024
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
2 changes: 1 addition & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ const config = {
docs: {
autodocs: 'tag',
},
staticDirs: ['../public', './public'],
staticDirs: ['../public', './public', '../src/docs'],
};
export default config;
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ This library is intended to be included and used within PKP's applications. If y
# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run serve
# serve with hot reload at localhost:6006
npm run storybook
```

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).
## Links

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

## Issues

Expand Down
2,252 changes: 866 additions & 1,386 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@storybook/addon-essentials": "^7.6.5",
"@storybook/addon-interactions": "^7.6.5",
"@storybook/addon-links": "^7.6.5",
"@storybook/addon-mdx-gfm": "^7.6.5",
"@storybook/addon-themes": "^7.6.5",
"@storybook/blocks": "^7.6.5",
"@storybook/addon-essentials": "^7.6.10",
"@storybook/addon-interactions": "^7.6.10",
"@storybook/addon-links": "^7.6.10",
"@storybook/addon-mdx-gfm": "^7.6.10",
"@storybook/addon-themes": "^7.6.10",
"@storybook/blocks": "^7.6.10",
"@storybook/testing-library": "^0.2.2",
"@storybook/vue3": "^7.6.5",
"@storybook/vue3-vite": "^7.6.5",
"@vitejs/plugin-vue": "^4.3.4",
"@storybook/vue3": "^7.6.10",
"@storybook/vue3-vite": "^7.6.10",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^8.0.0",
"autoprefixer": "^10.4.14",
"chromatic": "^9.1.0",
Expand All @@ -68,10 +68,10 @@
"prettier-plugin-tailwindcss": "^0.5.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.6.5",
"storybook": "^7.6.10",
"storybook-mock-date-decorator": "^1.0.1",
"tailwindcss": "3.4",
"vite": "^4.4.9",
"vite": "^5.0.12",
"vitest": "^1.0.4"
},
"postcss": {
Expand All @@ -98,7 +98,7 @@
"*.{js,vue}": [
"eslint --fix "
],
"src/**/*": [
"src/**/*.{js,vue,mdx}": [
"prettier --write"
]
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/Composer/Composer.stories.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {ref} from 'vue';

import Composer from './Composer.vue';
import fileAttachers from '@/docs/data/fileAttachers';
import insertContent from '@/docs/data/insertContent';
import emailTemplate from '@/docs/data/emailTemplate';
import fileAttachers from '@/mocks/fileAttachers';
import insertContent from '@/mocks/insertContent';
import emailTemplate from '@/mocks/emailTemplate';

export default {
title: 'Components/Composer',
Expand Down
4 changes: 2 additions & 2 deletions src/components/FileAttacher/FileAttacher.stories.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {ref} from 'vue';
import {http, HttpResponse, delay} from 'msw';
import FileAttacher from './FileAttacher.vue';
import fileAttachers from '@/docs/data/fileAttachers';
import submissionFiles from '@/docs/data/submissionFiles';
import fileAttachers from '@/mocks/fileAttachers';
import submissionFiles from '@/mocks/submissionFiles';

export default {
title: 'Components/FileAttacher',
Expand Down
2 changes: 1 addition & 1 deletion src/components/FileUploader/FileUploader.stories.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {ref} from 'vue';
import FileUploader from './FileUploader.vue';
import FileUploadProgress from '@/components/FileUploadProgress/FileUploadProgress.vue';
import dropzoneOptions from '@/docs/data/dropzoneOptions';
import dropzoneOptions from '@/mocks/dropzoneOptions';
import {http, HttpResponse} from 'msw';

import './FileUploader.stories.less';
Expand Down
15 changes: 15 additions & 0 deletions src/composables/useApiUrl.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {Meta} from '@storybook/blocks';

<Meta title="Composables/useApiUrl" />

# useApiUrl

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).

```javascript
import {useApiUrl} from '@/composables/useApiUrl';

const {apiUrl: submissionApiUrl} = useApiUrl(
`submissions/${props.submissionId}`,
);
```
4 changes: 2 additions & 2 deletions src/composables/useTranslation.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {t, replaceLocaleParams, localize} from '@/utils/i18n.js';
import {t, localize} from '@/utils/i18n.js';

/** Check detailed documentation in @/utils/i18n.js */
export function useTranslation() {
return {
t,
replaceLocaleParams,
localize,
};
}
67 changes: 67 additions & 0 deletions src/composables/useTranslation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {Meta} from '@storybook/blocks';

<Meta title="Composables/useTranslation" />

# useTranslation

## t - translation

Get translation from the 'po' files, based on currently selected language.

```html
<template>
<div>
<!-- use of translation directly in template -->
{{ t('common.ok') }}
</div>
<div>{{ paginationLabel }}</div>
</template>

<script setup>
import {computed} from 'vue';
import {useTranslation} from '@/composables/useTranslation';

const {t} = useTranslation();

const start = ref(5);
const end = ref(15);
const total = ref(35);

// more complex translation, dependant on state
const paginationLabel = computed(() => {
return t('common.pagination', {
start: start.value,
end: end.value
total: total.value
});
});
</script>
```

## localize

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.

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.

This method mimics the DataObject::getLocalizedData() method from the PHP backend.

```html
<template>
<span>{{ localize(publication.title)}}</span>
</template>

<script setup>
import {defineProps} from 'vue';
import {useTranslation} from '@/composables/useTranslation';

defineProps({
publication: {
type: Object,
required: true,
},
});

const {localize} = useTranslation();
</script>
```
19 changes: 19 additions & 0 deletions src/docs/guide/APIInteractions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/API interactions" />

# API Interactions

OJS/OMP/OPS already have substantial [Rest API interface](https://docs.pkp.sfu.ca/dev/api/ojs/3.4) well covered with documentation.

To interact with the API there are handy composables to simplify the process.

## Compose URL

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.

## Making HTTP requests

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.

For more custom use-cases its possible to use directly underlying [ofetch](https://github.com/unjs/ofetch) package.
45 changes: 45 additions & 0 deletions src/docs/guide/CompositionAPI.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/Vue Composition API" />

# Vue Composition API

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.

## Composition API

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.

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.

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.

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.

## Composables

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.

## How to organise the code

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`.

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.

## Refs vs reactive

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).

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..

```javascript
import {ref} from 'vue';

// defining state
const isLoading = ref(false);
const items = ref([]);

// modifying the state, using the .value
items.value.push({title: 'a'});
isLoading.value = true;
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions src/docs/guide/DesignSystem/Fonts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,61 @@ Some usage example can be checked [here](https://docs.google.com/spreadsheets/d/
<Story of={FontsDisplayStories.Default} />

<br />

# Examples

## text-base-normal

**Usage:** The base font for the application and is used everywhere. Examples are in a non-exhaustive list as it’s the base font.

<img
src="guide/DesignSystem/FontExamples/text-base-normal.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>

<img
src="guide/DesignSystem/FontExamples/text-base-normal-2.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>

## text-base-light

**Usage:** Messages in discussion need to be placed

<img
src="guide/DesignSystem/FontExamples/text-base-light.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>

## text-base-bold

**Usage:** For emphasis e.g. review due date in review pop-up and author name in table along with table healdings

<img
src="guide/DesignSystem/FontExamples/text-base-bold.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>

## text-lg-medium

**Usage:** Text Buttons and primary menu items

<img
src="guide/DesignSystem/FontExamples/text-lg-medium.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>

## text-lg-semibold

**Usage:** Buttons

<img
src="guide/DesignSystem/FontExamples/text-lg-semibold.png"
alt="Font example"
style={{width: '700px', height: 'auto'}}
/>
38 changes: 33 additions & 5 deletions src/docs/guide/PageArchitecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,37 @@ import {Meta} from '@storybook/blocks';

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

- Using [pinia store](https://pinia.vuejs.org) for state management. Check [vue3 guide](..?path=/docs/guide-vue3--docs#pinia-store) for more details.
- Page and page related components are located in `src/pages` folder
- 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.
- For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).
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.

Please checkout [Example Page](../?path=/docs/pages-example-examplepage--docs), which provides well documented boilerplate.
## Folder structure

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.

## Page template included in Page component.

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.

## State management

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.

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.

### Where to place business logic?

- **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.
- **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`.
- **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.

## Server side configuration

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.

This might include things like:

- Form structure configuration, as we do configure forms on PHP side
- Any configuration which needs to be passed just once on page load

## Styling

For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).
Loading