A button that opens a modal info dialog when clicked.
import { Icon, IconButton } from "@material-ui/core";
import { DialogButton } from "@eyeseetea/d2-ui-components";
const MyDialogHandler = () => (
<DialogButton
title="Help"
contents="This is some help message"
buttonComponent={
({ onClick }) => (
<IconButton tooltip="Help" onClick={onClick}>
<Icon color="primary">help</Icon>
</IconButton>
)
}
/>
);
A wrapper that creates all the logic needed to build a modal dialog.
import { ConfirmationDialog } from "@eyeseetea/d2-ui-components";
const MyDialog = () => (
<ConfirmationDialog
isOpen={this.dialogOpen}
onSave={this.handleDialogConfirm}
onCancel={this.handleDialogCancel}
title={"Delete Instance?"}
description={"Are you sure you want to delete this instance?"}
saveText={"Ok"}
/>
);
import { DatePicker } from "@eyeseetea/d2-ui-components";
const MyDatePicker = () => (
<DatePicker
label="Label of the TextInput component"
value={new Date("2019/01/30")}
onChange={newValue => console.log(newValue)}
/>
);
import { MultipleSelector } from "@eyeseetea/d2-ui-components";
const MyMultipleSelector = () => (
<MultiSelector
d2={d2}
height={300}
onChange={values => console.log("New selected values", values)}
options={[{text: "Option 1", value: "id1"}, {text: "Option 2", value: "id2"}]}
selected={["id1"]}
ordered={true}
/>
);
Visually similar to material-ui checkbox but much lighter, useful when you have lots of checks in a page.
import { SimpleCheckBox } from "@eyeseetea/d2-ui-components";
const MySimpleCheckBox = () => (
<SimpleCheckBox
checked={true}
onClick={values => console.log("Checkbox was clicked")}
/>
);
import { OrgUnitsSelector } from "@eyeseetea/d2-ui-components";
const MyOrgUnitsSelector = () => (
<OrgUnitsSelector
api={api}
onChange={orgUnitsPaths => console.log("Selected orgUnitPaths", orgUnitsPaths)}
selected={["/ImspTQPwCqd/O6uvpzGd5pu", "/ImspTQPwCqd/PMa2VCrupOd"]}
levels={[1, 2]}
rootIds={["ImspTQPwCqd"]}
listParams={{ maxLevel: 4 }}
withElevation={false}
typeInput="radio"
selectableLevels=[1,3] // the biggest selectableLevel has not children in OrgUnitTree
controls={filterByLevel: false, filterByGroup: true, selectAll: true)
/>
);
There should be a unique snackbar for the whole app, so we need to insert a single provider in the main component:
import { SnackbarProvider } from "@eyeseetea/d2-ui-components";
const MyAppWithSnackbar = (
<SnackbarProvider>
<MyApp />
</SnackbarProvider>
);
To use it, create a HOC with withSnackbar
, add snackbar
to your propTypes, and show messages using the functions props.snackbar[.level]
. Levels supported: success, error, info, warning.
import { withSnackbar } from "@eyeseetea/d2-ui-components";
import PropTypes from "prop-types";
const MyComponent = ({name, snackbar}) => (
<div>
<a onClick={() => snackbar.success(name)}>Success</a>
<a onClick={() => snackbar.error(name)}>Error</a>
<a onClick={() => snackbar.info(name)}>Info</a>
<a onClick={() => snackbar.warning(name)}>Warning</a>
</div>
);
MyComponent.propTypes = {
name: PropTypes.object.isRequired,
snackbar: PropTypes.object.isRequired,
}
export default withSnackbar(MyComponent);
There should be a unique loading mask for the whole app, so we need to insert a single provider in the main component:
import { LoadingProvider } from "@eyeseetea/d2-ui-components";
const MyAppWithLoadingMask = (
<LoadingProvider>
<MyApp />
</LoadingProvider>
);
To use it, create a HOC with withLoading
, add loading
to your propTypes, and show the mask using the functions props.loading.show()
.
import { withLoading } from "@eyeseetea/d2-ui-components";
import PropTypes from "prop-types";
const MyComponent = ({name, loading}) => (
<div>
<a onClick={() => loading.show()}>Show loading mask</a>
<a onClick={() => loading.show(false)}>Hide loading mask</a>
<a onClick={() => loading.hide()}>Also hides loading mask</a>
<a onClick={() => loading.show(true, 'Message', 35)}>Show loading mask with extra information</a>
<a onClick={() => loading.updateMessage('String')}>Update message</a>
<a onClick={() => loading.updateProgress(98)}>Update progress</a>
<a onClick={() => loading.reset()}>Hide and clear message/progress</a>
</div>
);
MyComponent.propTypes = {
name: PropTypes.object.isRequired,
loading: PropTypes.object.isRequired,
}
export default withLoading(MyComponent);
Display D2 objects in a table with:
- Sortable columns.
- Filters: By default, it shows a search bar to filter by name, custom filters can be added.
- Details sidebar.
- Configurable context actions.
- Pagination.
- Create button action.
Whenever you want to update the objects table, pass a different key
prop (i.e new Date()
), as you would do with any other React component.
The visibility of the float action button depends on the onButtonClick prop. If a function is defined it will be visible, otherwise, if the prop is undefined it will be hidden.
const columns = [
{ name: "displayName", text: i18n.t("Name"), sortable: true },
{ name: "publicAccess", text: i18n.t("Public access"), sortable: true },
{ name: "lastUpdated", text: i18n.t("Last updated"), sortable: true },
];
const detailsFields = [
{ name: "displayName", text: i18n.t("Name") },
{ name: "code", text: i18n.t("Code") },
{ name: "href", text: i18n.t("API link") },
];
const actions = [
{
name: "details",
text: i18n.t("Details"),
multiple: false,
type: "details",
},
{
name: "delete",
text: i18n.t("Delete"),
multiple: true,
},
];
const CustomFilters = (
<Checkbox ... />
);
const MyObjectsTable = () => (
<ObjectsTable
d2={d2}
model={d2.models.dataSet}
columns={columns}
detailsFields={detailsFields}
pageSize={20}
initialSorting={["displayName", "asc"]}
actions={actions}
onButtonClick={() => console.log("Floating button clicked")}
buttonLabel={someString || <SomeComponent >}
list={list} // list(d2, filters, pagination) -> {pager, objects}
customFiltersComponent={CustomFilters}
customFilters={{key1: "value1", key2: "value2}}
onSelectionChange={objectIds => console.log(objectIds)}
hideSearchBox={false}
/>
);
Display a Step by Step customizable wizard
const getStepsBaseInfo = [
{
key: "general-info",
label: "General info",
component: GeneralInfoStep,
validationKeys: ["name"],
description: "Description for a wizard step",
help: "Help text",
},
{
key: "summary",
label: "Summary",
component: SaveStep,
validationKeys: [],
description: undefined,
help: undefined,
},
];
onStepChangeRequest = async currentStep => {
return getValidationMessages(
currentStep.validationKeys
);
};
const MyWizard = props => {
const steps = getStepsBaseInfo.map(step => ({
...step,
props: {
onCancel: () => console.log("User wants to cancel the wizard!"),
},
}));
const urlHash = props.location.hash.slice(1);
const stepExists = steps.find(step => step.key === urlHash);
const firstStepKey = steps.map(step => step.key)[0];
const initialStepKey = stepExists ? urlHash : firstStepKey;
const lastClickableStepIndex = props.isEdit ? steps.length - 1 : 0;
return (
<Wizard
useSnackFeedback={true}
onStepChangeRequest={onStepChangeRequest}
initialStepKey={initialStepKey}
lastClickableStepIndex={lastClickableStepIndex}
steps={steps}
/>
);
};
import React from "react";
import { D2Api } from "d2-api";
import { Sharing, MetaObject } from "@eyeseetea/d2-ui-components";
const initialSharedObject: MetaObject = {
meta: {
allowPublicAccess: true,
allowExternalAccess: true,
},
object: {
id: "uKLXQPfYBQB",
displayName: "My data set",
user: { id: "M5zQapPyTZI", name: "Tom Waikiki" },
publicAccess: "rwrw----",
externalAccess: false,
userAccesses: [{ id: "G1zcewaT5b", displayName: "John Traore", access: "rwrw----" }],
},
};
const showOptions = {
dataSharing: true,
publicSharing: true,
externalSharing: true,
permissionPicker: true,
};
function searchUsers(api: D2Api, query: string) {
const options = {
fields: { id: true, displayName: true },
filter: { displayName: { ilike: query } },
};
return api.metadata.get({ users: options, userGroups: options }).getData();
}
const MySharingComponent: React.FC<{api: D2Api}> = ({ api }) => {
const [sharedObject, setSharedObject] = React.useState(initialSharedObject);
const search = React.useCallback((query: string) => searchUsers(api, query), [api]);
return (
<Sharing
meta={sharedObject}
showOptions={showOptions}
onSearch={search}
onChange={(newSharing, onSuccess) => {
setSharedObject({
...sharedObject,
object: { ...sharedObject.object, ...newSharing },
});
if (onSuccess) onSuccess();
}}
/>
);
};
$ yarn install
Run tests, linter and prettier:
$ yarn code-quality
To publish a new package to npmjs:
$ yarn build
Then run for alpha channel:
$ yarn publish build/ --new-version VERSION
or the following command for the beta channel:
$ yarn publish build/ --tag beta --new-version VERSION
-
Import i18n from utils module (
import i18n from "../utils/i18n";
) then use it:i18n.t("Some literal")
. This uses@dhis2/i18n
infrastructure with namespaced2-ui-components
. -
Do not call
i18n.t("Some literal")
on module or class level, only within functions. At that point, the locale is not yet selected, and you'll get always the default English translations.
$ yarn update-po
# ... add/edit translations in po files ...
$ yarn localize
$ cp i18n/en.pot i18n/es.po
# ... add translations to i18n/es.po ...
$ yarn localize
In d2-ui-components, only the first time:
$ yarn build
$ (cd build && yarn link)
When starting development:
$ yarn build-watch
In the main React application:
$ yarn link "@eyeseetea/d2-ui-components"