Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accordion: add error labels in title #276

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
119 changes: 74 additions & 45 deletions src/lib/forms/AccordionField.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,85 @@
// This file is part of React-Invenio-Forms
// Copyright (C) 2020 CERN.
// Copyright (C) 2020 Northwestern University.
//
// React-Invenio-Forms is free software; you can redistribute it and/or modify it
// under the terms of the MIT License; see LICENSE file for more details.

import React, { Component, useState } from "react";
import PropTypes from "prop-types";
import { Field, FastField } from "formik";
import { Accordion, Container, Icon } from "semantic-ui-react";
import { Accordion, Container, Icon, Label } from "semantic-ui-react";
import _omit from "lodash/omit";
import _get from "lodash/get";

export class AccordionField extends Component {
hasError(errors, initialValues = undefined, values = undefined) {
hasError = (errors, initialValues = undefined, values = undefined) => {
const { includesPaths } = this.props;
for (const errorPath in errors) {
for (const subPath in errors[errorPath]) {
const path = `${errorPath}.${subPath}`;
if (
_get(initialValues, path, "") === _get(values, path, "") &&
includesPaths.includes(`${errorPath}.${subPath}`)
)
includesPaths.includes(path)
) {
return true;
}
}
}
return false;
}
};

flattenErrors = (errors) => {
let flattened = {};

const recurse = (obj, path = "") => {
if (Array.isArray(obj)) {
// For arrays, treat each item individually, appending the index to the path
obj.forEach((item, index) => {
recurse(item, `${path}[${index}]`);
});
} else if (typeof obj === "object" && obj !== null) {
// If it's an object, recursively traverse it
for (const key in obj) {
const newPath = path ? `${path}.${key}` : key;
recurse(obj[key], newPath);
}
} else {
// If it's a value, save the path and value
flattened[path] = obj;
}
};

recurse(errors);
return flattened;
};

countErrors = (errors, includePaths) => {
const flattenedErrors = this.flattenErrors(errors);
let count = 0;

// Count matching paths from includePaths
for (const path in flattenedErrors) {
if (includePaths.some((includePath) => path.startsWith(includePath))) {
count++;
}
}

return count;
};

renderAccordion = (props) => {
const {
form: { errors, status, initialErrors, initialValues, values },

Check warning on line 72 in src/lib/forms/AccordionField.js

View workflow job for this annotation

GitHub Actions / Tests (18.x)

'status' is assigned a value but never used

Check warning on line 72 in src/lib/forms/AccordionField.js

View workflow job for this annotation

GitHub Actions / Tests (16.x)

'status' is assigned a value but never used
} = props;
const { includesPaths, label, children, active } = this.props;

// eslint-disable-next-line no-unused-vars
const { label, children, active, ...ui } = this.props;
const uiProps = _omit({ ...ui }, ["optimized", "includesPaths"]);
const hasError = status
? this.hasError(status)
: this.hasError(errors) || this.hasError(initialErrors, initialValues, values);
const panels = [
{
key: `panel-${label}`,
title: {
content: label,
},
content: {
content: <Container>{children}</Container>,
},
},
];
const uiProps = _omit(this.props, ["optimized", "includesPaths"]);
const hasError =
this.hasError(errors, initialValues, values) || this.hasError(initialErrors);

const errorCount =
this.countErrors(errors, includesPaths) ||
this.countErrors(initialErrors, includesPaths);
const errorClass = hasError ? "error secondary" : "";
const [activeIndex, setActiveIndex] = useState(active ? 0 : -1);

Expand All @@ -64,34 +93,34 @@
className={`invenio-accordion-field ${errorClass}`}
{...uiProps}
>
{panels.map((panel, index) => (
<React.Fragment key={panel.key}>
<Accordion.Title
active={activeIndex === index}
index={index}
onClick={handleTitleClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleTitleClick(e, { index });
}
}}
tabIndex={0}
>
{panel.title.content}
<Icon name="angle right" />
</Accordion.Title>
<Accordion.Content active={activeIndex === index}>
{panel.content.content}
</Accordion.Content>
</React.Fragment>
))}
<Accordion.Title
active={activeIndex === 0}
index={0}
onClick={handleTitleClick}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
handleTitleClick(e, { index: 0 });
}
}}
tabIndex={0}
>
{label}
{errorCount > 0 && (
<Label size="tiny" circular negative className="error-label">
{errorCount} {errorCount === 1 ? "error" : "errors"}
</Label>
)}
<Icon name={activeIndex === 0 ? "angle down" : "angle right"} />
</Accordion.Title>
<Accordion.Content active={activeIndex === 0}>
<Container>{children}</Container>
</Accordion.Content>
</Accordion>
);
};

render() {
const { optimized } = this.props;

const FormikField = optimized ? FastField : Field;
return <FormikField name="" component={this.renderAccordion} />;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib/forms/widgets/custom_fields/CustomFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,15 @@ export class CustomFields extends Component {
paths,
displaySection = true,
section: sectionName,
id: sectionId,
} = section;
return displaySection ? (
<AccordionField
key={`section-${sectionName}`}
includesPaths={paths}
label={sectionName}
active
id={sectionId}
>
{fields}
</AccordionField>
Expand Down
9 changes: 8 additions & 1 deletion src/lib/forms/widgets/custom_fields/DiscoverFieldsSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,16 @@ export class DiscoverFieldsSection extends Component {
...Object.entries(tempFields).map(([key, value]) => value.key),
...recordFields,
];
const tempFieldsPaths = tempFields.map((item) => item.key);

return (
<AccordionField key="discover-fields" label="Domain specific fields" active>
<AccordionField
key="discover-fields"
includesPaths={tempFieldsPaths}
label="Domain specific fields"
active
id="domain-specific-fields-section"
>
{sections.map(({ fields, paths, ...sectionConfig }) => {
const recordCustomFields = this.getFieldsWithValues(fields);
if (_isEmpty(recordCustomFields)) {
Expand Down
Loading