Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const path = require('path');

module.exports = {
stories: ['../stories/**/*.stories.js'],
stories: ['../stories/**/*.stories.js', '../stories/**/*.mdx'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
'^src/(.*)$': '<rootDir>/src/$1',
'^components/(.*)$': '<rootDir>/src/components/$1',
'^helpers/(.*)$': '<rootDir>/src/helpers/$1',
'@carbon/styles/css/styles\\.css$': 'identity-obj-proxy',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'test-file-stub'
},
Expand Down
50 changes: 26 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,37 @@
},
"author": "bahmni@thoughtworks.com",
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@bahmni/design-system": ">=0.0.1-dev.215",
"@carbon/react": ">=1.0.0"
"@carbon/react": ">=1.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"storybook": "^8.4.0",
"@storybook/react": "^8.4.0",
"@storybook/react-webpack5": "^8.4.0",
"@storybook/addon-actions": "^8.4.0",
"@storybook/addon-essentials": "^8.4.0",
"@storybook/addon-interactions": "^8.4.0",
"@storybook/blocks": "^8.4.0",
"@storybook/test": "^8.4.0",
"@bahmni/design-system": ">=0.0.1-dev.215",
"@babel/cli": "^7.28.3",
"@babel/core": "^7.28.4",
"@babel/eslint-parser": "^7.28.4",
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@babel/runtime": "^7.28.0",
"@bahmni/design-system": ">=0.0.1-dev.215",
"@carbon/icons-react": "^11.0.0",
"@carbon/layout": "^11.0.0",
"@carbon/react": "^1.0.0",
"@fortawesome/fontawesome-free": "^7.1.0",
"@storybook/addon-actions": "^8.4.0",
"@storybook/addon-backgrounds": "^8.4.0",
"@storybook/addon-controls": "^8.4.0",
"@storybook/addon-docs": "^8.4.0",
"@storybook/addon-essentials": "^8.4.0",
"@storybook/addon-interactions": "^8.4.0",
"@storybook/addon-measure": "^8.4.0",
"@storybook/addon-outline": "^8.4.0",
"@storybook/addon-toolbars": "^8.4.0",
"@storybook/addon-viewport": "^8.4.0",
"@storybook/blocks": "^8.4.0",
"@storybook/react": "^8.4.0",
"@storybook/react-webpack5": "^8.4.0",
"@storybook/test": "^8.4.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.1.0",
Expand All @@ -65,23 +75,27 @@
"eslint-plugin-react": "^7.37.5",
"fetch-mock": "^5.1.2",
"file-loader": "^0.11.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"mini-css-extract-plugin": "^2.7.6",
"polished": "^4.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-json-syntax-highlighter": "^0.1.15",
"react-json-tree": "^0.10.0",
"rimraf": "^2.5.4",
"sass": "^1.92.1",
"sass-loader": "^13.3.2",
"storybook": "^8.4.0",
"style-loader": "^3.3.3",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"ajv": "^8.17.1",
"babel-runtime": "^6.26.0",
"base64-inline-loader": "^1.1.0",
"classnames": "^2.2.5",
"immutable": "4.3.8",
Expand All @@ -94,18 +108,6 @@
"sinon-as-promised": "^4.0.3",
"whatwg-fetch": "^1.0.0"
},
"resolutions": {
"**/**/lodash": "^4.17.12",
"**/**/lodash-es": "^4.17.14",
"**/**/mixin-deep": "^1.3.2",
"**/**/set-value": "^2.0.1",
"**/**/decompress": "^4.2.1",
"**/**/xmlhttprequest-ssl": "^1.6.1",
"**/**/open": "^6.0.0",
"**/**/lodash.template": "^4.5.0",
"**/**/growl": "^1.10.0",
"**/**/handlebars": "^4.7.7"
},
"files": [
"dist/",
"README.md",
Expand Down
9 changes: 5 additions & 4 deletions stories/AbnormalObsControl.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import { List } from 'immutable';
import StoryWrapper from './StoryWrapper';
import { registerCoreComponents } from './componentRegistry';
import { pulseDataMetadata } from './mockData';
import { description, argTypes } from './_meta/abnormalObsControlMeta';

registerCoreComponents();

export default {
title: 'Complex Controls/AbnormalObsControl',
component: ObsGroupControl,
tags: ['autodocs'],
argTypes,
parameters: {
docs: {
description: {
component:
'AbnormalObsControl demonstrates an ObsGroup that pairs a numeric observation with a ' +
'Boolean "Abnormal" toggle button. The toggle is rendered inline using the Button display ' +
'type and lets clinicians flag an out-of-range reading without leaving the field.',
component: description,
},
},
},
Expand Down
19 changes: 5 additions & 14 deletions stories/Forms.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import StoryWrapper from './StoryWrapper';
import { Container } from 'src/components/Container.jsx';
import { runEventScript } from 'src/helpers/runEventScript';
import { SYSTOLIC_UUID, DIASTOLIC_UUID } from './mockData';
import { description, argTypes } from './_meta/formsMeta';

const form = {
id: 1,
Expand Down Expand Up @@ -163,23 +164,13 @@ const obsList = [

export default {
title: 'Example Forms/Lifecycle & Events',
component: Container,
tags: ['autodocs'],
argTypes,
parameters: {
docs: {
description: {
component: `
A guided tour of the form lifecycle. Each story documents one event or method exposed
by the form engine and shows it working on a real form.

**Stories on this page:**

- **Basic Data Binding** — foundation: how the Container renders pre-populated observations.
- **Form Lifecycle Demo** — all six lifecycle events on one interactive form with a live
Event Log: onFormInit, onValueChange, onValueUpdated, getValue(), onFormSave, Submit/Reset.
- **Event Flow Diagram** — the complete init → change → submit → post-save timeline.
- **Handler Templates** — copy-pasteable handler snippets for the most common patterns.
- **Accessibility for Events** — keyboard and assistive-technology considerations when
events update the form dynamically.
`,
component: description,
},
},
},
Expand Down
9 changes: 5 additions & 4 deletions stories/MultiSelect.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import StoryWrapper from './StoryWrapper';
import { Container } from 'src/components/Container.jsx';
import { description, argTypes } from './_meta/multiSelectMeta';

const form = {
id: 1,
Expand Down Expand Up @@ -321,13 +322,13 @@ const obsList = [

export default {
title: 'Atomic Controls/MultiSelect',
component: Container,
tags: ['autodocs'],
argTypes,
parameters: {
docs: {
description: {
component:
'Demonstrates a form containing a multi-select coded observation (Tuberculosis Comorbidity). ' +
'Multi-select obs allow clinicians to choose multiple answers for a single concept and store ' +
'each answer as a separate observation record.',
component: description,
},
},
},
Expand Down
30 changes: 4 additions & 26 deletions stories/ObsControl.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ObsControlWithIntl as ObsControl } from 'src/components/ObsControl.jsx'
import StoryWrapper from './StoryWrapper';
import '../styles/styles.scss';
import { SYSTOLIC_UUID, DIASTOLIC_UUID } from './mockData';
import { description, argTypes } from './_meta/obsControlMeta';

const form = {
controls: [
Expand Down Expand Up @@ -106,35 +107,12 @@ const emptyValue = { value: undefined, comment: undefined, interpretation: undef
export default {
title: 'Complex Controls/ObsControl',
component: ObsControl,
argTypes: {
validate: { control: 'boolean', description: 'Trigger field-level validation' },
validateForm: { control: 'boolean', description: 'Trigger form-level validation' },
onValueChanged: { action: 'onValueChanged' },
showNotification: { action: 'showNotification' },
onControlAdd: { action: 'onControlAdd' },
onControlRemove: { action: 'onControlRemove' },
},
tags: ['autodocs'],
argTypes,
parameters: {
docs: {
description: {
component:
'ObsControl is the primary observation control that binds a single concept to a form field. ' +
'It wraps the underlying input widget (NumericBox, TextBox, BooleanControl, CodedControl, etc.) ' +
'based on the concept datatype resolved at render time via componentStore.\n\n' +
'**Concept binding**: The `metadata.concept` object identifies which OpenMRS concept is being ' +
'captured. The `concept.datatype` drives which widget renders (Numeric → NumericBox, ' +
'Text → TextBox, Boolean → BooleanControl / Button, Coded → AutoComplete or DropDown).\n\n' +
'**Value wrapping**: ObsControl receives and emits values as `{ value, comment, interpretation }` ' +
'objects. When a user changes the input the `onValueChanged(formFieldPath, value, errors)` ' +
'callback is fired with the updated wrapped value so the parent Container can update its ' +
'ControlRecordTree.\n\n' +
'**Add More**: When `properties.addMore` is true, the control renders Add / Remove buttons ' +
'via AddMoreDecorator, allowing the clinician to capture repeated observations for the same ' +
'concept within a single encounter.\n\n' +
'Accessibility (WCAG 2.1 AA): Each input is associated with a `<label>` via `htmlFor` / `id` ' +
'pairing. Mandatory fields include `aria-required="true"`. Validation error messages are ' +
'rendered adjacent to the field and referenced with `aria-describedby` so screen readers ' +
'announce them immediately. Keyboard navigation is fully supported for all widget types.',
component: description,
},
},
},
Expand Down
35 changes: 4 additions & 31 deletions stories/Section.stories.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* global componentStore */
import React from 'react';
import StoryWrapper from './StoryWrapper';
import { Container } from 'src/components/Container.jsx';
import { Section } from 'src/components/Section.jsx';
import { registerCoreComponents } from './componentRegistry';
import { description, argTypes } from './_meta/sectionMeta';

registerCoreComponents();
componentStore.registerComponent('section', Section);
Expand Down Expand Up @@ -159,39 +159,12 @@ const nestedSectionsMetadata = {
export default {
title: 'Complex Controls/Section',
component: Container,
argTypes: {
collapse: { control: 'boolean', description: 'Render the section in its collapsed state' },
validate: { control: 'boolean', description: 'Trigger field-level validation' },
validateForm: { control: 'boolean', description: 'Trigger form-level validation' },
},
tags: ['autodocs'],
argTypes,
parameters: {
docs: {
description: {
component:
'Section renders a collapsible `<fieldset>` that visually and semantically groups ' +
'related form controls under a single heading (legend). It is used to organise ' +
'observations into logical panels on complex clinical forms.\n\n' +
'**Collapse / expand**: Clicking the legend header toggles `state.collapse`. ' +
'When collapsed, child controls are hidden via the `closing-group-controls` CSS class ' +
'without unmounting, preserving any partially entered data.\n\n' +
'**Nested sections**: A Section\'s `controls` array can contain other sections, ' +
'enabling multi-level hierarchical form layouts. Each level renders its own ' +
'`<fieldset>` / `<legend>` pair.\n\n' +
'**Rendering via Container**: Section is not rendered directly in production — the ' +
'`Container` component reads the form metadata tree, resolves `type: "section"` via ' +
'`componentStore`, and passes the appropriate child record props down. ' +
'These stories use `Container` to replicate that full rendering pipeline.\n\n' +
'Accessibility (WCAG 2.1 AA): The `<fieldset>` + `<legend>` combination provides a ' +
'native grouping landmark recognised by all screen readers. The collapse toggle is on ' +
'the `<legend>` element so it is naturally focusable and operable via keyboard. ' +
'Disabled sections add the `disabled` CSS class and all child inputs honour the ' +
'`enabled` prop to prevent data entry.\n\n' +
'**Composition chain**: The full atomic-to-container hierarchy is: ' +
'`Container` → resolves `type: "section"` via `componentStore` → renders `Section` → ' +
'each child `type: "obsControl"` is resolved to `ObsControl` → `ObsControl` resolves ' +
'the concept datatype to an atomic widget (`NumericBox`, `TextBox`, `BooleanControl`, etc.). ' +
'Each layer passes `formFieldPath`, `value`, and `onValueChanged` down so state flows ' +
'back up to the root `Container` and its `ControlRecordTree`.',
component: description,
},
},
},
Expand Down
19 changes: 17 additions & 2 deletions stories/StoryWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,17 @@ const headerStyle = {
export default class StoryWrapper extends PureComponent {

render() {
const { title, children, json } = this.props;
const { title, children, json, showDebug } = this.props;
if (!showDebug) {
return (
<div style={styles.wrap}>
<div>
{title && <div style={headerStyle}>{title}</div>}
{children}
</div>
</div>
);
}
return (<div style={styles.wrap}>
<div style={styles.box}>
{title && <div style={headerStyle}>{title}</div>}
Expand All @@ -64,6 +74,11 @@ export default class StoryWrapper extends PureComponent {
}

StoryWrapper.propTypes = {
json: PropTypes.object.isRequired,
json: PropTypes.object,
title: PropTypes.string,
showDebug: PropTypes.bool,
};

StoryWrapper.defaultProps = {
showDebug: false,
};
Loading
Loading