diff --git a/.editorconfig b/.editorconfig index 90aa6e045..ad6afd1f6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ trim_trailing_whitespace = true [*.md] indent_size = unset -[*.{html,js,md}] +[*.{html,js,md,mdx}] block_comment_start = /** block_comment = * block_comment_end = */ diff --git a/.storybook/build-storybook.config.js b/.storybook/build-storybook.config.js new file mode 100644 index 000000000..aae588bba --- /dev/null +++ b/.storybook/build-storybook.config.js @@ -0,0 +1,3 @@ +module.exports = { + stories: './packages/**/stories/*.stories.{js,mdx}', +}; diff --git a/.storybook/preview.js b/.storybook/preview.js index ae3815dd5..e5e9a38c0 100755 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -4,6 +4,7 @@ import { setCustomElements, withA11y, } from '@open-wc/demoing-storybook'; +import { sortEachDepth } from '../packages/helpers/index.js'; async function run() { // const customElements = await ( @@ -24,6 +25,14 @@ async function run() { docs: { iframeHeight: '200px', }, + options: { + showRoots: true, + storySort: sortEachDepth([ + ['Intro', 'Forms', 'Buttons', 'Overlays', 'Navigation', 'Localize', 'Icons', '...'], + ['Intro', 'Features Overview', '...', 'System'], + ['Overview', '...', '_internals'], + ]), + }, }); } diff --git a/.storybook/start-storybook.config.js b/.storybook/start-storybook.config.js new file mode 100644 index 000000000..d0261520d --- /dev/null +++ b/.storybook/start-storybook.config.js @@ -0,0 +1,6 @@ +module.exports = { + nodeResolve: true, + watch: true, + open: true, + stories: './packages/**/stories/*.stories.{js,mdx}', +}; diff --git a/README.md b/README.md index 9b2dd1d00..c82e51f81 100644 --- a/README.md +++ b/README.md @@ -29,39 +29,43 @@ npm i @lion/ The accessibility column indicates whether the functionality is accessible in its core. Aspects like styling and content determine actual accessibility in usage. -| Package | Version | Description | Accessibility | -| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------- | -| [core](./packages/core) | [![core](https://img.shields.io/npm/v/@lion/core.svg)](https://www.npmjs.com/package/@lion/core) | Core System (exports LitElement, lit-html) | n/a | -| [localize](./packages/localize) | [![localize](https://img.shields.io/npm/v/@lion/localize.svg)](https://www.npmjs.com/package/@lion/localize) | Localize and translate your application/components | n/a | -| [ajax](./packages/ajax) | [![ajax](https://img.shields.io/npm/v/@lion/ajax.svg)](https://www.npmjs.com/package/@lion/ajax) | Fetching data via ajax request | n/a | -| [button](./packages/button) | [![button](https://img.shields.io/npm/v/@lion/button.svg)](https://www.npmjs.com/package/@lion/button) | Button | [#64][i64] | -| [switch](./packages/switch) | [![switch](https://img.shields.io/npm/v/@lion/switch.svg)](https://www.npmjs.com/package/@lion/switch) | Switch | ✔️ | -| [calendar](./packages/calendar) | [![calendar](https://img.shields.io/npm/v/@lion/calendar.svg)](https://www.npmjs.com/package/@lion/calendar) | Standalone calendar | [#195][i195], [#194][i194], [#193][i193], [#191][i191] | -| [icon](./packages/icon) | [![icon](https://img.shields.io/npm/v/@lion/icon.svg)](https://www.npmjs.com/package/@lion/icon) | Display our svg icons | [#173][i173], [#172][i172] | -| [steps](./packages/steps) | [![steps](https://img.shields.io/npm/v/@lion/steps.svg)](https://www.npmjs.com/package/@lion/steps) | Multi Step System | n/a | -| [tabs](./packages/tabs) | [![tBS](https://img.shields.io/npm/v/@lion/tabs.svg)](https://www.npmjs.com/package/@lion/tabs) | Move between a small number of equally important views | n/a | -| **-- Forms --** | | | | -| [form](./packages/form) | [![form](https://img.shields.io/npm/v/@lion/form.svg)](https://www.npmjs.com/package/@lion/form) | Wrapper for multiple form elements | ✔️ | -| [field](./packages/field) | [![field](https://img.shields.io/npm/v/@lion/field.svg)](https://www.npmjs.com/package/@lion/field) | Base Class for all inputs | [#190][i190] | -| [fieldset](./packages/fieldset) | [![fieldset](https://img.shields.io/npm/v/@lion/fieldset.svg)](https://www.npmjs.com/package/@lion/fieldset) | Group for form inputs | ✔️ | -| [validate](./packages/validate) | [![validate](https://img.shields.io/npm/v/@lion/validate.svg)](https://www.npmjs.com/package/@lion/validate) | Validation for form components | n/a | -| [checkbox](./packages/checkbox) | [![checkbox](https://img.shields.io/npm/v/@lion/checkbox.svg)](https://www.npmjs.com/package/@lion/checkbox) | Checkbox form element | ✔️ | -| [checkbox-group](./packages/checkbox-group) | [![checkbox-group](https://img.shields.io/npm/v/@lion/checkbox-group.svg)](https://www.npmjs.com/package/@lion/checkbox-group) | Group of checkboxes | ✔️ | -| [input](./packages/input) | [![input](https://img.shields.io/npm/v/@lion/input.svg)](https://www.npmjs.com/package/@lion/input) | Input element for strings | ✔️ | -| [input-amount](./packages/input-amount) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | [#166][i166] | -| [input-date](./packages/input-date) | [![input-date](https://img.shields.io/npm/v/@lion/input-date.svg)](https://www.npmjs.com/package/@lion/input-date) | Input element for dates | ✔️ | -| [input-datepicker](./packages/input-datepicker) | [![input-datepicker](https://img.shields.io/npm/v/@lion/input-datepicker.svg)](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ | -| [input-email](./packages/input-email) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | [#169][i169] | -| [input-iban](./packages/input-iban) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | [#169][i169] | -| [input-range](./packages/input-range) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ | -| [radio](./packages/radio) | [![radio](https://img.shields.io/npm/v/@lion/radio.svg)](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ | -| [radio-group](./packages/radio-group) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ | -| [select](./packages/select) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ | -| [textarea](./packages/textarea) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | [#165][i165] | -| **-- Overlays --** | | | | -| [overlays](./packages/overlays) | [![overlays](https://img.shields.io/npm/v/@lion/overlays.svg)](https://www.npmjs.com/package/@lion/overlays) | Overlay System | ✔️ | -| [dialog](./packages/dialog) | [![dialog](https://img.shields.io/npm/v/@lion/dialog.svg)](https://www.npmjs.com/package/@lion/dialog) | Dialog element | ✔️ | -| [tooltip](./packages/tooltip) | [![tooltip](https://img.shields.io/npm/v/@lion/tooltip.svg)](https://www.npmjs.com/package/@lion/tooltip) | Tooltip element | [#178][i178], [#177][i177], [#176][i176], [#175][i175], [#174][i174] | +| Package | Version | Description | Accessibility | +| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ | -------------------------- | +| **-- Buttons --** | | | | +| [button](./packages/button) | [![button](https://img.shields.io/npm/v/@lion/button.svg)](https://www.npmjs.com/package/@lion/button) | Button | ✔️ | +| [switch](./packages/switch) | [![switch](https://img.shields.io/npm/v/@lion/switch.svg)](https://www.npmjs.com/package/@lion/switch) | Switch | ✔️ | +| **-- Forms --** | | | | +| [form](./packages/form) | [![form](https://img.shields.io/npm/v/@lion/form.svg)](https://www.npmjs.com/package/@lion/form) | Wrapper for multiple form elements | ✔️ | +| [field](./packages/field) | [![field](https://img.shields.io/npm/v/@lion/field.svg)](https://www.npmjs.com/package/@lion/field) | Base Class for all inputs | ✔️ | +| [fieldset](./packages/fieldset) | [![fieldset](https://img.shields.io/npm/v/@lion/fieldset.svg)](https://www.npmjs.com/package/@lion/fieldset) | Group for form inputs | ✔️ | +| [validate](./packages/validate) | [![validate](https://img.shields.io/npm/v/@lion/validate.svg)](https://www.npmjs.com/package/@lion/validate) | Validation for form components | n/a | +| [checkbox](./packages/checkbox) | [![checkbox](https://img.shields.io/npm/v/@lion/checkbox.svg)](https://www.npmjs.com/package/@lion/checkbox) | Checkbox form element | ✔️ | +| [checkbox-group](./packages/checkbox-group) | [![checkbox-group](https://img.shields.io/npm/v/@lion/checkbox-group.svg)](https://www.npmjs.com/package/@lion/checkbox-group) | Group of checkboxes | ✔️ | +| [input](./packages/input) | [![input](https://img.shields.io/npm/v/@lion/input.svg)](https://www.npmjs.com/package/@lion/input) | Input element for strings | ✔️ | +| [input-amount](./packages/input-amount) | [![input-amount](https://img.shields.io/npm/v/@lion/input-amount.svg)](https://www.npmjs.com/package/@lion/input-amount) | Input element for amounts | ✔️ | +| [input-date](./packages/input-date) | [![input-date](https://img.shields.io/npm/v/@lion/input-date.svg)](https://www.npmjs.com/package/@lion/input-date) | Input element for dates | ✔️ | +| [input-datepicker](./packages/input-datepicker) | [![input-datepicker](https://img.shields.io/npm/v/@lion/input-datepicker.svg)](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ | +| [input-email](./packages/input-email) | [![input-email](https://img.shields.io/npm/v/@lion/input-email.svg)](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | ✔️ | +| [input-iban](./packages/input-iban) | [![input-iban](https://img.shields.io/npm/v/@lion/input-iban.svg)](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | ✔️ | +| [input-range](./packages/input-range) | [![input-range](https://img.shields.io/npm/v/@lion/input-range.svg)](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ | +| [radio](./packages/radio) | [![radio](https://img.shields.io/npm/v/@lion/radio.svg)](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ | +| [radio-group](./packages/radio-group) | [![radio-group](https://img.shields.io/npm/v/@lion/radio-group.svg)](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ | +| [select](./packages/select) | [![select](https://img.shields.io/npm/v/@lion/select.svg)](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ | +| [textarea](./packages/textarea) | [![textarea](https://img.shields.io/npm/v/@lion/textarea.svg)](https://www.npmjs.com/package/@lion/textarea) | Multiline text input | ✔️ | +| **-- Overlays --** | | | | +| [overlays](./packages/overlays) | [![overlays](https://img.shields.io/npm/v/@lion/overlays.svg)](https://www.npmjs.com/package/@lion/overlays) | Overlay System | ✔️ | +| [dialog](./packages/dialog) | [![dialog](https://img.shields.io/npm/v/@lion/dialog.svg)](https://www.npmjs.com/package/@lion/dialog) | Dialog element | ✔️ | +| [tooltip](./packages/tooltip) | [![tooltip](https://img.shields.io/npm/v/@lion/tooltip.svg)](https://www.npmjs.com/package/@lion/tooltip) | Tooltip element | [#175][i175] | +| **-- Icons --** | | | | +| [icon](./packages/icon) | [![icon](https://img.shields.io/npm/v/@lion/icon.svg)](https://www.npmjs.com/package/@lion/icon) | Display our svg icons | [#173][i173], [#172][i172] | +| **-- Navigation --** | | | | +| [steps](./packages/steps) | [![steps](https://img.shields.io/npm/v/@lion/steps.svg)](https://www.npmjs.com/package/@lion/steps) | Multi Step System | n/a | +| [tabs](./packages/tabs) | [![tBS](https://img.shields.io/npm/v/@lion/tabs.svg)](https://www.npmjs.com/package/@lion/tabs) | Move between a small number of equally important views | n/a | +| **-- Others --** | | | | +| [core](./packages/core) | [![core](https://img.shields.io/npm/v/@lion/core.svg)](https://www.npmjs.com/package/@lion/core) | Core System (exports LitElement, lit-html) | n/a | +| [calendar](./packages/calendar) | [![calendar](https://img.shields.io/npm/v/@lion/calendar.svg)](https://www.npmjs.com/package/@lion/calendar) | Standalone calendar | [#195][i195], [#194][i194] | +| [localize](./packages/localize) | [![localize](https://img.shields.io/npm/v/@lion/localize.svg)](https://www.npmjs.com/package/@lion/localize) | Localize and translate your application/components | n/a | +| [ajax](./packages/ajax) | [![ajax](https://img.shields.io/npm/v/@lion/ajax.svg)](https://www.npmjs.com/package/@lion/ajax) | Fetching data via ajax request | n/a | ## How to use @@ -110,7 +114,6 @@ Lion Web Components aims to be future proof and use well-supported proven techno - [npm](http://npmjs.com) - [yarn](https://yarnpkg.com) - [open-wc](https://open-wc.org) -- [webpack](https://webpack.js.org) - [Karma](https://karma-runner.github.io) - [Mocha](https://mochajs.org) - [Chai](https://www.chaijs.com) @@ -150,19 +153,8 @@ You can join the Polymer slack by visiting [Polymer Slack Invite](https://join.s As stated above "support and issues time" is currently rather limited: feel free to open a discussion. However, we can not guarantee any response times. -[i64]: https://github.com/ing-bank/lion/issues/64 -[i165]: https://github.com/ing-bank/lion/issues/165 -[i166]: https://github.com/ing-bank/lion/issues/166 -[i169]: https://github.com/ing-bank/lion/issues/169 [i172]: https://github.com/ing-bank/lion/issues/172 [i173]: https://github.com/ing-bank/lion/issues/173 -[i174]: https://github.com/ing-bank/lion/issues/174 [i175]: https://github.com/ing-bank/lion/issues/175 -[i176]: https://github.com/ing-bank/lion/issues/176 -[i177]: https://github.com/ing-bank/lion/issues/177 -[i178]: https://github.com/ing-bank/lion/issues/178 -[i190]: https://github.com/ing-bank/lion/issues/190 -[i191]: https://github.com/ing-bank/lion/issues/191 -[i193]: https://github.com/ing-bank/lion/issues/193 [i194]: https://github.com/ing-bank/lion/issues/194 [i195]: https://github.com/ing-bank/lion/issues/195 diff --git a/assets/.editorconfig b/assets/.editorconfig deleted file mode 100644 index 78b36ca08..000000000 --- a/assets/.editorconfig +++ /dev/null @@ -1 +0,0 @@ -root = true diff --git a/assets/dummy-jsons/max.json b/assets/dummy-jsons/max.json deleted file mode 100644 index 5b5deb650..000000000 --- a/assets/dummy-jsons/max.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "file-name": "max.json", - "name": "Max", - "age": "30" -} diff --git a/assets/dummy-jsons/peter.json b/assets/dummy-jsons/peter.json deleted file mode 100644 index be96f5fb3..000000000 --- a/assets/dummy-jsons/peter.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "file-name": "peter.json", - "name": "Peter", - "age": "20" -} diff --git a/package.json b/package.json index fdf5b5048..3633c106b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@commitlint/cli": "^7.0.0", "@commitlint/config-conventional": "^7.0.0", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/eslint-config": "^1.0.0", "@open-wc/prettier-config": "^0.1.0", "@open-wc/testing": "^2.3.4", @@ -35,8 +35,8 @@ }, "scripts": { "start": "npm run storybook", - "storybook": "start-storybook -p 9001 --stories \"./packages/*/stories/*.stories.{js,mdx}\" --node-resolve --watch --open", - "storybook:build": "build-storybook --stories \"./packages/*/stories/*.stories.{js,mdx}\" --output-dir ./storybook-static", + "storybook": "start-storybook -p 9001", + "storybook:build": "build-storybook", "test": "karma start --coverage", "test:watch": "karma start --auto-watch=true --single-run=false", "test:compatibility": "karma start --compatibility all --coverage", diff --git a/packages/ajax/README.md b/packages/ajax/README.md index 17d1ffd72..1b6c974e0 100644 --- a/packages/ajax/README.md +++ b/packages/ajax/README.md @@ -5,11 +5,9 @@ `ajax` is the global manager for handling all ajax requests. It is a promise based system for fetching data, based on [axios](https://github.com/axios/axios) -## Features +## Live Demo/Documentation -- only JS functions, no (unnecessarily expensive) web components -- supports GET, POST, PUT, DELETE, REQUEST, PATCH and HEAD methods -- can be used with or without XSRF token +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/fetch-system-ajax) for a live demo and documentation ## How to use @@ -24,66 +22,5 @@ npm i --save @lion/ajax ```js import { ajax } from '@lion/ajax'; -ajax - .get('data.json') - .then(response => { - console.log(response); - }) - .catch(error => { - console.log(error); - }); +ajax.get('data.json').then(response => console.log(response)); ``` - -### Create own instances for custom options - -#### Cancel - -```js -import { AjaxClass } from '@lion/ajax'; - -const myAjax = AjaxClass.getNewInstance({ cancelable: true }); -myAjax - .get('data.json') - .then(response => { - document.querySelector('#canceled').innerHTML = JSON.stringify(response.data); - }) - .catch(error => { - document.querySelector('#canceled').innerHTML = `I got cancelled: ${error.message}`; - }); -setTimeout(() => { - myAjax.cancel('too slow'); -}, 1); -``` - -#### Cancel previous on new request - -```js -import { AjaxClass } from '@lion/ajax'; - -const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true }); -myAjax - .get('data.json') - .then(response => { - document.querySelector('#request1').innerHTML = 'Request 1: ' + JSON.stringify(response.data); - }) - .catch(error => { - document.querySelector('#request1').innerHTML = `Request 1: I got cancelled: ${error.message}`; - }); -myAjax - .get('data2.json') - .then(response => { - document.querySelector('#request2').innerHTML = 'Request 2: ' + JSON.stringify(response.data); - }) - .catch(error => { - document.querySelector('#request2').innerHTML = `Request 2: I got cancelled: ${error.message}`; - }); -``` - -## Considerations - -> Due to a [bug in axios](https://github.com/axios/axios/issues/385) options may leak in to other instances. So please avoid setting global options in axios. Interceptors have no issues. - -## Future plans - -- Endplan is to remove axios and replace it with fetch -- This wrapper exist so that this switch should not mean any breaking changes for our users diff --git a/packages/ajax/package.json b/packages/ajax/package.json index 129a2e603..725b81bc4 100644 --- a/packages/ajax/package.json +++ b/packages/ajax/package.json @@ -36,7 +36,7 @@ "@lion/core": "0.3.0" }, "devDependencies": { - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4", "sinon": "^7.2.2" } diff --git a/packages/ajax/stories/data.json b/packages/ajax/stories/data.json new file mode 100644 index 000000000..c75dffd5c --- /dev/null +++ b/packages/ajax/stories/data.json @@ -0,0 +1,16 @@ +{ + "animals": { + "cow": { + "type": "mammal", + "limbs": 4 + }, + "frog": { + "type": "amphibian", + "limbs": 4 + }, + "snake": { + "type": "reptile", + "limbs": 0 + } + } +} diff --git a/packages/ajax/stories/index.stories.js b/packages/ajax/stories/index.stories.js deleted file mode 100644 index 1e4f193f2..000000000 --- a/packages/ajax/stories/index.stories.js +++ /dev/null @@ -1,77 +0,0 @@ -import { html, storiesOf } from '@open-wc/demoing-storybook'; -import { ajax, AjaxClass } from '../index.js'; - -storiesOf('Ajax system|Ajax') - .addParameters({ options: { selectedPanel: 'storybook/actions/actions-panel' } }) - .add( - 'Get', - () => html` - - `, - ) - .add( - 'Cancelable', - () => html` - - `, - ) - .add( - 'CancelPreviousOnNewRequest', - () => html` - - `, - ); diff --git a/packages/ajax/stories/index.stories.mdx b/packages/ajax/stories/index.stories.mdx new file mode 100644 index 000000000..0eca20597 --- /dev/null +++ b/packages/ajax/stories/index.stories.mdx @@ -0,0 +1,166 @@ +import { + Story, + Meta, + html, +} from '@open-wc/demoing-storybook'; + +import { ajax } from '../src/ajax.js'; +import { AjaxClass } from '../src/AjaxClass.js'; + + + +# Ajax + +`lion-ajax` is the global manager for handling all ajax requests. +It is a promise based system for fetching data, based on +axios + + + {html` + + `} + + +```js +ajax.get('./packages/ajax/stories/data.json').then(response => console.log(response.data)); +``` + +## Features + +- only JS functions, no (unnecessarily expensive) web components +- supports GET, POST, PUT, DELETE, REQUEST, PATCH and HEAD methods +- can be used with or without XSRF token + +## How to use + +### Installation + +```sh +npm i --save @lion/ajax +``` + +```js +import { ajax, AjaxClass } from '@lion/ajax'; +``` + +## Cancelable Request + +It is possible to make an Ajax request cancelable, and then call `cancel()` to make the request provide a custom error once fired. + + + {html` + + `} + + +```js +const myAjax = AjaxClass.getNewInstance({ cancelable: true }); + +requestAnimationFrame(() => { + myAjax.cancel('too slow'); +}); + +myAjax + .get('./packages/ajax/stories/data.json') + .then(response => { + console.log(response.data); + }) + .catch(error => { + console.log(error); + }); +``` + +## Cancel concurrent requests + +You can cancel concurrent requests with the `cancelPreviousOnNewRequest` option. + + + {html` + + `} + + +```js +const myAjax = AjaxClass.getNewInstance({ cancelPreviousOnNewRequest: true }); + +myAjax + .get('./packages/ajax/stories/data.json') + .then(response => { + console.log(response.data); + }) + .catch(error => { + console.log(error.message); + }); + +myAjax + .get('./packages/ajax/stories/data.json') + .then(response => { + console.log(response.data); + }) + .catch(error => { + console.log(error.message); + }); +``` + +## Considerations + +Due to a bug in axios options may leak in to other instances. +So please avoid setting global options in axios. Interceptors have no issues. + +## Future plans + +- Eventually we want to remove axios and replace it with +Fetch +- This wrapper exist to prevent this switch from causing breaking changes for our users diff --git a/packages/button/README.md b/packages/button/README.md index 81bac11c2..c3fb944a4 100644 --- a/packages/button/README.md +++ b/packages/button/README.md @@ -2,13 +2,11 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH' -`lion-button` provides a component that is easily stylable and is accessible in all contexts. +`lion-button` provides a button component that is easily stylable and is accessible. -## Features +## Live Demo/Documentation -### Disabled - -You can also set a button as disabled with the `disabled` property. +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/buttons-button) for a live demo and API documentation ## How to use @@ -27,39 +25,3 @@ import '@lion/button/lion-button.js'; ```html Button Text ``` - -## Considerations - -### Why a Web Component? - -There are multiple reasons why we used a Web Component as opposed to a CSS component. - -- **Target size**: The minimum target size is 40 pixels, which makes even the small buttons easy to activate. A container element was needed to make this size possible. -- **Accessibility**: Our button is accessible because it uses the native button element. Having this native button element available in the light dom, preserves all platform accessibility features, like having it recognized by a native form. -- **Advanced styling**: There are advanced styling options regarding icons in buttons, where it is a lot more maintainable to handle icons in our button using slots. An example is that a sticky icon-only buttons may looks different from buttons which have both icons and text. - -### Event target - -We want to ensure that the event target returned to the user is `lion-button`, not `button`. Therefore, simply delegating the click to the native button immediately, is not desired. Instead, we catch the click event in the `lion-button`, and ensure delegation inside of there. - -### Flashing a native button click as a direct child of form - -By delegating the `click()` to the native button, it will bubble back up to `lion-button` which would cause duplicate actions. We have to simulate the full `.click()` however, otherwise form submission is not triggered. So this bubbling cannot be prevented. -Therefore, on click, we flash a ` + + +

+ `; + }} + + +## Limiting selectable values + +### Providing a lower limit + +To give a lower limit you can bind a date to the `minDate` property. + + + {() => { + const minDate = new Date(); + return html` + + + `; + }} + + +```html + +``` + +### Provide a higher limit + +To give a higher limit you can bind a date to the `maxDate` property. In this example, we show how to create an offset of + 2 days. + + + {() => { + const today = new Date(); + const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2); + return html` + + + `; + }} + + +```js +const today = new Date(); +const maxDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 2); +``` + +```html + +``` + +### Provide a list of disabled dates + +In some cases a specific date or day of the week needs to be disabled, supply those days to the `disableDates` property. + + + {html` + + day.getDay() === 6 || day.getDay() === 0} + > + `} + + +```html + +``` + +### Combined disable dates + +To limit the scope of possible dates further, combine the methods mentioned above. + + + {() => { + const today = new Date(); + const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate()); + return html` + + day.getDay() === 6 || day.getDay() === 0} + .minDate="${new Date()}" + .maxDate="${maxDate}" + > + `; + }} + + +```js +const today = new Date(); +const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, today.getDate()); +``` + +```html + day.getDay() === 6 || day.getDay() === 0} + .minDate="${new Date()}" + .maxDate="${maxDate}" +> +``` diff --git a/packages/checkbox-group/README.md b/packages/checkbox-group/README.md index 6c10e1bf2..5bb008418 100644 --- a/packages/checkbox-group/README.md +++ b/packages/checkbox-group/README.md @@ -2,13 +2,13 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH' -`lion-checkbox-group` component is webcomponent that enhances the functionality of the native `` element. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle. +`lion-checkbox-group` component enhances the functionality of the native `` element. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle. You should use [lion-checkbox](../checkbox/)'s inside this element. -## Features +## Live Demo/Documentation -Since it extends from [lion-fieldset](../fieldset/), it has all the features a fieldset has. +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-checkbox-group) for a live demo and API documentation ## How to use @@ -21,24 +21,17 @@ npm i --save @lion/checkbox @lion/checkbox-group ```js import '@lion/checkbox/lion-checkbox.js'; import '@lion/checkbox-group/lion-checkbox-group.js'; -// validator import example -import { Required } from '@lion/validate'; ``` ### Example ```html - - - - - - - + + + + + ``` - -- Make sure that it has a name attribute, this is necessary for the [lion-form](../form/)'s serialization result. diff --git a/packages/checkbox-group/package.json b/packages/checkbox-group/package.json index b57216d40..56e8de163 100644 --- a/packages/checkbox-group/package.json +++ b/packages/checkbox-group/package.json @@ -39,7 +39,7 @@ "@lion/checkbox": "0.2.13", "@lion/localize": "0.7.2", "@lion/validate": "0.5.4", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4", "sinon": "^7.2.2" } diff --git a/packages/checkbox-group/stories/index.stories.js b/packages/checkbox-group/stories/index.stories.js deleted file mode 100644 index 255c6df63..000000000 --- a/packages/checkbox-group/stories/index.stories.js +++ /dev/null @@ -1,156 +0,0 @@ -import '@lion/checkbox/lion-checkbox.js'; -import { Required, Validator } from '@lion/validate'; -import { html, storiesOf } from '@open-wc/demoing-storybook'; -import '../lion-checkbox-group.js'; - -storiesOf('Forms|Checkbox Group') - .add( - 'Default', - () => html` - - - - - - `, - ) - .add( - 'Pre Select', - () => html` - - - - - - `, - ) - .add( - 'Disabled', - () => html` - - - - - - `, - ) - .add('Validation', () => { - const validate = () => { - const checkboxGroup = document.querySelector('#scientistsGroup'); - checkboxGroup.submitted = !checkboxGroup.submitted; - }; - return html` - - - - - - - `; - }) - .add('Validation 2 checked', () => { - class HasMinTwoChecked extends Validator { - constructor(...args) { - super(...args); - this.name = 'HasMinTwoChecked'; - } - - execute(value) { - let hasError = false; - const selectedValues = value['scientists[]'].filter(v => v.checked === true); - if (!(selectedValues.length >= 2)) { - hasError = true; - } - return hasError; - } - - static async getMessage() { - return 'You need to select at least 2 values.'; - } - } - - const validate = () => { - const checkboxGroup = document.querySelector('#scientistsGroup'); - checkboxGroup.submitted = !checkboxGroup.submitted; - }; - return html` - - - - - - - `; - }); diff --git a/packages/checkbox-group/stories/index.stories.mdx b/packages/checkbox-group/stories/index.stories.mdx new file mode 100644 index 000000000..9e5acda9a --- /dev/null +++ b/packages/checkbox-group/stories/index.stories.mdx @@ -0,0 +1,342 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; +import { Required, Validator, loadDefaultFeedbackMessages } from '@lion/validate'; +import '@lion/checkbox/lion-checkbox.js'; + +import '../lion-checkbox-group.js'; + + + +# Checkbox Group + +`lion-checkbox-group` component enhances the functionality of the native `` element. +Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle. + +> You should use `` elements as the children of the ``. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +> Make sure that the checkbox-group also has a name attribute, this is necessary for the [lion-form](?path=/docs/forms-form)'s serialization result. + +## Features + +Since it extends from [lion-fieldset](?path=/docs/forms-fieldset), it has all the features a fieldset has. + +## How to use + +### Installation + +```sh +npm i --save @lion/checkbox @lion/checkbox-group +``` + +```js +import '@lion/checkbox/lion-checkbox.js'; +import '@lion/checkbox-group/lion-checkbox-group.js'; +``` + +## Pre-select + +You can pre-select options by targeting the `modelValue` object of the option and setting the `checked` property to `true`. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +## Disabled + +You can disable the entire group by setting the `disabled` attribute on the ``. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +## Validation + +You can apply validation to the ``, similar to how you would do so in any fieldset. +The interaction states of the `` are evaluated in order to hide or show feedback messages. + + + {() => { + loadDefaultFeedbackMessages(); + const validate = () => { + const checkboxGroup = document.querySelector('#scientistsGroup'); + checkboxGroup.submitted = !checkboxGroup.submitted; + }; + return html` + + + + + + + `; + }} + + +```js +import { Required, loadDefaultFeedbackMessages } from '@lion/validate'; +loadDefaultFeedbackMessages(); +const validate = () => { + const checkboxGroup = document.querySelector('#scientistsGroup'); + checkboxGroup.submitted = !checkboxGroup.submitted; +}; +``` + +```html + + + + + + +``` + + +## Validation advanced + +Below is a more advanced validator on the group that evaluates the children checkboxes' checked states. + + + {() => { + loadDefaultFeedbackMessages(); + class HasMinTwoChecked extends Validator { + constructor(...args) { + super(...args); + this.name = 'HasMinTwoChecked'; + } + execute(value) { + let hasError = false; + const selectedValues = value['scientists[]'].filter(v => v.checked === true); + if (!(selectedValues.length >= 2)) { + hasError = true; + } + return hasError; + } + static async getMessage() { + return 'You need to select at least 2 values.'; + } + } + const validate = () => { + const checkboxGroup = document.querySelector('#scientistsGroup2'); + checkboxGroup.submitted = !checkboxGroup.submitted; + }; + return html` + + + + + + + `; + }} + + +```js +import { Required, Validator, loadDefaultFeedbackMessages } from '@lion/validate'; + +loadDefaultFeedbackMessages(); + +class HasMinTwoChecked extends Validator { + constructor(...args) { + super(...args); + this.name = 'HasMinTwoChecked'; + } + + execute(value) { + let hasError = false; + const selectedValues = value['scientists[]'].filter(v => v.checked === true); + if (!(selectedValues.length >= 2)) { + hasError = true; + } + return hasError; + } + + static async getMessage() { + return 'You need to select at least 2 values.'; + } +} + +const validate = () => { + const checkboxGroup = document.querySelector('#scientistsGroup'); + checkboxGroup.submitted = !checkboxGroup.submitted; +}; +``` + +```html + + + + + + +``` diff --git a/packages/checkbox/package.json b/packages/checkbox/package.json index bc872512d..4ae4080b3 100644 --- a/packages/checkbox/package.json +++ b/packages/checkbox/package.json @@ -37,7 +37,7 @@ "@lion/input": "0.4.2" }, "devDependencies": { - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/checkbox/stories/index.stories.mdx b/packages/checkbox/stories/index.stories.mdx new file mode 100644 index 000000000..d13828111 --- /dev/null +++ b/packages/checkbox/stories/index.stories.mdx @@ -0,0 +1,42 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; + +import '../lion-checkbox.js'; + + + +# Checkbox + +`lion-checkbox` component is a sub-element to be used in [lion-checkbox-group](?path=/docs/forms-checkbox-group--default-story) elements. Its purpose is to provide a way for users to check **multiple** options amongst a set of choices, or to function as a single toggle. + +{html` + + + +`} + +```html + + + +``` + +- Use this component inside a [lion-checkbox-group](?path=/docs/forms-checkbox-group--default-story) +- Make sure that it has a name attribute with appended `[]` for multiple choices. + +## Features + +- Get the checked state (boolean) - `checked` boolean attribute +- Pre-select an option by setting the `checked` boolean attribute +- Get or set the value of the choice - `choiceValue()` + +## How to use + +### Installation + +```sh +npm i --save @lion/checkbox +``` + +```js +import '@lion/checkbox/lion-checkbox.js'; +``` diff --git a/packages/choice-input/package.json b/packages/choice-input/package.json index 83a4a493e..921a7bde0 100644 --- a/packages/choice-input/package.json +++ b/packages/choice-input/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@lion/input": "0.4.2", "@lion/validate": "0.5.4", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4", "sinon": "^7.2.2" } diff --git a/packages/core/README.md b/packages/core/README.md index 2f7fce4ee..2f378311b 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -2,49 +2,28 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH' -## Deprecations +`lion-input-amount` component is based on the generic text input field. Its purpose is to provide a way for users to fill in an amount. -Currently all deprecations are removed due to alpha state. +## Live Demo/Documentation -## Deduping of mixins +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/core) for a live demo and API documentation -### Why is deduping of mixins necessary? +## How to use -Imagine you are developing web components and creating ES classes for Custom Elements. You have two generic mixins (let's say `M1` and `M2`) which require independently the same even more generic mixin (`BaseMixin`). `M1` and `M2` can be used independently, that means they have to inherit from `BaseMixin` also independently. But they can be also used in combination. Sometimes `M1` and `M2` are used in the same component and can mess up the inheritance chain if `BaseMixin` is applied twice. -In other words, this may happen to the protoype chain `... -> M2 -> BaseMixin -> M1 -> BaseMixin -> ...`. +### Installation -An example of this may be a `LocalizeMixin` used across different components and mixins. Some mixins may need it and many components need it too and can not rely on other mixins to have it by default, so must inherit from it independently. +```sh +npm i --save @lion/core +``` -The more generic the mixin is, the higher the chance of being appliend more than once. As a mixin author you can't control how it is used, and can't always predict it. So as a safety measure it is always recommended to create deduping mixins. +```js +import { dedupeMixin, LitElement } from '@lion/core'; +``` -### Usage of dedupeMixin() - -This is an example of how to make a conventional ES mixin deduping. +### Example ```js const BaseMixin = dedupeMixin((superClass) => { return class extends superClass { ... }; }); - -// inherits from BaseMixin -const M1 = dedupeMixin((superClass) => { - return class extends BaseMixin(superClass) { ... }; -}); - -// inherits from BaseMixin -const M2 = dedupeMixin((superClass) => { - return class extends BaseMixin(superClass) { ... }; -}); - -// component inherits from M1 -// MyCustomElement -> M1 -> BaseMixin -> BaseCustomElement; -class MyCustomElement extends M1(BaseCustomElement) { ... } - -// component inherits from M2 -// MyCustomElement -> M2 -> BaseMixin -> BaseCustomElement; -class MyCustomElement extends M2(BaseCustomElement) { ... } - -// component inherits from both M1 and M2 -// MyCustomElement -> M2 -> M1 -> BaseMixin -> BaseCustomElement; -class MyCustomElement extends M2(M1(BaseCustomElement)) { ... } ``` diff --git a/packages/core/index.js b/packages/core/index.js index 632387c09..de1162c39 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -3,25 +3,43 @@ * For now please import types from lit-element and lit-html directly. */ +// lit-element +export { + css, + CSSResult, + // decorators.js + customElement, + // updating-element.js + defaultConverter, + eventOptions, + LitElement, + notEqual, + property, + query, + queryAll, + // css-tag.js + supportsAdoptingStyleSheets, + unsafeCSS, + UpdatingElement, +} from 'lit-element'; // lit-html export { - html, - svg, - render, - noChange, - nothing, - directive, - isDirective, - TemplateResult, - SVGTemplateResult, AttributePart, BooleanAttributePart, + directive, EventPart, - NodePart, - PropertyPart, + html, + isDirective, isPrimitive, + noChange, + NodePart, + nothing, + PropertyPart, + render, + svg, + SVGTemplateResult, + TemplateResult, } from 'lit-html'; -export { render as renderShady } from 'lit-html/lib/shady-render.js'; export { asyncAppend } from 'lit-html/directives/async-append.js'; export { asyncReplace } from 'lit-html/directives/async-replace.js'; export { cache } from 'lit-html/directives/cache.js'; @@ -32,30 +50,12 @@ export { repeat } from 'lit-html/directives/repeat.js'; export { styleMap } from 'lit-html/directives/style-map.js'; export { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; export { until } from 'lit-html/directives/until.js'; -// lit-element -export { - LitElement, - // css-tag.js - supportsAdoptingStyleSheets, - CSSResult, - unsafeCSS, - css, - // updating-element.js - defaultConverter, - notEqual, - UpdatingElement, - // decorators.js - customElement, - property, - query, - queryAll, - eventOptions, -} from 'lit-element'; +export { render as renderShady } from 'lit-html/lib/shady-render.js'; // ours export { dedupeMixin } from './src/dedupeMixin.js'; export { DelegateMixin } from './src/DelegateMixin.js'; -export { LionSingleton } from './src/LionSingleton.js'; -export { SlotMixin } from './src/SlotMixin.js'; export { DisabledMixin } from './src/DisabledMixin.js'; export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js'; +export { LionSingleton } from './src/LionSingleton.js'; +export { SlotMixin } from './src/SlotMixin.js'; export { UpdateStylesMixin } from './src/UpdateStylesMixin.js'; diff --git a/packages/core/package.json b/packages/core/package.json index e816f85fd..0658c1255 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,7 +35,7 @@ "lit-html": "^1.0.0" }, "devDependencies": { - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4", "sinon": "^7.2.2" } diff --git a/packages/core/stories/index.stories.mdx b/packages/core/stories/index.stories.mdx new file mode 100644 index 000000000..bf1981cdf --- /dev/null +++ b/packages/core/stories/index.stories.mdx @@ -0,0 +1,67 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; + + + +# Core + +The `@lion/core` package is mostly for in depth usage. +It handles the version of `lit-element` and `lit-html`. + +In order to be sure a compatible version is used import it via this package. + +```js +import { LitElement, html, render } from '@lion/core'; +``` + +It offers low level functionality for + +- [function to deduplicate mixins (dedupeMixin)](#deduping-of-mixins) +- Mixin to handle disabled (DisabledMixin) +- Mixin to handle disabled AND tabIndex (DisabledWithTabIndexMixin) +- Mixin to manage auto generated needed slot elements in light dom (SlotMixin) + + +> These features are not well documented - care to help out? + +## Deduping of mixins + +### Why is deduping of mixins necessary? + +Imagine you are developing web components and creating ES classes for Custom Elements. You have two generic mixins (let's say `M1` and `M2`) which require independently the same even more generic mixin (`BaseMixin`). `M1` and `M2` can be used independently, that means they have to inherit from `BaseMixin` also independently. But they can be also used in combination. Sometimes `M1` and `M2` are used in the same component and can mess up the inheritance chain if `BaseMixin` is applied twice. +In other words, this may happen to the protoype chain `... -> M2 -> BaseMixin -> M1 -> BaseMixin -> ...`. + +An example of this may be a `LocalizeMixin` used across different components and mixins. Some mixins may need it and many components need it too and can not rely on other mixins to have it by default, so must inherit from it independently. + +The more generic the mixin is, the higher the chance of being appliend more than once. As a mixin author you can't control how it is used, and can't always predict it. So as a safety measure it is always recommended to create deduping mixins. + +### Usage of dedupeMixin() + +This is an example of how to make a conventional ES mixin deduping. + +```js +const BaseMixin = dedupeMixin((superClass) => { + return class extends superClass { ... }; +}); + +// inherits from BaseMixin +const M1 = dedupeMixin((superClass) => { + return class extends BaseMixin(superClass) { ... }; +}); + +// inherits from BaseMixin +const M2 = dedupeMixin((superClass) => { + return class extends BaseMixin(superClass) { ... }; +}); + +// component inherits from M1 +// MyCustomElement -> M1 -> BaseMixin -> BaseCustomElement; +class MyCustomElement extends M1(BaseCustomElement) { ... } + +// component inherits from M2 +// MyCustomElement -> M2 -> BaseMixin -> BaseCustomElement; +class MyCustomElement extends M2(BaseCustomElement) { ... } + +// component inherits from both M1 and M2 +// MyCustomElement -> M2 -> M1 -> BaseMixin -> BaseCustomElement; +class MyCustomElement extends M2(M1(BaseCustomElement)) { ... } +``` diff --git a/packages/dialog/README.md b/packages/dialog/README.md index 8b3e1e9d2..fcb13c881 100644 --- a/packages/dialog/README.md +++ b/packages/dialog/README.md @@ -2,15 +2,12 @@ [//]: # 'AUTO INSERT HEADER PREPUBLISH' -`lion-dialog` is a component wrapping a modal dialog controller -Its purpose is to make it easy to use our Overlay System declaratively -With regards to modal dialogs, this is one of the more commonly used examples of overlays. +`lion-dialog` is a component wrapping a modal dialog controller. +Its purpose is to make it easy to use our Overlay System declaratively. -## Features +## Live Demo/Documentation -- Show content when clicking the invoker -- Respond to close event in the slot="content" element, to close the content -- Have a `.config` object to set or update the OverlayController's configuration +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/overlays-specific-wc-dialog) for a live demo and documentation ## How to use @@ -26,20 +23,12 @@ import '@lion/dialog/lion-dialog.js'; ### Example -```js -html` - -
- This is a dialog - -
- - -`; +```html + +
+ This is a dialog + +
+ + ``` diff --git a/packages/dialog/package.json b/packages/dialog/package.json index 12aa186c2..ff6c10cc8 100644 --- a/packages/dialog/package.json +++ b/packages/dialog/package.json @@ -34,7 +34,7 @@ "@lion/overlays": "0.10.1" }, "devDependencies": { - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/dialog/stories/demo-dialog-style.js b/packages/dialog/stories/demo-dialog-style.js new file mode 100644 index 000000000..cd5677b05 --- /dev/null +++ b/packages/dialog/stories/demo-dialog-style.js @@ -0,0 +1,44 @@ +import { css } from '@lion/core'; + +export default css` + .demo-box { + width: 200px; + background-color: white; + border-radius: 2px; + border: 1px solid grey; + padding: 8px; + } + + .demo-box_placements { + display: flex; + flex-direction: column; + margin-top: 20px; + } + + lion-dialog { + display: block; + padding: 10px; + margin-bottom: 10px; + } + + .close-button { + color: black; + font-size: 28px; + line-height: 28px; + } + + .demo-box__column { + display: flex; + flex-direction: column; + } + + .dialog { + display: block; + position: absolute; + font-size: 16px; + color: white; + background-color: black; + border-radius: 4px; + padding: 8px; + } +`; diff --git a/packages/dialog/stories/index.stories.js b/packages/dialog/stories/index.stories.js deleted file mode 100644 index bb199dd2d..000000000 --- a/packages/dialog/stories/index.stories.js +++ /dev/null @@ -1,142 +0,0 @@ -import { css } from '@lion/core'; -import { html, object, storiesOf, withKnobs } from '@open-wc/demoing-storybook'; -import '../lion-dialog.js'; - -const dialogDemoStyle = css` - .demo-box { - width: 200px; - background-color: white; - border-radius: 2px; - border: 1px solid grey; - padding: 8px; - } - - .demo-box_placements { - display: flex; - flex-direction: column; - width: 173px; - margin: 0 auto; - margin-top: 68px; - } - - lion-dialog { - padding: 10px; - } - - .close-button { - color: black; - font-size: 28px; - line-height: 28px; - } - - .demo-box__column { - display: flex; - flex-direction: column; - } - - .dialog { - display: block; - position: absolute; - font-size: 16px; - color: white; - background-color: black; - border-radius: 4px; - padding: 8px; - } - - @media (max-width: 480px) { - .dialog { - display: none; - } - } -`; - -storiesOf('Overlays Specific WC | Dialog') - .addDecorator(withKnobs) - .add( - 'Button dialog', - () => html` - -

- Important note: Your slot="content" gets moved to global overlay container. - After initialization it is no longer a child of lion-dialog -

-

- To close your dialog from some action performed inside the content slot, fire a - hide event. -

-

- For the dialog to close, it will need to bubble to the content slot (use - bubbles: true. If absolutely needed composed: true can be used to - traverse shadow boundaries) -

-

The demo below demonstrates this

-
- - -
- Hello! You can close this notification here: - -
-
-
- `, - ) - .add('Custom configuration', () => { - const dialog = placement => html` - - -
- Hello! You can close this notification here: - -
-
- `; - - return html` - -
- ${dialog('center')} ${dialog('top-left')} ${dialog('top-right')} ${dialog('bottom-left')} - ${dialog('bottom-right')} -
- `; - }) - .add('Toggle placement with knobs', () => { - const dialog = html` - - -
- Hello! You can close this notification here: - -
-
- `; - - return html` - -
- ${dialog} -
- `; - }); diff --git a/packages/dialog/stories/index.stories.mdx b/packages/dialog/stories/index.stories.mdx new file mode 100644 index 000000000..5a24881c6 --- /dev/null +++ b/packages/dialog/stories/index.stories.mdx @@ -0,0 +1,182 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; +import demoStyle from './demo-dialog-style.js'; +import '../lion-dialog.js'; + + + +# Dialog + +`lion-dialog` is a component wrapping a modal dialog controller. +Its purpose is to make it easy to use our Overlay System declaratively. + + + {html` + + + +
+ Hello! You can close this dialog here: + +
+
+ `} +
+ +```html + + +
+ Hello! You can close this dialog here: + +
+
+``` + +## Features + +- Show content when clicking the invoker +- Respond to close event in the slot="content" element, to close the content +- Have a `.config` object to set or update the OverlayController's configuration + +## How to use + +### Installation + +```sh +npm i --save @lion/dialog +``` + +```js +import '@lion/dialog/lion-dialog.js'; +``` + +## Usage notes + +- Your `slot="content"` node will be moved to the global overlay container during initialization. + After, your content node is no longer a child of `lion-dialog`. + If you still need to access it from the `lion-dialog` you can do so by using the `._overlayContentNode` property. +- To close the overlay from within the content node, you need to dispatch a `close-overlay` event that bubbles. + It has to be able to reach the content node. +- If you need to traverse shadow boundaries, you will have to add `composed: true` as well, although this is discouraged as a practice. + +## Changing the configuration + +You can use the `config` property on the dialog to change the configuration. +The documentation of the full config object can be found in the `lion/overlay` package. + +The `config` property uses a setter to merge the passed configuration with the current, so you only **overwrite what you pass** when updating `config`. + + +### Placement overrides + + + {() => { + const dialog = placement => html` + + +
+ Hello! You can close this notification here: + +
+
+ `; + return html` + +
+ ${dialog('center')} ${dialog('top-left')} ${dialog('top-right')} ${dialog('bottom-left')} + ${dialog('bottom-right')} +
+ `; + }} +
+ +```html + + +
+ Hello! You can close this notification here: + +
+
+``` + +Configuration passed to `config` property: +```js +{ + viewportConfig: { + placement: ... // <-- choose a position + } +} +``` + +### Other overrides + +No backdrop, hides on escape, prevents scrolling while opened, and focuses the body when hiding. + + + {html` + + + +
+ Hello! You can close this dialog here: + +
+
+ `} +
+ +```html + + +
+ Hello! You can close this notification here: + +
+
+``` + +Configuration passed to `config` property: +```js +{ + hasBackdrop: false, + hidesOnEscape: true, + preventsScroll: true, + elementToFocusAfterHide: document.body +} +``` diff --git a/packages/field/docs/InteractionStates.md b/packages/field/docs/InteractionStates.md deleted file mode 100644 index 30a846e59..000000000 --- a/packages/field/docs/InteractionStates.md +++ /dev/null @@ -1,53 +0,0 @@ -# Interaction States - -`InteractionStateMixin` saves meta information about interaction states. It allows the -Application Developer to create advanced UX scenarios. -Examples of such scenarios are: - -- show the validation message of an input only after the user has blurred the input field -- hide the validation message when an invalid value becomes valid -- show a red border around the input right after the input became invalid - -The meta information that InteractionStateMixin collects, is stored in the properties: - -- `touched` : the user blurred the field -- `dirty` : the value in the field has changed -- `prefilled` : a prepopulated field is non empty - -## Listenening for changes - -Application Developers can subscribe to the events `touched-changed` and `dirty-changed`. - -## Advanced use cases - -### Overriding interaction states - -When creating a [custom wrapper or platform wrapper](./FormFundaments.md), it can be needed to -override the way prefilled values are 'computed'. The example below shows how this is done for -checkboxes/radio-inputs. - -```js -/** - * @override - */ -static _isPrefilled(modelValue) { - return modelValue.checked; -} -``` - -## How interaction states are preconfigured - -We show the validity feedback when one of the following conditions is met: - -- prefilled: - The user already filled in something, or the value is prefilled - when the form is initially rendered. - -- touched && dirty && !prefilled: - When a user starts typing for the first time in a field with for instance `required` validation, - error message should not be shown until a field becomes `touched` (a user leaves(blurs) a field). - When a user enters a field without altering the value (making it `dirty` but not `touched`), - an error message shouldn't be shown either. - -- submitted: - If the form is submitted, always show the error message. diff --git a/packages/field/docs/formatterParserFlow.odg b/packages/field/docs/formatterParserFlow.odg deleted file mode 100644 index 1eb0244f1..000000000 Binary files a/packages/field/docs/formatterParserFlow.odg and /dev/null differ diff --git a/packages/field/docs/formatterParserFlow.svg b/packages/field/docs/formatterParserFlow.svg deleted file mode 100644 index f4d5d18eb..000000000 --- a/packages/field/docs/formatterParserFlow.svg +++ /dev/null @@ -1,267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Master slide - - - - - - - - - - - - - - - - - - - - - set modelValue - - - - - - - - - Convert modelValue - - to formattedValue - - (run formatter) - - - - - - - - - Sync formattedValue - - to <input> value - - - - - - - - - Convert modelValue - - to serializedValue - - - (run serializer) - - - - - - - - - set - - serializedValue - - - - - - - - - Convert - - - - serializedValue - - - - - to modelValue - - (run deserializer) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - developer - - - - - - - end user - - - - - - - - - keydown <input> - - - - - - - - - Sync <input> value to - - - modelValue - - - - - - - - - blur <input> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <lion-field> - - - - - - - - - \ No newline at end of file diff --git a/packages/field/docs/modelValue.md b/packages/field/docs/modelValue.md deleted file mode 100644 index 79812f067..000000000 --- a/packages/field/docs/modelValue.md +++ /dev/null @@ -1,69 +0,0 @@ -# ModelValue - -The modelValue or model can be considered as the ‘aorta’ of our form system. -It is the single source of truth; not only for the current state -of the form, also for all derived states: interaction, validation, visibility and other states are -computed from a modelValue change. - -## Single source of truth - -ModelValues are designed to provide the Application Developer a single way of programmatical -interaction with the form for an Application Developer. - -### One single concept for Application Developers - -Application Developers need to only care about interacting with the modelValue on a form control -level, via: - -- `.modelValue` -- `@model-value-changed` - -> Internal/private concepts like viewValue, formattedValue, serializedValue are therefore not -> recommended as a means of interaction. - -### One single concept for internals - -Internally, all derived states are computed from model-value-changed events. -Since the modelValue is computed 'realtime' and reflects all user interaction, visibility and -validation states, we can guarantee a system that enables the best User Experience -(see Interaction States). - -## Unparseable modelValues - -A modelValue can demand a certain type (Date, Number, Iban etc.). A correct type will always be -translatable into a String representation (the value presented to the end user) via the `formatter`. -When the type is not valid (usually as a consequence of a user typing in an invalid or incomplete -viewValue), the current truth is captured in the `Unparseable` type. -For example: a viewValue can't be parsed (for instance 'foo' when the type should be Number). - -The model(value) concept as implemented in lion-web is conceptually comparable to those found in -popular frameworks like Angular and Vue. - -The Unparseable type is an addition on top of this that mainly is added for the following two -purposes: - -- restoring user sessions -- realtime updated with all value changes - -### Restoring user sessions - -As a modelValue is always a single source of truth - -### Realtime updated with all value changes - -As an Application Developer, you will be notified when a user tries to write the correct type of -a value. This might be handy for giving feedback to the user. - -In order to check whether the input is correct, an Application Developer can do the following: - -```html - -``` - -```js -function handleChange({ target: { modelValue, hasFeedbackFor } }) { - if (!(modelValue instanceof Unparseable) && !(hasFeedbackFor.include('error))) { - // do my thing - } -} -``` diff --git a/packages/field/package.json b/packages/field/package.json index 5e60d691a..5155aa05e 100644 --- a/packages/field/package.json +++ b/packages/field/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@lion/localize": "0.7.2", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4", "sinon": "^7.2.2" } diff --git a/packages/fieldset/README.md b/packages/fieldset/README.md index 5b6025a2e..b4b3bd6af 100644 --- a/packages/fieldset/README.md +++ b/packages/fieldset/README.md @@ -4,18 +4,9 @@ `lion-fieldset` groups multiple input fields or other fieldsets together. -Two specific types of fieldsets: +## Live Demo/Documentation -- [lion-checkbox-group](../checkbox-group/) -- [lion-radio-group](../radio-group/) - -## Features - -- easy retrieval of form data based on field names -- advanced user interaction scenarios via [interaction states](../field/docs/InteractionStates.md) -- can have [validate](../validate/) on fieldset level and shows the validation feedback below the fieldset -- can disable input fields on fieldset level -- accessible out of the box +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-fieldset) for a live demo and documentation ## How to use @@ -35,9 +26,5 @@ import '@lion/input/lion-input.js'; ```html - - - - ``` diff --git a/packages/fieldset/src/LionFieldset.js b/packages/fieldset/src/LionFieldset.js index 413b8aa6e..0d7b2a0c3 100644 --- a/packages/fieldset/src/LionFieldset.js +++ b/packages/fieldset/src/LionFieldset.js @@ -1,8 +1,8 @@ -import { SlotMixin, html, LitElement } from '@lion/core'; +import { html, LitElement, SlotMixin } from '@lion/core'; import { DisabledMixin } from '@lion/core/src/DisabledMixin.js'; -import { ValidateMixin } from '@lion/validate'; import { FormControlMixin, FormRegistrarMixin } from '@lion/field'; import { getAriaElementsInRightDomOrder } from '@lion/field/src/utils/getAriaElementsInRightDomOrder.js'; +import { ValidateMixin } from '@lion/validate'; import { FormElementsHaveNoError } from './FormElementsHaveNoError.js'; /** @@ -314,7 +314,7 @@ export class LionFieldset extends FormRegistrarMixin( * Gets triggered by event 'validate-performed' which enabled us to handle 2 different situations * - react on modelValue change, which says something about the validity as a whole * (at least two checkboxes for instance) and nothing about the children's values - * - children validatity states have changed, so fieldset needs to update itself based on that + * - children validity states have changed, so fieldset needs to update itself based on that */ __validate(ev) { if (ev && this.isRegisteredFormElement(ev.target)) { diff --git a/packages/fieldset/stories/helpers/demo-fieldset-child.js b/packages/fieldset/stories/helpers/demo-fieldset-child.js new file mode 100644 index 000000000..c370e8fd4 --- /dev/null +++ b/packages/fieldset/stories/helpers/demo-fieldset-child.js @@ -0,0 +1,13 @@ +import { LionField } from '@lion/field'; + +customElements.define( + 'demo-fieldset-child', + class extends LionField { + get slots() { + return { + ...super.slots, + input: () => document.createElement('input'), + }; + } + }, +); diff --git a/packages/fieldset/stories/index.stories.js b/packages/fieldset/stories/index.stories.js deleted file mode 100644 index 5e0bf65d5..000000000 --- a/packages/fieldset/stories/index.stories.js +++ /dev/null @@ -1,205 +0,0 @@ -import '@lion/input/lion-input.js'; -import { localize } from '@lion/localize'; -import { loadDefaultFeedbackMessages, MinLength, Validator } from '@lion/validate'; -import { html, storiesOf } from '@open-wc/demoing-storybook'; -import '../../form-system/stories/helper-wc/h-output.js'; -import '../lion-fieldset.js'; - -localize.locale = 'en-GB'; -loadDefaultFeedbackMessages(); - -storiesOf('Forms|Fieldset') - .add( - 'Default', - () => html` -

- A native fieldset element should always have a legend-element for a11y purposes. Our - fieldset element is not native and should not have a legend-element. Our fieldset instead - has a label attribute or you can add a label with a div- or heading-element using the - slot="label". Please don't use the the label-element because that is reserved for - input-elements. -

- - - - - `, - ) - .add( - 'Data', - () => html` - - - - - - `, - ) - .add('Disabled', () => { - function toggleDisabled() { - const fieldset = document.querySelector('#fieldset'); - fieldset.disabled = !fieldset.disabled; - } - return html` - - - - - - - - - - `; - }) - .add( - 'Sub Fieldsets Data', - () => html` - -
Personal data
- - - - - - - - - -
- -
- `, - ) - .add('Validation', () => { - const DemoValidator = class extends Validator { - constructor() { - super(); - this.name = 'DemoValidator'; - } - - execute(value) { - if (value && value.input1) { - return true; // el.hasError = true - } - return false; - } - - static async getMessage() { - return '[Fieldset Error] Demo error message'; - } - }; - return html` - - - - `; - }) - .add('Validation 2 inputs', () => { - const IsCatsAndDogs = class extends Validator { - constructor() { - super(); - this.name = 'IsCatsAndDogs'; - } - - execute(value) { - if (!(value && value.input1 && value.input2)) { - return false; - } - return !(value.input1 === 'cats' && value.input2 === 'dogs'); - } - - static async getMessage() { - return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"'; - } - }; - return html` - - - - - - - `; - }) - .add('Validation 2 fields', () => { - const IsCats = class extends Validator { - constructor() { - super(); - this.name = 'IsCats'; - } - - execute(value) { - return value.input1 !== 'cats'; - } - - static async getMessage() { - return '[Fieldset Nr. 1 Error] Input 1 needs to be "cats"'; - } - }; - - const IsDogs = class extends Validator { - constructor() { - super(); - this.name = 'IsDogs'; - } - - execute(value) { - return value.input1 !== 'dogs'; - } - - static async getMessage() { - return '[Fieldset Nr. 2 Error] Input 1 needs to be "dogs"'; - } - }; - return html` - - - - - - -
- - - - - - - `; - }); diff --git a/packages/fieldset/stories/index.stories.mdx b/packages/fieldset/stories/index.stories.mdx new file mode 100644 index 000000000..bbe60b0c1 --- /dev/null +++ b/packages/fieldset/stories/index.stories.mdx @@ -0,0 +1,417 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; +import '@lion/input/lion-input.js'; +import { localize } from '@lion/localize'; +import { loadDefaultFeedbackMessages, MinLength, Validator, Required } from '@lion/validate'; +import '../lion-fieldset.js'; +import './helpers/demo-fieldset-child.js'; + + + +# Fieldset + +`lion-fieldset` groups multiple input fields or other fieldsets together. + +We have three specific fieldset implementations: + +- [lion-form](?path=/docs/forms-form) +- [lion-checkbox-group](?path=/docs/forms-checkbox-group) +- [lion-radio-group](?path=/docs/forms-radio-group) + + + {html` + + + + + + `} + + +```html + + + + +``` + +A native fieldset element should always have a legend-element for a11y purposes. +However, our fieldset element is not native and should not have a legend-element. +Our fieldset instead has a label attribute or you can add a label with a div- or heading-element using `slot="label"`. + +## Features + +- Easy retrieval of form data based on field names +- Advanced user interaction scenarios via [interaction states](?path=/docs/forms-system-interaction-states) +- Can have [validate](?path=/docs/forms-system-validate-system) on fieldset level and shows the validation feedback below the fieldset +- Can disable input fields on fieldset level +- Accessible out of the box + + +## Examples + +### With Data + +The fieldset's modelValue is an `Object` containing properties where the key is the `name` attribute of the field, +and the value is the `modelValue` of the field. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +### Disabled + +Disabling a fieldset disables all its child fields. +When enabling a fieldset, fields that have disabled explicitly set will stay disabled. + + + {() => { + function toggleDisabled() { + const fieldset = document.querySelector('#fieldset'); + fieldset.disabled = !fieldset.disabled; + } + return html` + + + + + + + + + + `; + }} + + +```js +function toggleDisabled() { + const fieldset = document.querySelector('#fieldset'); + fieldset.disabled = !fieldset.disabled; +} +``` + +```html + + + + + + + + + +``` + +### Nesting fieldsets + +Fieldsets can also be nested. The level of nesting will correspond one to one with the `modelValue` object. + + + {html` + +
Personal data
+ + + + + + + + + +
+ +
+ `} +
+ +```html + +
Personal data
+ + + + + + + + + +
+ +
+``` + +## Validation + +You can create validators that work on a fieldset level. +Below, we mimic a `required` validator, but on the fieldset. +Try it by typing something in the input, then removing it. + + + {() => { + const DemoValidator = class extends Validator { + constructor() { + super(); + this.name = 'DemoValidator'; + } + execute(value) { + if (value && value.input1) { + return false; // el.hasError = true + } + return true; + } + static async getMessage() { + return '[Fieldset Error] Demo error message'; + } + }; + return html` + + + + `; + }} + + +```js +const DemoValidator = class extends Validator { + constructor() { + super(); + this.name = 'DemoValidator'; + } + + execute(value) { + if (value && value.input1) { + return false; + } + return true; + } + + static async getMessage() { + return '[Fieldset Error] Demo error message'; + } +}; +``` + +```html + + + +``` + +### Validating multiple inputs in a fieldset + +You can have your fieldset validator take into consideration multiple fields. + + + {() => { + const IsCatsAndDogs = class extends Validator { + constructor() { + super(); + this.name = 'IsCatsAndDogs'; + } + execute(value) { + return !(value.input1 === 'cats' && value.input2 === 'dogs'); + } + static async getMessage() { + return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"'; + } + }; + return html` + + + + + + + `; + }} + + +```js +const IsCatsAndDogs = class extends Validator { + constructor() { + super(); + this.name = 'IsCatsAndDogs'; + } + + execute(value) { + return !(value.input1 === 'cats' && value.input2 === 'dogs'); + } + + static async getMessage() { + return '[Fieldset Error] Input 1 needs to be "cats" and Input 2 needs to be "dogs"'; + } +}; +``` + +```html + + + + + + +``` + +Alternatively you can also let the fieldset validator be dependent on the error states of its child fields. + +Simply loop over the formElements inside your Validator's `execute` method: +```js +this.formElementsArray.some(el => el.hasFeedbackFor.includes('error')); +``` + +### Validating multiple fieldsets + +You can have your fieldset validator take into accounts multiple nested fieldsets. + + + {() => { + const IsCatsDogs = class extends Validator { + constructor() { + super(); + this.name = 'IsCatsDogs'; + } + execute(value) { + if ((value.inner1 && value.inner1.input1 === 'cats') && + (value.inner2 && value.inner2.input1 === 'dogs') + ) { + return false; + } + return true; + } + static async getMessage() { + return 'There is a problem with one of your fieldsets'; + } + }; + return html` + + + + + + +
+ + + + + +
+ `; + }} +
+ +```js +const IsCatsDogs = class extends Validator { + constructor() { + super(); + this.name = 'IsCatsDogs'; + } + + execute(value) { + if ((value.inner1 && value.inner1.input1 === 'cats') && + (value.inner2 && value.inner2.input1 === 'dogs') + ) { + return false; + } + return true; + } + + static async getMessage() { + return 'There is a problem with one of your fieldsets'; + } +}; +``` + +```html + + + + + + +
+ + + + + +
+``` diff --git a/packages/form-system/README.md b/packages/form-system/README.md index ee57325cd..cc96317fa 100644 --- a/packages/form-system/README.md +++ b/packages/form-system/README.md @@ -6,10 +6,9 @@ The Form System allows you to create complex forms with various validation in an ## Features -- built in [validate](../validate) for error/warning/info/success -- formatting of values -- accessible -- ... +- Built in [validate](../validate) for error/warning/info/success +- Formatting of values +- Accessible ## Packages diff --git a/packages/form-system/package.json b/packages/form-system/package.json index c5fdbb219..5e2c8d713 100644 --- a/packages/form-system/package.json +++ b/packages/form-system/package.json @@ -49,7 +49,7 @@ "@lion/radio-group": "0.4.5", "@lion/textarea": "0.4.2", "@lion/validate": "0.5.4", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/form-system/stories/10-intro.stories.mdx b/packages/form-system/stories/10-intro.stories.mdx new file mode 100644 index 000000000..191eade71 --- /dev/null +++ b/packages/form-system/stories/10-intro.stories.mdx @@ -0,0 +1,32 @@ + + +# Form System + +The Form System allows you to create complex forms with various validations in an easy way. + +## Features + +- Built in [validate](?path=/docs/forms-system-validate-system) for error/warning/info/success +- Formatting of values +- Accessible + +For a more in depth description look into the [Form System Overview](?path=/docs/forms-system-overview--page). + +## Packages + +| Package | Description | +| ---------------------------------------------------- | ---------------------------------- | +| [checkbox](?path=/docs/forms-checkbox--default-story) | Checkbox form element | +| [checkbox-group](?path=/docs/forms-checkbox-group--default-story) | Group of checkboxes | +| [field](?path=/docs/forms-field--default-story) | Base class for all inputs | +| [form](?path=/docs/forms-form--default-story) | Wrapper for multiple form elements | +| [input](?path=/docs/forms-input--default-story) | Input element for strings | +| [input-amount](?path=/docs/forms-input-amount--default-story) | Input element for amounts | +| [input-date](?path=/docs/forms-input-date--default-story) | Input element for dates | +| [input-email](?path=/docs/forms-input-email--default-story) | Input element for e-mails | +| [input-iban](?path=/docs/forms-input-iban--default-story) | Input element for IBANs | +| [radio](?path=/docs/forms-radio--default-story) | Radio form element | +| [radio-group](?path=/docs/forms-radio-group--default-story) | Group of radios | +| [select](?path=/docs/forms-select--default-story) | Simple native dropdown element | +| [textarea](?path=/docs/forms-textarea--default-story) | Multiline text input | +| [validate](?path=/docs/forms-system-validate-system--default-story) | Validation for our form components | diff --git a/packages/form-system/stories/15-features-overview.stories.mdx b/packages/form-system/stories/15-features-overview.stories.mdx new file mode 100644 index 000000000..1910d7f5c --- /dev/null +++ b/packages/form-system/stories/15-features-overview.stories.mdx @@ -0,0 +1,125 @@ +import { Story, Preview, Meta, html } from '@open-wc/demoing-storybook'; +import '@lion/checkbox-group/lion-checkbox-group.js'; +import '@lion/checkbox/lion-checkbox.js'; +import '@lion/fieldset/lion-fieldset.js'; +import '@lion/form/lion-form.js'; +import '@lion/input-amount/lion-input-amount.js'; +import '@lion/input-date/lion-input-date.js'; +import '@lion/input-email/lion-input-email.js'; +import '@lion/input-iban/lion-input-iban.js'; +import '@lion/input/lion-input.js'; +import '@lion/radio-group/lion-radio-group.js'; +import '@lion/radio/lion-radio.js'; +import '@lion/textarea/lion-textarea.js'; +import { MinLength, Required } from '@lion/validate'; + + + +# Features Overview + +This is a meta package to show interaction between various form elements. +For usage and installation please see the appropriate packages. + +## Umbrella Form + + + + {html` + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Submit + ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()} + >Reset +
+
+
+ `} +
+
+ +```js +import '@lion/checkbox-group/lion-checkbox-group.js'; +import '@lion/checkbox/lion-checkbox.js'; +import '@lion/fieldset/lion-fieldset.js'; +import '@lion/form/lion-form.js'; +import '@lion/input-amount/lion-input-amount.js'; +import '@lion/input-date/lion-input-date.js'; +import '@lion/input-email/lion-input-email.js'; +import '@lion/input-iban/lion-input-iban.js'; +import '@lion/input/lion-input.js'; +import '@lion/radio-group/lion-radio-group.js'; +import '@lion/radio/lion-radio.js'; +import '@lion/textarea/lion-textarea.js'; +import { MinLength, Required } from '@lion/validate'; +``` diff --git a/packages/form-system/stories/20-System-Overview.stories.mdx b/packages/form-system/stories/20-System-Overview.stories.mdx new file mode 100644 index 000000000..64a6a2c96 --- /dev/null +++ b/packages/form-system/stories/20-System-Overview.stories.mdx @@ -0,0 +1,96 @@ +import { Meta } from '@open-wc/demoing-storybook'; + + + +# Form System Overview + +This page should be used as a starting point when first using the Form System. +It provides an overview of its essential building blocks and provides links to detailed explanations +of most of its core concepts. + +## Building Blocks + +Our Form System is built from a set of very fundamental building blocks: `form control`s, `field`s and `fieldset`s. + +### Form Controls + +`Form control`s are the most fundamental building blocks of our Form System. +They are the fundament of both `field`s, and `fieldset`s and provide a normalized, predictable api +throughout the whole form. +Every form element inherits from `FormControlMixin`. + +`FormControlMixin` creates the default html structure +and accessibility is designed to be used in conjunction with the ValidateMixin and the FormatMixin. + +### Fields + +Fields (think of an input, texarea, select) are the actual form controls the end user interacts with. +They extend `LionField`, which in turn uses the `FormControlMixin`. +Fields provide a normalized api for both platform components and custom made form controls. +On top of this, they offer: + +- [formatting/parsing/serializing](?path=/docs/forms-system-formatting-and-parsing) of view values +- advanced [validation](?path=/docs/forms-system-validate-system) possibilities +- creation of advanced user interaction scenarios via [interaction states](?path=/docs/forms-system-interaction-states--interaction-states) +- provision of labels and help texts in an easy, declarative manner +- accessibility out of the box +- advanced styling possibilities: map your own Design System to the internal HTML structure + +#### Platform fields (wrappers) + +Platform field wrappers add all of the functionality described above to native form elements. + +- [LionInput](?path=/docs/forms-input), a wrapper for `` +- [LionTextarea](?path=/docs/forms-textarea), a wrapper for ` - - -
- `), - }); - - return html` - - Anchor 1 - - Anchor 2 - `; - }) - .add('trapsKeyboardFocus" (multiple)', () => { - const overlayCtrl2 = new OverlayController({ - placementMode: 'global', - trapsKeyboardFocus: true, - viewportConfig: { - placement: 'left', - }, - contentNode: fixtureSync(html` -
-

Overlay 2. Tab key is trapped within the overlay

- -
- `), - }); - - const overlayCtrl1 = new OverlayController({ - placementMode: 'global', - trapsKeyboardFocus: true, - contentNode: fixtureSync(html` -
-

Overlay 1. Tab key is trapped within the overlay

- - -
- `), - }); - - return html` - - Anchor 1 - - Anchor 2 - `; - }) - .add('isBlocking', () => { - const blockingOverlayCtrl = new OverlayController({ - placementMode: 'global', - isBlocking: true, - viewportConfig: { - placement: 'left', - }, - contentNode: fixtureSync(html` -
-

Hides other overlays

- -
- `), - }); - - const normalOverlayCtrl = new OverlayController({ - placementMode: 'global', - contentNode: fixtureSync(html` -
-

Normal overlay

- - -
- `), - }); - - return html` - - - `; - }) - .add('viewportConfig:placement', () => { - const tagName = 'lion-overlay-placement-demo'; - if (!customElements.get(tagName)) { - customElements.define( - tagName, - class extends LitElement { - static get properties() { - return { - // controller: { type: Object }, - placement: { type: String }, - }; - } - - constructor() { - super(); - this.options = [ - 'top', - 'top-right', - 'right', - 'bottom-right', - 'bottom', - 'bottom-left', - 'left', - 'top-left', - 'center', - ]; - } - - render() { - return html` -

Overlay placement: ${this.placement}

- - - `; - } - - _togglePlacement() { - this.placement = this.options[ - (this.options.indexOf(this.placement) + 1) % this.options.length - ]; - this.dispatchEvent( - new CustomEvent('toggle-placement', { - detail: this.placement, - }), - ); - } - }, - ); - } - const initialPlacement = 'center'; - const overlayCtrl = new OverlayController({ - placementMode: 'global', - viewportConfig: { - placement: initialPlacement, - }, - contentNode: fixtureSync(html` - - `), - }); - const element = overlayCtrl.content.querySelector(tagName); - element.placement = initialPlacement; - element.addEventListener('toggle-placement', e => { - overlayCtrl.updateConfig({ viewportConfig: { placement: e.detail } }); - }); - return html` - - - `; - }) - .add('hidesOnOutsideClick', () => { - const shadowContent = document.createElement('div'); - shadowContent.attachShadow({ mode: 'open' }); - shadowContent.shadowRoot.appendChild( - fixtureSync(html` -
- Shadow area -
- `), - ); - - const ctrl = new OverlayController({ - placementMode: 'global', - hidesOnOutsideClick: true, - contentNode: fixtureSync(html` -
-

Hides when clicked outside

- ${shadowContent} - -
- `), - }); - - return html` - - - `; - }); diff --git a/packages/radio-group/README.md b/packages/radio-group/README.md index d12bf62bc..7fd939273 100644 --- a/packages/radio-group/README.md +++ b/packages/radio-group/README.md @@ -6,14 +6,9 @@ You should use [lion-radio](../radio/)'s inside this element. -## Features +## Live Demo/Documentation -Since it extends from [lion-fieldset](../fieldset/), it has all the features a fieldset has. - -- Get or set the checked value of the group: - - modelValue (default) - `checkedValue()` - - formattedValue - `formattedValue()` - - serializedValue - `serializedValue()` +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-radio-group) for a live demo and API documentation ## How to use @@ -31,17 +26,11 @@ import '@lion/radio-group/lion-radio-group.js'; ### Example ```html -
- - - - - -
+ + + + + ``` - Make sure that to use a name attribute as it is necessary for the [lion-form](../form)'s serialization result. diff --git a/packages/radio-group/package.json b/packages/radio-group/package.json index bbe7cac5c..d94431264 100644 --- a/packages/radio-group/package.json +++ b/packages/radio-group/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@lion/radio": "0.2.13", "@lion/validate": "0.5.4", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/radio-group/stories/index.stories.js b/packages/radio-group/stories/index.stories.js deleted file mode 100644 index d0615fcfb..000000000 --- a/packages/radio-group/stories/index.stories.js +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import '@lion/radio/lion-radio.js'; -import { loadDefaultFeedbackMessages, Required, Validator } from '@lion/validate'; -import { html, storiesOf } from '@open-wc/demoing-storybook'; -import '../lion-radio-group.js'; - -loadDefaultFeedbackMessages(); - -storiesOf('Forms|Radio Group') - .add( - 'Default', - () => html` - - - - - - `, - ) - .add( - 'Pre Select', - () => html` - - - - - - `, - ) - .add( - 'Disabled', - () => html` - - - - - - `, - ) - .add('Validation', () => { - const validate = () => { - const radioGroup = document.querySelector('#dinosGroup'); - radioGroup.submitted = !radioGroup.submitted; - }; - return html` - - - - - - - `; - }) - .add('Validation Item', () => { - class IsBrontosaurus extends Validator { - constructor() { - super(); - this.name = 'IsBrontosaurus'; - } - - execute(value) { - const selectedValue = value['dinos[]'].find(v => v.checked === true); - const hasError = selectedValue ? selectedValue.value !== 'brontosaurus' : false; - return hasError; - } - - static async getMessage() { - return 'You need to select "brontosaurus"'; - } - } - - const validate = () => { - const radioGroup = document.querySelector('#dinosGroup'); - radioGroup.submitted = !radioGroup.submitted; - }; - - return html` - - - - - - - `; - }); diff --git a/packages/radio-group/stories/index.stories.mdx b/packages/radio-group/stories/index.stories.mdx new file mode 100644 index 000000000..3667d2358 --- /dev/null +++ b/packages/radio-group/stories/index.stories.mdx @@ -0,0 +1,242 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; +import { loadDefaultFeedbackMessages, Required, Validator } from '@lion/validate'; +import '@lion/radio/lion-radio.js'; +import '../lion-radio-group.js'; + + + +# Radio Group + +You should use ``s inside this element. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +- Make sure that to use a name attribute as it is necessary for the [lion-form](?path=/docs/forms-form)'s serialization result. +- If you have many options for a user to pick from, consider using [lion-select](?path=/docs/forms-select) instead + +## Features + +Since it extends from [lion-fieldset](?path=/docs/forms-fieldset), it has all the features a fieldset has. + +- Get or set the checked value of the group: + - modelValue (default) - `checkedValue()` + - formattedValue - `formattedValue()` + - serializedValue - `serializedValue()` + +## How to use + +### Installation + +```sh +npm i --save @lion/radio @lion/radio-group +``` + +```js +import '@lion/radio/lion-radio.js'; +import '@lion/radio-group/lion-radio-group.js'; +``` + +## Examples + +## Pre-select + +You can pre-select an option by adding the checked attribute to the selected `lion-radio`. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +## Disabled + +You can disable a specific `lion-radio` option by adding the `disabled` attribute. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +You can do the same thing for the entire group by setting the `disabled` attribute on the `lion-radio-group` element. + + + {html` + + + + + + `} + + +```html + + + + + +``` + +## Validation + + + {() => { + loadDefaultFeedbackMessages(); + const validate = () => { + const radioGroup = document.querySelector('#dinosGroup'); + radioGroup.submitted = !radioGroup.submitted; + }; + return html` + + + + + + + `; + }} + + +```js +import { loadDefaultFeedbackMessages, Required } from 'lion/validate'; +loadDefaultFeedbackMessages(); +const validate = () => { + const radioGroup = document.querySelector('#dinosGroup'); + radioGroup.submitted = !radioGroup.submitted; +}; +``` + +```html + + + + + + +``` + +You can also create a validator that validates whether a certain option is checked. + + + {() => { + class IsBrontosaurus extends Validator { + constructor() { + super(); + this.name = 'IsBrontosaurus'; + } + execute(value) { + const selectedValue = value['dinos[]'].find(v => v.checked === true); + const hasError = selectedValue ? selectedValue.value !== 'brontosaurus' : false; + return hasError; + } + static async getMessage() { + return 'You need to select "brontosaurus"'; + } + } + const validate = () => { + const radioGroup = document.querySelector('#dinosGroupTwo'); + radioGroup.submitted = !radioGroup.submitted; + }; + return html` + + + + + + + `; + }} + + +```js +import { loadDefaultFeedbackMessages, Required, Validator } from 'lion/validate'; + +class IsBrontosaurus extends Validator { + constructor() { + super(); + this.name = 'IsBrontosaurus'; + } + execute(value) { + const selectedValue = value['dinos[]'].find(v => v.checked === true); + const hasError = selectedValue ? selectedValue.value !== 'brontosaurus' : false; + return hasError; + } + static async getMessage() { + return 'You need to select "brontosaurus"'; + } +} +const validate = () => { + const radioGroup = document.querySelector('#dinosGroupTwo'); + radioGroup.submitted = !radioGroup.submitted; +}; +``` + +```html + + + + + + +``` diff --git a/packages/radio/package.json b/packages/radio/package.json index 525db980f..b08bdfa70 100644 --- a/packages/radio/package.json +++ b/packages/radio/package.json @@ -37,7 +37,7 @@ "@lion/input": "0.4.2" }, "devDependencies": { - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/radio/stories/index.stories.mdx b/packages/radio/stories/index.stories.mdx new file mode 100644 index 000000000..1e82dddc1 --- /dev/null +++ b/packages/radio/stories/index.stories.mdx @@ -0,0 +1,42 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; + +import '../lion-radio.js'; + + + +# Radio + +`lion-radio` component is a sub-element to be used in [lion-radio-group](?path=/docs/forms-radio-group--default-story) elements. Its purpose is to provide a way for users to check a **single** option amongst a set of choices. + +{html` + + + +`} + +```html + + + +``` + +- Use this component inside a [lion-radio-group](?path=/docs/forms-radio-group--default-story) +- Make sure that it has a name attribute with appended `[]` for multiple choices. + +## Features + +- Get the checked state (boolean) - `checked` boolean attribute +- Pre-select an option by setting the `checked` boolean attribute +- Get or set the value of the choice - `choiceValue()` + +## How to use + +### Installation + +```sh +npm i --save @lion/radio +``` + +```js +import '@lion/radio/lion-radio.js'; +``` diff --git a/packages/select-rich/README.md b/packages/select-rich/README.md index 6a00e31ba..97b64b277 100644 --- a/packages/select-rich/README.md +++ b/packages/select-rich/README.md @@ -7,16 +7,9 @@ It allows to provide fully customized options and a fully customized invoker but The component is meant to be used whenever the native `` element. +It allows to provide fully customized options and a fully customized invoker button. +The component is meant to be used whenever the native ` { + const selectEl = document.getElementById('checkedRichSelect'); + selectEl.checkedIndex = e.target.value; + }} + > +
+ + + + Red + Hotpink + Teal + + + `} + + +```html + + + Red + Hotpink + Teal + + +``` + +```js +console.log(`checkedIndex: ${selectEl.checkedIndex}`); // 1 +console.log(`checkedValue: ${selectEl.checkedValue}`); // 'hotpink' +selectEl.checkedIndex = 0; +console.log(`checkedIndex: ${selectEl.checkedIndex}`); // 0 +console.log(`checkedValue: ${selectEl.checkedValue}`); // 'red' +``` + +### Custom Invoker + +You can provide a custom invoker using the invoker slot. +This means it will get the selected value(s) as an input property `.selectedElement`. + +You can use this `selectedElement` to then render the content to your own invoker. + +```html + + + + ... + + +``` + +An example of how such a custom invoker class could look like: + +```js +class MyInvokerButton extends LitElement() { + static get properties() { + return { + selectedElement: { + type: Object, + }; + } + } + + _contentTemplate() { + if (this.selectedElement) { + const labelNodes = Array.from(this.selectedElement.querySelectorAll('*')); + // Nested html in the selected option + if (labelNodes.length > 0) { + // Cloning is important if you plan on passing nodes straight to a lit template + return labelNodes.map(node => node.cloneNode(true)); + } + // Or if it is just text inside the selected option, no html + return this.selectedElement.textContent; + } + return ``; + } + + render() { + return html` +
+ ${this._contentTemplate()} +
+ `; + } +} +``` + +> This example only works if your option elements don't have ShadowDOM boundaries themselves. +> Cloning deeply only works up until the first shadow boundary. diff --git a/packages/select/README.md b/packages/select/README.md index 86a6b5b76..8d848de3b 100644 --- a/packages/select/README.md +++ b/packages/select/README.md @@ -4,15 +4,9 @@ `lion-select` component is a wrapper around the native `select`. -You cannot use interactive elements inside the options. Avoid very long names to -facilitate the understandability and perceivability for screen reader users. Sets of options -where each option name starts with the same word or phrase can also significantly degrade -usability for keyboard and screen reader users. +## Live Demo/Documentation -## Features - -- catches and forwards the select events -- can be set to required or disabled +> See our [storybook](http://lion-web-components.netlify.com/?path=/docs/forms-select) for a live demo and API documentation ## How to use @@ -29,11 +23,7 @@ import '@lion/select/lion-select.js'; ### Example ```html - -
Favorite color
+ ``` - -You can preselect an option by setting the property modelValue. - -```html - - ... - -``` diff --git a/packages/select/package.json b/packages/select/package.json index 6da4d3a09..eca2278e1 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@lion/validate": "0.5.4", - "@open-wc/demoing-storybook": "^1.1.1", + "@open-wc/demoing-storybook": "^1.6.1", "@open-wc/testing": "^2.3.4" } } diff --git a/packages/select/stories/index.stories.js b/packages/select/stories/index.stories.js deleted file mode 100644 index 502214ddb..000000000 --- a/packages/select/stories/index.stories.js +++ /dev/null @@ -1,65 +0,0 @@ -import { Required } from '@lion/validate'; -import { html, storiesOf } from '@open-wc/demoing-storybook'; -import '../lion-select.js'; - -storiesOf('Forms|Select') - .add( - 'Default', - () => html` - -
Favorite color
- -
- `, - ) - .add( - 'Disabled', - () => html` - -
Favorite color
- -
- `, - ) - .add( - 'Pre selected', - () => html` - -
Favorite color
- -
- `, - ) - .add('Validation', () => { - const validate = () => { - const select = document.querySelector('#color'); - select.submitted = !select.submitted; - }; - return html` - - - - - - `; - }); diff --git a/packages/select/stories/index.stories.mdx b/packages/select/stories/index.stories.mdx new file mode 100644 index 000000000..0af87ec7d --- /dev/null +++ b/packages/select/stories/index.stories.mdx @@ -0,0 +1,192 @@ +import { Story, Meta, html } from '@open-wc/demoing-storybook'; +import { loadDefaultFeedbackMessages, Required } from '@lion/validate'; + +import '../lion-select.js'; + + + +# Select + +[//]: # 'AUTO INSERT HEADER PREPUBLISH' + +`lion-select` component is a wrapper around the native `select`. + +You cannot use interactive elements inside the options. Avoid very long names to +facilitate the understandability and perceivability for screen reader users. Sets of options +where each option name starts with the same word or phrase can also significantly degrade +usability for keyboard and screen reader users. + + + {html` + + + + `} + + +```html + + + +``` + +For this form element it is important to put the `slot="input"` with the native `select` yourself, because you are responsible for filling it with `