@luscii-healthtech/web-ui 20.2.0 โ†’ 20.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,18 +4,12 @@ The `web-ui` repository contains the UI components for Luscii's frontend project
4
4
 
5
5
  ## Table of contents
6
6
 
7
- - ๐Ÿ“ฆ [Installation](#installation)
8
- - ๐Ÿ’ป [Running locally](#running-locally)
9
- - ๐Ÿญ [Testing your production build](#testing-your-production-build)
7
+ - ๐Ÿš€ [Getting started](#getting-started)
8
+ - ๐Ÿ“– [Fonts](#fonts)
9
+ - ๐Ÿ“ฆ [How to use](#how-to-use)
10
10
  - ๐Ÿค [Contributing](#contributing)
11
- - ๐Ÿ“– [Documentation](#documentation)
12
- - ๐ŸŒˆ [Accessibility](#accessibility)
13
- - ๐Ÿงช [Testing](#testing)
14
- - ๐Ÿ”ฐ [Adding icons](#adding-icons)
15
- - โ™ป๏ธ [CI setup](#ci-setup)
16
- - ๐Ÿ“— [Stories](#stories)
17
11
 
18
- ## Installation
12
+ ## Getting started
19
13
 
20
14
  To use WebUI, you need to add it as a dependency to your project:
21
15
 
@@ -61,178 +55,59 @@ The `font-family` property value is important, as this is what WebUI is referenc
61
55
 
62
56
  If you don't declare these `@font-face` rules, the font will not be available and the headings will gracefully fall back to next available font in your application.
63
57
 
64
- ## Running locally
58
+ ## How to use
65
59
 
66
- If you want to run Storybook locally you can use:
60
+ The primary goal of WebUI is to make frontend development faster. It does this by providing a set of commonly used components that can be composed together to build user interfaces.
61
+ WebUI aims to have every component well documented, both in the code and in Storybook. You can find all available components and documentation on how to
62
+ use them at [design.luscii.com](https://design.luscii.com/).
67
63
 
68
- ```bash
69
- yarn install
70
- yarn dev # runs a Tailwind watcher and Storybook
71
- ```
72
-
73
- ## Testing your production build
74
-
75
- Assuming the following setup:
76
-
77
- - The `web-ui` and `cVitals-Web` repositories are siblings in the same folder.
78
- - You ran `yarn install` in `cVitals-Web`.
79
- - You ran `yarn build` in `web-ui`.
80
-
81
- Run the script below in your terminal, _in the parent folder of the repositories_.
82
-
83
- ```
84
- cp -a web-ui/dist/. cVitals-Web/node_modules/@luscii-healthtech/web-ui/dist
85
- ```
64
+ ### Example scenario
86
65
 
87
- Or use the shortcut:
66
+ Say you're tasked with building a UI that contains a card. This card has a title, a subtitle, some body text and a button. You could build this UI by writing the following code:
88
67
 
89
- ```
90
- yarn test-copy-build
91
- ```
92
-
93
- When you run `cVitals-Web` again, it should contain your latest updates in the component library. Use this to make sure your changes are solid before opening a pull request.
94
-
95
- ## Contributing
96
-
97
- Thank you for your help in keeping our component library organized and easy to use!
98
-
99
- When adding components, please make sure to implement them consistently in our projects and that it is clear where and when they should be used. We want to avoid having many similar components. If you want to add components, do so in the `/src/components` directory. When you create new components, add them to Storybook as well. Stories go in the `/stories` directory.
100
-
101
- If you add new components, please make sure they fit in with our existing ones and that it's clear where and when they should be used. We want to avoid having too many similar components.
102
-
103
- ### Documentation
104
-
105
- Every component should have proper documentation that explains when, where and how to use it. This sounds like a lot of work, but it doesn't have to be. And it's important, also for you, to quickly see how to best use a component.
106
-
107
- Component documentation should include the following parts:
108
-
109
- - A short explanation of each prop in a JSDoc comment style.
110
- - A stories file (`<ComponentName>.stories.tsx`).
111
- - A docs file (`<ComponentName>.docs.mdx`).
112
-
113
- Below is an explanation of each.
114
-
115
- #### Props documented via JSDoc comments
116
-
117
- A short explanation of each prop in a JSDoc comment style. These will show up in your IDE for on the fly documentation when you need it. It will also show up in Storybook, so this is a double-whammy and provides documentation exactly where you need it.
68
+ ```tsx
69
+ import { Card, Text, Button } from "@luscii-healthtech/web-ui";
118
70
 
119
- ```ts
120
- type Props = {
121
- /**
122
- * Optional description that will be shown close to the label, used to provide more
123
- * detail about the field next to the concise text of the label.
124
- */
125
- helperText?: string;
126
- };
71
+ function MyComponent() {
72
+ return (
73
+ <div className="my-layout">
74
+ <Card>
75
+ <Card.Title>Heart program</Card.Title>
76
+ <Card.Subtitle>79 participants</Card.Subtitle>
77
+ <Card.Text>Some description of how great this program is.</Card.Text>
78
+ <Card.Button>View program</Card.Button>
79
+ </Card>
80
+ </div>
81
+ );
82
+ }
127
83
  ```
128
84
 
129
- #### Stories file
85
+ Often times WebUI will provide a component that is a composition of other components. In this case, the `Card` component is a composition of `Card.Title`, `Card.Subtitle`, `Card.Text` and `Card.Button`. This allows you to build UIs faster, as you don't have to think about where to get a certain component. You can just use `Card` and its subcomponents and everything will be styled correctly.
130
86
 
131
- Stories files (`*stories.tsx`) will show up in Storybook as separate entries/pages for each component. Each component can have multiple stories defined for it, showing different use cases, variants and states of that component.
132
- It is important to include stories for a lot of these different variants and states, because visual snapshots will be created of these stories by a tool called Chromatic. This allows us to visually track changes to the component over time, and ensure that it looks and behaves correctly across different use cases. By writing stories that showcase all the different ways your component can be used, you can catch issues early and ensure that your component is robust and reliable.
87
+ ## Frequently asked questions
133
88
 
134
- You can find the Chromatic project for WebUI [here](https://www.chromatic.com/library?appId=62b0c4d5afd432f7ee70a740).
89
+ <details>
90
+ <summary>The feature I'm building needs a component from WebUI that doesn't exist.</summary>
135
91
 
136
- #### Docs file
92
+ Please get in touch with the design system circle by sending us a message in the [#design-system-circle channel](https://luscii.slack.com/archives/C03507ZKRCY). We'll discuss your needs and see what would be the best way to move forward.
137
93
 
138
- The component docs file brings everything together and should be considered the source of truth about the component. What should be documented:
94
+ </details>
139
95
 
140
- - Why we have the component and where it should be used.
141
- - An overview of the different variants and states.
142
- - Usage guidelines of when and where to use the component.
143
- - Best practices to show what to do and what not do when working with the component.
96
+ <details>
97
+ <summary>The component I'm using doesn't have the props/subcomponents to build my feature.</summary>
144
98
 
145
- > ๐Ÿ”Ž If you need to e.g. create a story to show how NOT to use a component, you might not want
146
- > to create a snapshot for that component or show it in the sidebar of Storybook. To omit a story
147
- > from snapshots, add the following to its parameters:
148
- >
149
- > ```ts
150
- > chromatic: { disableSnapshot: true },
151
- > ```
152
- >
153
- > To hide a story from the sidebar, prefix the story (variable) name
154
- > with `HIDE_`.
99
+ In most cases you can build the UI you need by composing the components that are available. If for instance `Card.Text` doesn't exist, go "one level up" and find the component that is closest to what you need. In this case, you can use the `Text` component and configure it with the correct props to match the design.
155
100
 
156
- Please see examples of other components' docs files for inspiration and try to stay consistent with how they are documented.
101
+ In case the component is missing a prop that you need, please get in touch with the design system circle by sending us a message in the [#design-system-circle channel](https://luscii.slack.com/archives/C03507ZKRCY). We'll discuss your needs and see what would be the best way to move forward.
157
102
 
158
- ### Accessibility
103
+ </details>
159
104
 
160
- WebUI is not currently designed to support screen reader usage. This is because our application is designed specifically for healthcare providers and is not publicly accessible. Our primary users are caregivers who work in healthcare and do not rely on screen readers to be able to use our application.
161
- However, it is important for developers to write accessible frontend code. Even if our current application is not designed to support screen reader usage. This means following best practices for web accessibility, such as using semantic HTML, providing alternative text for images, and ensuring keyboard navigation is possible. While our primary users may not rely on screen readers, it is important to make our application as inclusive as possible to all users.
162
-
163
- ### Testing
164
-
165
- WebUI relies on two types of testing: Visual snapshot testing via Chromatic and interaction testing via Storybook.
166
-
167
- Visual snapshot testing is handled for you. Any story you write will be picked up by Chromatic and it will create a snapshot for it.
168
-
169
- Interaction testing via Storybook allows to programmatically interact with the component. This is very helpful in testing any logic that the component may use internally and any states of the component that might be hard/impossible to trigger from the outside.
170
- More information on how to approach writing interaction tests can be found in [this page of the Storybook documentation](https://storybook.js.org/docs/react/writing-tests/interaction-testing#write-an-interaction-test).
171
-
172
- ### Adding icons
173
-
174
- Add new icons by following these steps:
175
-
176
- - Add the svg file(s) in `src/components/Icons/icons`.
177
- - Run `yarn icons`.
178
-
179
- The icons are optimized by SVGO and transformed into React components by [SVGR](https://react-svgr.com) which are placed in `src/components/Icons`.
180
-
181
- ## CI setup
182
-
183
- ### Branching
184
-
185
- The `main` branch is our default branch. When you contribute, branch from there and use the following branch naming convention:
186
-
187
- ```
188
- // Branch naming convention (enforced)
189
-
190
- major/* // For a new design system or changes that effect our foundations (e.g. typography, color).
191
- minor/* // For new components and stories.
192
- patch/* // For small improvements to existing components or stories.
193
- bug/* // For bugs or fixes.
194
- chore/* // For things related to development that don't impact the output for consumers and/or users.
195
- ```
196
-
197
- We have configured at lot of magic for your convenience.
198
-
199
- ### On every PR
200
-
201
- 1. The module build, lint and tests are checked.
202
- 2. The Storybook build is published to Chromatic.
203
- 3. Labels are added based on the branch name and PR size.
204
- 4. Branch names must follow the convention and are checked.
205
-
206
- ### On merge to main
207
-
208
- 1. Drafts a GitHub release.
209
- 2. Bumps the version of the package.
210
- 3. Publishes the new package version to NPM.
211
-
212
- ## Stories
213
-
214
- ### Stories using deprecated props
215
-
216
- When creating new stories for components with deprecated functionality please prefix the story name with `Deprecated`. In addition, also mark it with a "DEPRECATED" badge via the story's parameters.
105
+ ## Contributing
217
106
 
218
- For example a story of a `FullPageModal` component using the deprecated `primaryButtonProps` prop would look like this:
107
+ Great to have you help making WebUI better! We have a few guidelines and tips
108
+ to make sure we can keep the library consistent and easy to use. You can find
109
+ them in our [contributing guide](CONTRIBUTING.md).
219
110
 
220
- ```tsx
221
- import { BADGES } from "../.storybook/constants";
222
-
223
- export const DeprecatedWithPrimaryButtonProps = {
224
- render: OneColumTemplate,
225
- args: {
226
- isOpen: true,
227
- primaryButtonProps: {
228
- onClick: action("onClick Primary Button"),
229
- text: "Primary",
230
- },
231
- },
232
- parameters: {
233
- badges: [BADGES.DEPRECATED],
234
- },
235
- };
236
- ```
111
+ ### Reporting bugs
237
112
 
238
- This groups deprecated stories together and will make it easier to remove them later on.
113
+ If you find a bug, please report it by sending us a message in the [#design-system-circle channel](https://luscii.slack.com/archives/C03507ZKRCY). It helps us a lot knowing of anything that is not working as expected.
@@ -5,6 +5,11 @@ export interface AccordionProps {
5
5
  items: AccordionItemProps[];
6
6
  className?: string;
7
7
  isCollapsedByDefault?: boolean;
8
+ /**
9
+ * A section at the bottom of the accordion that is
10
+ * always visible, even when the accordion is closed.
11
+ */
12
+ footer?: React.ReactNode;
8
13
  }
9
- declare const Accordion: React.VFC<AccordionProps>;
14
+ declare const Accordion: React.FC<AccordionProps>;
10
15
  export default Accordion;
@@ -1,9 +1,17 @@
1
1
  import React from "react";
2
2
  export interface AccordionItemProps {
3
3
  id: string | number;
4
- title: string;
4
+ title: React.ReactNode;
5
5
  content: React.ReactNode;
6
6
  className?: string;
7
7
  isCollapsedByDefault?: boolean;
8
+ /**
9
+ * Called when the accordion item opens.
10
+ */
11
+ onOpened?: () => void;
12
+ /**
13
+ * Called when the accordion item closes.
14
+ */
15
+ onClosed?: () => void;
8
16
  }
9
- export declare const AccordionItem: React.VFC<AccordionItemProps>;
17
+ export declare const AccordionItem: React.FC<AccordionItemProps>;
@@ -21,18 +21,21 @@ export interface AccordionListProps extends Omit<AccordionProps, "items"> {
21
21
  isLoading?: boolean;
22
22
  actions?: React.ReactNode;
23
23
  }
24
- export type AccordionListItemProps<T = ListItemProps> = Omit<AccordionItemProps, "content"> & {
24
+ export type AccordionListItemProps<T = ListItemProps> = Omit<AccordionItemProps, "title" | "content"> & {
25
25
  listItems: T[];
26
26
  draggableListType?: "default";
27
+ title: string;
27
28
  };
28
- export type DraggableAccordionListItemProps = Omit<AccordionItemProps, "content"> & {
29
+ export type DraggableAccordionListItemProps = Omit<AccordionItemProps, "title" | "content"> & {
29
30
  draggableListType: "draggable";
30
31
  listItems: DraggableListProps["items"];
32
+ title: string;
31
33
  };
32
- export type SortableAccordionListItemProps = Omit<AccordionItemProps, "content"> & {
34
+ export type SortableAccordionListItemProps = Omit<AccordionItemProps, "title" | "content"> & {
33
35
  draggableListType: "sortable";
34
36
  draggableIdentifier: string;
35
37
  listItems: SortableListProps["items"];
38
+ title: string;
36
39
  };
37
40
  export type AccordionItem = AccordionListItemProps | DraggableAccordionListItemProps | SortableAccordionListItemProps;
38
41
  interface StaticComponents {
package/dist/index.d.ts CHANGED
@@ -3,6 +3,13 @@ export { FlexRow } from "./components/Container/FlexRow";
3
3
  export type { FlexContainerProps } from "./components/Container/types/FlexContainerProps.type";
4
4
  export { default as Toaster, TOASTER_TYPE_OPTIONS, } from "./components/Toaster/Toaster";
5
5
  export { toast } from "./components/Toaster/toast";
6
+ /**
7
+ * We want a better version for the Accordion component, so we're
8
+ * only temporarily exporting the current version. The new version
9
+ * will be called `Accordion`, which will allow us to slowly migrate
10
+ * to the new version.
11
+ */
12
+ export { default as AccordionTemporary } from "./components/Accordion/Accordion";
6
13
  export { AccordionList, AccordionListItemProps, AccordionListProps, } from "./components/AccordionList/AccordionList";
7
14
  export { Modal, ModalProps } from "./components/Modal/Modal";
8
15
  export { ModalSize, ModalBaseProps } from "./components/Modal/ModalBase";
@@ -2,6 +2,7 @@
2
2
 
3
3
  var React = require('react');
4
4
  var classNames = require('classnames');
5
+ var isString = require('lodash/isString');
5
6
  var lodash = require('lodash');
6
7
  var core = require('@dnd-kit/core');
7
8
  var sortable = require('@dnd-kit/sortable');
@@ -49,6 +50,7 @@ function _interopNamespace(e) {
49
50
 
50
51
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
51
52
  var classNames__default = /*#__PURE__*/_interopDefault(classNames);
53
+ var isString__default = /*#__PURE__*/_interopDefault(isString);
52
54
  var ReactTooltip__default = /*#__PURE__*/_interopDefault(ReactTooltip);
53
55
  var ReactModal__default = /*#__PURE__*/_interopDefault(ReactModal);
54
56
  var Glider__default = /*#__PURE__*/_interopDefault(Glider);
@@ -1073,8 +1075,16 @@ Title.defaultProps = {
1073
1075
  type: "base"
1074
1076
  };
1075
1077
 
1076
- const AccordionItem = ({ id, title, content, isCollapsedByDefault = false }) => {
1077
- const [isCollapsed, toggleIsCollapsed] = React.useReducer((state) => !state, isCollapsedByDefault);
1078
+ const AccordionItem = ({ id, title, content, isCollapsedByDefault = false, onClosed, onOpened }) => {
1079
+ const [isCollapsed, toggleIsCollapsed] = React.useReducer((isCol) => {
1080
+ const newStateIsCollapsed = !isCol;
1081
+ if (newStateIsCollapsed) {
1082
+ onClosed === null || onClosed === void 0 ? void 0 : onClosed();
1083
+ } else {
1084
+ onOpened === null || onOpened === void 0 ? void 0 : onOpened();
1085
+ }
1086
+ return newStateIsCollapsed;
1087
+ }, isCollapsedByDefault);
1078
1088
  const Chevron = isCollapsed ? RightArrowIcon : ChevronDownIcon;
1079
1089
  return React__namespace.default.createElement(
1080
1090
  "li",
@@ -1085,18 +1095,23 @@ const AccordionItem = ({ id, title, content, isCollapsedByDefault = false }) =>
1085
1095
  "ui-border-b ui-border-slate-200": !isCollapsed
1086
1096
  }) },
1087
1097
  React__namespace.default.createElement(Chevron, { className: "ui-text-slate-300" }),
1088
- React__namespace.default.createElement(Title, { text: title, type: "xs" })
1098
+ isString__default.default(title) ? React__namespace.default.createElement(Title, { type: "xs" }, title) : title
1089
1099
  ),
1090
1100
  React__namespace.default.createElement("div", { className: classNames__default.default({ "ui-hidden": isCollapsed }) }, content)
1091
1101
  );
1092
1102
  };
1093
1103
 
1094
- const Accordion = ({ dataTestId, isCollapsedByDefault = false, items, className }) => {
1104
+ const Accordion = ({ dataTestId, isCollapsedByDefault = false, items, className, footer }) => {
1095
1105
  var _a;
1096
- return React__namespace.default.createElement("ul", { "data-test-id": dataTestId, className }, (_a = items.map) === null || _a === void 0 ? void 0 : _a.call(items, (item) => {
1097
- var _a2;
1098
- return React__namespace.default.createElement(AccordionItem, Object.assign({}, item, { key: item.id, isCollapsedByDefault: (_a2 = item.isCollapsedByDefault) !== null && _a2 !== void 0 ? _a2 : isCollapsedByDefault }));
1099
- }));
1106
+ return React__namespace.default.createElement(
1107
+ "ul",
1108
+ { "data-test-id": dataTestId, className },
1109
+ (_a = items.map) === null || _a === void 0 ? void 0 : _a.call(items, (item) => {
1110
+ var _a2;
1111
+ return React__namespace.default.createElement(AccordionItem, Object.assign({}, item, { key: item.id, isCollapsedByDefault: (_a2 = item.isCollapsedByDefault) !== null && _a2 !== void 0 ? _a2 : isCollapsedByDefault }));
1112
+ }),
1113
+ footer && React__namespace.default.createElement("div", { className: "ui-p-4" }, footer)
1114
+ );
1100
1115
  };
1101
1116
 
1102
1117
  var css_248z$j = ".list-skeleton .skeleton-box {\n display: inline-block;\n height: 1em;\n position: relative;\n overflow: hidden;\n background-color: #cbd5e1;\n border-radius: 3px;\n}\n.list-skeleton .skeleton-box::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n transform: translateX(-100%);\n background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.2) 20%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0));\n animation: shimmer 800ms infinite;\n content: \"\";\n}\n.list-skeleton .skeleton-box.is-circle {\n border-radius: 50%;\n}\n.list-skeleton .skeleton-box.is-button {\n background-color: #e2e8f0;\n border-radius: 9999px;\n}\n.list-skeleton .skeleton-box.is-button::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n transform: translateX(-100%);\n background-image: linear-gradient(90deg, rgba(221, 221, 221, 0) 0, rgba(221, 221, 221, 0.2) 20%, rgba(221, 221, 221, 0.5) 60%, rgba(221, 221, 221, 0));\n animation: shimmer 800ms infinite;\n content: \"\";\n}\n@keyframes shimmer {\n 100% {\n transform: translateX(100%);\n }\n}";
@@ -5807,6 +5822,7 @@ WeekdaysPicker.weekdayNameToIndex = weekdayNameToIndex;
5807
5822
  WeekdaysPicker.indexToWeekdayName = indexToWeekdayName;
5808
5823
 
5809
5824
  exports.AccordionList = AccordionList;
5825
+ exports.AccordionTemporary = Accordion;
5810
5826
  exports.AddIcon = AddIcon;
5811
5827
  exports.AlertsIcon = BellIcon;
5812
5828
  exports.Avatar = Avatar;