From 2f912ff5669e55c55299db914fe40465c441b343 Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Fri, 18 Aug 2023 12:50:53 +0300 Subject: [PATCH] (feat) Apply some design changes (#178) --- .../dashboard/dashboard.component.tsx | 34 +++- src/components/dashboard/dashboard.scss | 53 ++++-- src/components/empty-state/empty-state.scss | 10 +- src/components/error-state/error-state.scss | 10 +- .../form-editor/form-editor.component.tsx | 175 +++++++++++++++--- src/components/form-editor/form-editor.scss | 30 ++- .../form-renderer/form-renderer.component.tsx | 60 +++--- .../form-renderer/form-renderer.scss | 5 +- .../draggable-question.scss | 19 +- .../interactive-builder/editable-value.scss | 1 - .../interactive-builder.component.tsx | 13 +- .../interactive-builder.scss | 10 +- .../interactive-builder/question-modal.scss | 26 +-- src/components/pagination/pagination.scss | 10 +- .../schema-editor/schema-editor.component.tsx | 152 +-------------- 15 files changed, 325 insertions(+), 283 deletions(-) diff --git a/src/components/dashboard/dashboard.component.tsx b/src/components/dashboard/dashboard.component.tsx index f5df7ee..6fbc618 100644 --- a/src/components/dashboard/dashboard.component.tsx +++ b/src/components/dashboard/dashboard.component.tsx @@ -61,6 +61,7 @@ type Mutator = KeyedMutator<{ type ActionButtonsProps = { form: FormType; mutate: Mutator; + responsiveSize: string; t: TFunction; }; @@ -89,7 +90,12 @@ function CustomTag({ condition }: { condition: boolean }) { ); } -function ActionButtons({ form, mutate, t }: ActionButtonsProps) { +function ActionButtons({ + form, + mutate, + responsiveSize, + t, +}: ActionButtonsProps) { const { clobdata } = useClobdata(form); const formResources = form?.resources; const [showDeleteFormModal, setShowDeleteFormModal] = useState(false); @@ -144,6 +150,7 @@ function ActionButtons({ form, mutate, t }: ActionButtonsProps) { kind={"ghost"} iconDescription={t("import", "Import")} hasIconOnly + size={responsiveSize} /> ); }; @@ -161,6 +168,7 @@ function ActionButtons({ form, mutate, t }: ActionButtonsProps) { kind={"ghost"} iconDescription={t("editSchema", "Edit schema")} hasIconOnly + size={responsiveSize} tooltipAlignment="start" /> ); @@ -179,6 +187,7 @@ function ActionButtons({ form, mutate, t }: ActionButtonsProps) { kind={"ghost"} iconDescription={t("downloadSchema", "Download schema")} hasIconOnly + size={responsiveSize} tooltipAlignment="start" /> @@ -194,6 +203,7 @@ function ActionButtons({ form, mutate, t }: ActionButtonsProps) { kind={"ghost"} iconDescription={t("deleteSchema", "Delete schema")} hasIconOnly + size={responsiveSize} tooltipAlignment="start" /> ); @@ -266,6 +276,7 @@ function ActionButtons({ form, mutate, t }: ActionButtonsProps) { function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { const config = useConfig(); const isTablet = useLayoutType() === "tablet"; + const responsiveSize = isTablet ? "lg" : "sm"; const [filter, setFilter] = useState(""); const [searchString, setSearchString] = useState(""); const pageSize = 10; @@ -329,7 +340,14 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { id: form?.uuid, published: , retired: , - actions: , + actions: ( + + ), })); const handlePublishStatusChange = ({ @@ -368,6 +386,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { titleText={ t("filterByPublishedStatus", "Filter by publish status") + ":" } + size={responsiveSize} type="inline" items={["All", "Published", "Unpublished"]} onChange={handlePublishStatusChange} @@ -380,7 +399,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { {({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => ( @@ -390,9 +409,13 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { data-testid="forms-table" >
- - + + @@ -400,6 +423,7 @@ function FormsList({ forms, isValidating, mutate, t }: FormsListProps) { kind="primary" iconDescription={t("createNewForm", "Create a new form")} renderIcon={(props) => } + size={responsiveSize} onClick={() => navigate({ to: `${window.spaBase}/form-builder/new`, diff --git a/src/components/dashboard/dashboard.scss b/src/components/dashboard/dashboard.scss index 68ef9a5..a4afb25 100644 --- a/src/components/dashboard/dashboard.scss +++ b/src/components/dashboard/dashboard.scss @@ -1,10 +1,10 @@ @use '@carbon/styles/scss/type'; @use '@carbon/styles/scss/spacing'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/styles/scss/colors'; .container { padding: 2rem; - background-color: $ui-01; + background-color: colors.$gray-10; } .backgroundDataFetchingIndicator { @@ -19,18 +19,49 @@ .toolbarWrapper { position: relative; display: flex; - height: spacing.$spacing-09; justify-content: flex-end; } +:global(.omrs-breakpoint-lt-desktop) { + .toolbarWrapper { + height: spacing.$spacing-09; + } +} + +:global(.omrs-breakpoint-gt-tablet) { + .toolbarWrapper { + height: spacing.$spacing-07; + } +} + .tableToolbar { width: 20%; min-width: 12.5rem; } -.table tr:last-of-type { +.headerContainer { + background-color: colors.$gray-10; +} + +.searchbox { + input { + outline: 2px solid colors.$orange-40 !important; + } +} + + +.table { + tr { + &:last-of-type { + td { + border-bottom: none; + } + } + } + td { - border-bottom: none; + padding-top: 0; + padding-bottom: 0; } } @@ -42,7 +73,7 @@ position: relative; display: flex; width: 100%; - min-height: 3rem; + min-height: 2rem; margin-bottom: 0.5rem; } @@ -57,7 +88,7 @@ padding: 0; :global(.cds--data-table-content) { - border: 1px solid $ui-03; + border: 1px solid colors.$gray-20; border-bottom: none; overflow: visible; } @@ -65,7 +96,7 @@ :global(.cds--table-toolbar) { position: relative; height: 2rem; - min-height: 0rem; + min-height: 0rem; overflow: visible; top: 0; } @@ -89,13 +120,13 @@ .content { @include type.type-style('heading-compact-02'); - color: $text-02; + color: colors.$gray-70; margin-bottom: 0.5rem; } .tileContainer { - background-color: $ui-02; - border-top: 1px solid $ui-03; + background-color: colors.$white-0; + border-top: 1px solid colors.$gray-70; padding: 5rem 0; } diff --git a/src/components/empty-state/empty-state.scss b/src/components/empty-state/empty-state.scss index c0b5275..988e2b4 100644 --- a/src/components/empty-state/empty-state.scss +++ b/src/components/empty-state/empty-state.scss @@ -1,6 +1,6 @@ +@use '@carbon/styles/scss/colors'; @use '@carbon/styles/scss/spacing'; @use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; .action { margin-bottom: spacing.$spacing-03; @@ -8,7 +8,7 @@ .content { @include type.type-style("heading-compact-01"); - color: $text-02; + color: colors.$gray-70; margin-top: spacing.$spacing-05; margin-bottom: spacing.$spacing-03; } @@ -16,14 +16,14 @@ .desktopHeading { h4 { @include type.type-style('heading-compact-02'); - color: $text-02; + color: colors.$gray-70; } } .tabletHeading { h4 { @include type.type-style('heading-03'); - color: $text-02; + color: colors.$gray-70; } } @@ -51,5 +51,5 @@ .tile { text-align: center; - border: 1px solid $ui-03; + border: 1px solid colors.$gray-70; } \ No newline at end of file diff --git a/src/components/error-state/error-state.scss b/src/components/error-state/error-state.scss index 5b4900b..5100224 100644 --- a/src/components/error-state/error-state.scss +++ b/src/components/error-state/error-state.scss @@ -1,6 +1,6 @@ +@use '@carbon/styles/scss/colors'; @use '@carbon/styles/scss/spacing'; @use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; .errorMessage { @include type.type-style("heading-compact-02"); @@ -12,20 +12,20 @@ .errorCopy { margin-bottom: spacing.$spacing-03; @include type.type-style("body-01"); - color: $text-02; + color: colors.$gray-70; } .desktopHeading { h4 { @include type.type-style('heading-compact-02'); - color: $text-02; + color: colors.$gray-70; } } .tabletHeading { h4 { @include type.type-style('heading-03'); - color: $text-02; + color: colors.$gray-70; } } @@ -45,5 +45,5 @@ .tile { text-align: center; - border: 1px solid $ui-03; + border: 1px solid colors.$gray-20; } diff --git a/src/components/form-editor/form-editor.component.tsx b/src/components/form-editor/form-editor.component.tsx index 794fa70..8aa0ab2 100644 --- a/src/components/form-editor/form-editor.component.tsx +++ b/src/components/form-editor/form-editor.component.tsx @@ -3,6 +3,7 @@ import { Button, Column, ComposedModal, + InlineLoading, InlineNotification, Form, Grid, @@ -18,10 +19,12 @@ import { import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { ExtensionSlot } from "@openmrs/esm-framework"; +import type { OHRIFormSchema } from "@openmrs/openmrs-form-engine-lib"; import type { Schema, RouteParams } from "../../types"; import { useClobdata } from "../../hooks/useClobdata"; import { useForm } from "../../hooks/useForm"; +import ActionButtons from "../action-buttons/action-buttons.component"; import FormRenderer from "../form-renderer/form-renderer.component"; import InteractiveBuilder from "../interactive-builder/interactive-builder.component"; import SchemaEditor from "../schema-editor/schema-editor.component"; @@ -49,11 +52,15 @@ const Error = ({ error, title }: ErrorProps) => { const FormEditor: React.FC = () => { const { t } = useTranslation(); const { formUuid } = useParams(); + const isNewSchema = !formUuid; const [schema, setSchema] = useState(); const [showDraftSchemaModal, setShowDraftSchemaModal] = useState(false); const { form, formError, isLoadingForm } = useForm(formUuid); const { clobdata, clobdataError, isLoadingClobdata } = useClobdata(form); const [status, setStatus] = useState("idle"); + const [stringifiedSchema, setStringifiedSchema] = useState( + schema ? JSON.stringify(schema, null, 2) : "" + ); const isLoadingFormOrSchema = formUuid && (isLoadingClobdata || isLoadingForm); @@ -87,17 +94,112 @@ const FormEditor: React.FC = () => { status, ]); + useEffect(() => { + setStringifiedSchema(JSON.stringify(schema, null, 2)); + }, [schema]); + const handleLoadDraftSchema = useCallback(() => { setShowDraftSchemaModal(false); const draftSchema = localStorage.getItem("formJSON"); setSchema(JSON.parse(draftSchema)); }, []); + const handleSchemaChange = useCallback((updatedSchema: string) => { + setStringifiedSchema(updatedSchema); + }, []); + const updateSchema = useCallback((updatedSchema) => { setSchema(updatedSchema); localStorage.setItem("formJSON", JSON.stringify(updatedSchema)); }, []); + const inputDummySchema = useCallback(() => { + const dummySchema: OHRIFormSchema = { + encounterType: "", + name: "Sample Form", + processor: "EncounterFormProcessor", + referencedForms: [], + uuid: "", + version: "1.0", + pages: [ + { + label: "First Page", + sections: [ + { + label: "A Section", + isExpanded: "true", + questions: [ + { + label: "A Question of type obs that renders a text input", + type: "obs", + questionOptions: { + rendering: "text", + concept: "a-system-defined-concept-uuid", + }, + id: "sampleQuestion", + }, + ], + }, + { + label: "Another Section", + isExpanded: "true", + questions: [ + { + label: + "Another Question of type obs whose answers get rendered as radio inputs", + type: "obs", + questionOptions: { + rendering: "radio", + concept: "system-defined-concept-uuid", + answers: [ + { + concept: "another-system-defined-concept-uuid", + label: "Choice 1", + conceptMappings: [], + }, + { + concept: "yet-another-system-defined-concept-uuid", + label: "Choice 2", + conceptMappings: [], + }, + { + concept: "yet-one-more-system-defined-concept-uuid", + label: "Choice 3", + conceptMappings: [], + }, + ], + }, + id: "anotherSampleQuestion", + }, + ], + }, + ], + }, + ], + }; + + setStringifiedSchema(JSON.stringify(dummySchema, null, 2)); + updateSchema({ ...dummySchema }); + }, [updateSchema]); + + const [invalidJsonErrorMessage, setInvalidJsonErrorMessage] = useState(""); + + const resetErrorMessage = useCallback(() => { + setInvalidJsonErrorMessage(""); + }, []); + + const renderSchemaChanges = useCallback(() => { + resetErrorMessage(); + + try { + const parsedJson: Schema = JSON.parse(stringifiedSchema); + updateSchema(parsedJson); + setStringifiedSchema(JSON.stringify(parsedJson, null, 2)); + } catch (error) { + setInvalidJsonErrorMessage(error.message); + } + }, [stringifiedSchema, updateSchema, resetErrorMessage]); + const DraftSchemaModal = () => { return ( { {showDraftSchemaModal && } - - - {t("schemaEditor", "Schema Editor")} - - - - <> - {formError ? ( - - ) : null} - {clobdataError ? ( - - ) : null} - - - - - +
+ {isLoadingFormOrSchema ? ( + + ) : null} + + {isNewSchema && !schema ? ( + + ) : null} + + {schema ? ( + + ) : null} +
+
+ + {t("schemaEditor", "Schema Editor")} + + {formError ? ( + + ) : null} + {clobdataError ? ( + + ) : null} +
+ +
+
+ {t("preview", "Preview")} diff --git a/src/components/form-editor/form-editor.scss b/src/components/form-editor/form-editor.scss index 21d1bed..cdba4aa 100644 --- a/src/components/form-editor/form-editor.scss +++ b/src/components/form-editor/form-editor.scss @@ -1,6 +1,6 @@ +@use "@carbon/styles/scss/colors"; @use "@carbon/styles/scss/spacing"; @use "@carbon/styles/scss/type"; -@import '~@openmrs/esm-styleguide/src/vars'; .container { padding: 2rem; @@ -10,7 +10,7 @@ .breadcrumbsContainer { nav { - background-color: $ui-02; + background-color: colors.$white-0; } } @@ -22,7 +22,7 @@ max-width: 100%; :global(.cds--tabs__nav-item--selected) { - border-bottom: 2px solid var(--cds-border-interactive, #005d5d); + border-bottom: 2px solid var(--cds-border-interactive, colors.$teal-70); outline: none !important; } } @@ -37,3 +37,27 @@ margin: 0; padding: 0; } + +.actionButtons { + display: flex; + align-items: center; + justify-content: flex-end; + margin: 1rem 0; + + button { + margin-left: 1rem + } +} + +.editorContainer { + padding: 1rem; +} + +.tabHeading { + display: flex; + align-items: center; + @include type.type-style('heading-compact-01'); + min-height: 2.5rem; + width: 100%; + padding: 0.75rem; +} diff --git a/src/components/form-renderer/form-renderer.component.tsx b/src/components/form-renderer/form-renderer.component.tsx index ecaceaa..64f3082 100644 --- a/src/components/form-renderer/form-renderer.component.tsx +++ b/src/components/form-renderer/form-renderer.component.tsx @@ -3,21 +3,19 @@ import { ErrorBoundary } from "react-error-boundary"; import { useTranslation } from "react-i18next"; import { Button, InlineLoading, Tile } from "@carbon/react"; import { OHRIFormSchema, OHRIForm } from "@openmrs/openmrs-form-engine-lib"; - -import ActionButtons from "../action-buttons/action-buttons.component"; import styles from "./form-renderer.scss"; +type ErrorFallbackProps = { + error: Error; + resetErrorBoundary: () => void; +}; + type FormRendererProps = { isLoading: boolean; onSchemaChange?: (schema: OHRIFormSchema) => void; schema: OHRIFormSchema; }; -type ErrorFallbackProps = { - error: Error; - resetErrorBoundary: () => void; -}; - const FormRenderer: React.FC = ({ isLoading, schema }) => { const { t } = useTranslation(); @@ -72,34 +70,26 @@ const FormRenderer: React.FC = ({ isLoading, schema }) => { } return ( - <> - - -
- {!schema && ( - -

- {t("noSchemaLoaded", "No schema loaded")} -

-

- {t( - "formRendererHelperText", - "Load a form schema in the Schema Editor to the left to see it rendered here by the Form Engine." - )} -

-
- )} - {schema === schemaToRender && ( - - - - )} -
- +
+ {!schema && ( + +

+ {t("noSchemaLoaded", "No schema loaded")} +

+

+ {t( + "formRendererHelperText", + "Load a form schema in the Schema Editor to the left to see it rendered here by the Form Engine." + )} +

+
+ )} + {schema === schemaToRender && ( + + + + )} +
); }; diff --git a/src/components/form-renderer/form-renderer.scss b/src/components/form-renderer/form-renderer.scss index 5bdd0e5..c21cf7e 100644 --- a/src/components/form-renderer/form-renderer.scss +++ b/src/components/form-renderer/form-renderer.scss @@ -1,10 +1,9 @@ @use '@carbon/styles/scss/colors'; @use '@carbon/styles/scss/spacing'; @use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; .container { - background-color: white; + background-color: colors.$white-0; padding: 1rem; overflow-y: scroll; height: 100vh; @@ -12,7 +11,7 @@ .loadingContainer { @extend .container; - background-color: $ui-01; + background-color: colors.$gray-10; margin-top: 5rem; } diff --git a/src/components/interactive-builder/draggable-question.scss b/src/components/interactive-builder/draggable-question.scss index b2c2dcd..e2d4589 100644 --- a/src/components/interactive-builder/draggable-question.scss +++ b/src/components/interactive-builder/draggable-question.scss @@ -1,6 +1,6 @@ -@use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/colors'; @use '@carbon/styles/scss/spacing'; -@import '~@openmrs/esm-styleguide/src/vars'; +@use '@carbon/styles/scss/type'; .buttonsContainer { display: flex; @@ -12,17 +12,20 @@ align-items: center; } +.questionLabel { + @include type.type-style('body-01'); +} + .isDragged, .normal { display: flex; height: 3rem; justify-content: space-between; align-items: center; - margin: spacing.$spacing-02 spacing.$spacing-03; width: 100%; } .normal:hover { - background-color: $ui-03; + background-color: colors.$gray-70; } .isDragged { @@ -32,18 +35,16 @@ .dragIconContainer { cursor: pointer; - margin-right: spacing.$spacing-03; - - :hover { - background-color: $ui-02; + background-color: colors.$white-0; } } + .dragIcon { margin: 0 spacing.$spacing-03; padding: 0.25rem; - color: $ui-04; + color: colors.$gray-50; margin-top: 0.2rem; width: 1.5rem; height: 1.5rem; diff --git a/src/components/interactive-builder/editable-value.scss b/src/components/interactive-builder/editable-value.scss index 55034d9..32d12db 100644 --- a/src/components/interactive-builder/editable-value.scss +++ b/src/components/interactive-builder/editable-value.scss @@ -1,5 +1,4 @@ @use '@carbon/styles/scss/type'; -@import '~@openmrs/esm-styleguide/src/vars'; .schemaLabel { @include type.type-style('heading-03'); diff --git a/src/components/interactive-builder/interactive-builder.component.tsx b/src/components/interactive-builder/interactive-builder.component.tsx index 8284561..4c2173c 100644 --- a/src/components/interactive-builder/interactive-builder.component.tsx +++ b/src/components/interactive-builder/interactive-builder.component.tsx @@ -14,7 +14,6 @@ import { } from "@dnd-kit/core"; import type { Question, RouteParams, Schema } from "../../types"; -import ActionButtons from "../action-buttons/action-buttons.component"; import AddQuestionModal from "./add-question-modal.component"; import DeleteSectionModal from "./delete-section-modal.component"; import DeletePageModal from "./delete-page-modal.component"; @@ -42,7 +41,7 @@ const InteractiveBuilder: React.FC = ({ }) => { const mouseSensor = useSensor(MouseSensor, { activationConstraint: { - distance: 10, // Enable sort function when dragging 10px 💡 here!!! + distance: 10, // Enable sort function when dragging 10px 💡 here!!! }, }); const keyboardSensor = useSensor(KeyboardSensor); @@ -301,8 +300,6 @@ const InteractiveBuilder: React.FC = ({ /> ) : null} - - {showNewFormModal ? ( = ({

@@ -559,7 +556,7 @@ const InteractiveBuilder: React.FC = ({ - ) : null} - - {schema ? ( - - ) : null} - - {invalidJsonErrorMessage ? (

@@ -173,7 +35,7 @@ const SchemaEditor: React.FC = ({ mode="json" theme="textmate" name="schemaEditor" - onChange={handleSchemaChange} + onChange={onSchemaChange} fontSize={15} showPrintMargin={false} showGutter={true}