Skip to content

Commit

Permalink
refactor: redesign quality evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas-C committed Oct 31, 2024
1 parent 3757ca0 commit 80b080a
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const AverageQualityEvaluation = ({ gradeAverage, nodeType }: Props) => {
<QualityEvaluationGrade
grade={gradeAverage.averageValue}
averageGrade={gradeAverage.averageValue.toFixed(1)}
ariaLabel={t("taxonomy.qualityDescription", {
tooltip={t("taxonomy.qualityDescription", {
nodeType: t(`taxonomy.${nodeType}`),
count: gradeAverage.count,
})}
Expand Down
27 changes: 1 addition & 26 deletions src/components/QualityEvaluation/QualityEvaluation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
*/

import { FieldHelperProps, FieldInputProps } from "formik";
import { CSSProperties } from "react";
import { useTranslation } from "react-i18next";
import styled from "@emotion/styled";
import { colors, spacing } from "@ndla/core";
import { IArticle, IUpdatedArticle } from "@ndla/types-backend/draft-api";
import { Node } from "@ndla/types-taxonomy";
import { Text } from "@ndla/typography";
import { gradeItemStyles, qualityEvaluationOptions } from "./QualityEvaluationForm";
import QualityEvaluationModal from "./QualityEvaluationModal";
import { ArticleFormType } from "../../containers/FormikForm/articleFormHooks";
import SmallQualityEvaluationGrade from "../../containers/StructurePage/resourceComponents/QualityEvaluationGrade";
Expand All @@ -25,11 +23,6 @@ const FlexWrapper = styled.div`
gap: ${spacing.xsmall};
`;

const LargeGradeItem = styled.div`
${gradeItemStyles}
cursor: default;
`;

const StyledNoEvaluation = styled(Text)`
color: ${colors.brand.greyMedium};
font-style: italic;
Expand All @@ -42,7 +35,6 @@ interface Props {
iconButtonColor?: "light" | "primary";
revisionMetaField?: FieldInputProps<ArticleFormType["revisionMeta"]>;
revisionMetaHelpers?: FieldHelperProps<ArticleFormType["revisionMeta"]>;
gradeVariant?: "small" | "large";
updateNotes?: (art: IUpdatedArticle) => Promise<IArticle>;
}

Expand All @@ -53,7 +45,6 @@ const QualityEvaluation = ({
iconButtonColor,
revisionMetaField,
revisionMetaHelpers,
gradeVariant = "large",
updateNotes,
}: Props) => {
const { t } = useTranslation();
Expand All @@ -67,23 +58,7 @@ const QualityEvaluation = ({
</Text>
{qualityEvaluation?.grade ? (
<>
{gradeVariant === "large" && (
<LargeGradeItem
title={qualityEvaluation?.note}
aria-label={qualityEvaluation?.note}
style={
{
"--item-color": qualityEvaluationOptions[qualityEvaluation.grade],
} as CSSProperties
}
data-border={qualityEvaluation.grade === 1 || qualityEvaluation.grade === 5}
>
{qualityEvaluation?.grade}
</LargeGradeItem>
)}
{gradeVariant === "small" && (
<SmallQualityEvaluationGrade grade={qualityEvaluation.grade} ariaLabel={qualityEvaluation?.note} />
)}
<SmallQualityEvaluationGrade grade={qualityEvaluation.grade} tooltip={qualityEvaluation?.note} />
</>
) : (
<StyledNoEvaluation margin="none" textStyle="button">
Expand Down
256 changes: 126 additions & 130 deletions src/components/QualityEvaluation/QualityEvaluationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,26 @@
*
*/

import { FieldHelperProps, FieldInputProps, Form, Formik } from "formik";
import { CSSProperties, useMemo, useState } from "react";
import { FieldHelperProps, FieldInputProps, Formik } from "formik";
import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { Item, Indicator } from "@radix-ui/react-radio-group";
import { useQueryClient } from "@tanstack/react-query";
import { ButtonV2 } from "@ndla/button";
import { colors, spacing, fonts, misc } from "@ndla/core";
import { FieldErrorMessage, Fieldset, InputV3, Label, Legend, RadioButtonGroup } from "@ndla/forms";
import {
Button,
FieldErrorMessage,
FieldHelper,
FieldInput,
FieldLabel,
FieldRoot,
RadioGroupItem,
RadioGroupItemControl,
RadioGroupItemHiddenInput,
RadioGroupItemText,
RadioGroupLabel,
RadioGroupRoot,
Text,
} from "@ndla/primitives";
import { styled } from "@ndla/styled-system/jsx";
import { IArticle, IUpdatedArticle } from "@ndla/types-backend/draft-api";
import { Grade, Node } from "@ndla/types-taxonomy";
import { ArticleFormType } from "../../containers/FormikForm/articleFormHooks";
Expand All @@ -25,78 +35,83 @@ import { usePutNodeMutation } from "../../modules/nodes/nodeMutations";
import { nodeQueryKeys } from "../../modules/nodes/nodeQueries";
import { formatDateForBackend } from "../../util/formatDate";
import handleError from "../../util/handleError";
import { FieldWarning, FormControl, FormField } from "../FormField";
import { FormField } from "../FormField";
import { FormActionsContainer, FormikForm } from "../FormikForm";
import validateFormik, { RulesType } from "../formikValidationSchema";
import Spinner from "../Spinner";

export const qualityEvaluationOptions: { [key: number]: string } = {
1: colors.support.green,
2: "#90C670",
3: "#C3D060",
4: colors.support.yellow,
5: colors.support.red,
};

const StyledFieldset = styled(Fieldset)`
display: flex;
gap: ${spacing.xsmall};
align-items: center;
`;

// Color needed in order for wcag contrast reqirements to be met
export const blackContrastColor = "#000";

export const gradeItemStyles = css`
padding: 0px ${spacing.nsmall};
font-weight: ${fonts.weight.semibold};
border-radius: ${misc.borderRadius};
color: ${blackContrastColor};
${fonts.size.text.content};
&[data-border="false"] {
background-color: var(--item-color);
}
&[data-border="true"] {
box-shadow: inset 0px 0px 0px 2px var(--item-color);
}
`;

const StyledItem = styled(Item)`
all: unset;
${gradeItemStyles};
&:hover {
cursor: pointer;
border-radius: ${misc.borderRadius};
outline: 2px solid ${colors.brand.primary};
}
&[data-state="checked"] {
outline: 2px solid ${blackContrastColor};
}
`;
export type QualityEvaluationValue = "1" | "2" | "3" | "4" | "5";

const ButtonContainer = styled.div`
margin-top: ${spacing.small};
display: flex;
justify-content: space-between;
`;
// TODO: We should change these colors
const qualityEvaluationOptions: Record<QualityEvaluationValue, string> = {
"1": "#5cbc80",
"2": "#90C670",
"3": "#C3D060",
"4": "#ead854",
"5": "#d1372e",
};

const RightButtonsWrapper = styled.div`
display: flex;
gap: ${spacing.xsmall};
`;
const ButtonContainer = styled("div", {
base: {
display: "flex",
justifyContent: "space-between",
},
});

const StyledForm = styled(Form)`
display: flex;
flex-direction: column;
gap: ${spacing.small};
`;
const MutationErrorMessage = styled(FieldErrorMessage)`
margin-left: auto;
`;
const StyledRadioGroupItem = styled(RadioGroupItem, {
base: {
padding: "xxsmall",
borderRadius: "xsmall",
outlineOffset: "-5xsmall",
"&:has(input:focus-visible)": {
outlineOffset: "0",
},
},
variants: {
quality: {
"1": {
borderRadius: "xsmall",
outline: "2px solid",
outlineOffset: "-5xsmall",
outlineColor: qualityEvaluationOptions["1"],
"&:has(input:focus-visible)": {
outlineColor: qualityEvaluationOptions["1"],
outlineOffset: "-5xsmall",
boxShadow: "0 0 0 2px var(--shadow-color)",
boxShadowColor: "stroke.default",
},
},
"2": {
background: qualityEvaluationOptions["2"],
},
"3": {
background: qualityEvaluationOptions["3"],
},
"4": {
background: qualityEvaluationOptions["4"],
},
"5": {
borderRadius: "xsmall",
outline: "2px solid",
outlineOffset: "-5xsmall",
outlineColor: qualityEvaluationOptions["5"],
"&:has(input:focus-visible)": {
outlineColor: qualityEvaluationOptions["5"],
outlineOffset: "-5xsmall",
boxShadow: "0 0 0 2px var(--shadow-color)",
boxShadowColor: "stroke.default",
},
},
},
},
});

const StyledFieldWarning = styled(FieldWarning)`
margin-left: auto;
`;
const ItemsWrapper = styled("div", {
base: {
display: "flex",
gap: "3xsmall",
flexWrap: "wrap",
},
});

interface Props {
setOpen: (open: boolean) => void;
Expand Down Expand Up @@ -237,76 +252,57 @@ const QualityEvaluationForm = ({
onSubmit={onSubmit}
onReset={onDelete}
>
{({ dirty, isValid, isSubmitting, values }) => (
<StyledForm>
{({ dirty, isValid, isSubmitting }) => (
<FormikForm>
<FormField name="grade">
{({ field, meta, helpers }) => (
<FormControl isInvalid={!!meta.error} isRequired>
<RadioButtonGroup
orientation="horizontal"
<FieldRoot invalid={!!meta.error} required>
<RadioGroupRoot
orientation="vertical"
value={field.value?.toString()}
onValueChange={(v) => helpers.setValue(Number(v))}
asChild
onValueChange={(details) => helpers.setValue(Number(details.value))}
>
<StyledFieldset>
<Legend margin="none" textStyle="label-small">
{t("qualityEvaluationForm.title")}
</Legend>
{Object.entries(qualityEvaluationOptions).map(([value, color]) => (
<div key={value}>
<StyledItem
id={`quality-${value}`}
value={value.toString()}
data-color-value={value}
style={{ "--item-color": color } as CSSProperties}
data-border={value === "1" || value === "5"}
>
<Indicator forceMount>{value}</Indicator>
</StyledItem>
<Label htmlFor={`quality-${value}`} visuallyHidden>
{value}
</Label>
</div>
<RadioGroupLabel>{t("qualityEvaluationForm.title")}</RadioGroupLabel>
<FieldErrorMessage>{meta.error}</FieldErrorMessage>
{field.value === 5 && <FieldHelper>{t("qualityEvaluationForm.warning")}</FieldHelper>}
<ItemsWrapper>
{Object.entries(qualityEvaluationOptions).map(([value, _]) => (
<StyledRadioGroupItem key={value} value={value} quality={value as QualityEvaluationValue}>
<RadioGroupItemControl />
<RadioGroupItemText>{value}</RadioGroupItemText>
<RadioGroupItemHiddenInput />
</StyledRadioGroupItem>
))}
</StyledFieldset>
</RadioButtonGroup>
<FieldErrorMessage>{meta.error}</FieldErrorMessage>
</FormControl>
</ItemsWrapper>
</RadioGroupRoot>
</FieldRoot>
)}
</FormField>
<FormField name="note">
{({ field }) => (
<FormControl>
<Label margin="none" textStyle="label-small">
{t("qualityEvaluationForm.note")}
</Label>
<InputV3 {...field} />
</FormControl>
<FieldRoot>
<FieldLabel>{t("qualityEvaluationForm.note")}</FieldLabel>
<FieldInput {...field} />
</FieldRoot>
)}
</FormField>
<ButtonContainer>
<div>
{node.qualityEvaluation?.grade && (
<ButtonV2 variant="outline" colorTheme="danger" type="reset">
{loading.delete && <Spinner appearance="small" />}
{t("qualityEvaluationForm.delete")}
</ButtonV2>
)}
</div>
<RightButtonsWrapper>
<ButtonV2 variant="outline" onClick={() => setOpen(false)}>
{node.qualityEvaluation?.grade && (
<Button variant="danger" type="reset" loading={loading.delete}>
{t("qualityEvaluationForm.delete")}
</Button>
)}
<FormActionsContainer>
<Button variant="secondary" onClick={() => setOpen(false)}>
{t("form.abort")}
</ButtonV2>
<ButtonV2 disabled={!dirty || !isValid || isSubmitting} type="submit">
{loading.save && <Spinner appearance="small" />} {t("form.save")}
</ButtonV2>
</RightButtonsWrapper>
</Button>
<Button disabled={!dirty || !isValid || isSubmitting} loading={loading.save} type="submit">
{t("form.save")}
</Button>
</FormActionsContainer>
</ButtonContainer>
{updateTaxMutation.isError && <MutationErrorMessage>{t("qualityEvaluationForm.error")}</MutationErrorMessage>}
{isResource && values.grade === 5 && (
<StyledFieldWarning>{t("qualityEvaluationForm.warning")}</StyledFieldWarning>
)}
</StyledForm>
{updateTaxMutation.isError && <Text color="text.error">{t("qualityEvaluationForm.error")}</Text>}
</FormikForm>
)}
</Formik>
);
Expand Down
Loading

0 comments on commit 80b080a

Please sign in to comment.