Skip to content

Commit

Permalink
Enhance FieldContainer with secondaryHelperText prop and `helperT…
Browse files Browse the repository at this point in the history
…extIcon` prop (#2863)

`secondaryHelperText` displays additional text beneath the input, aligned to the bottom-right.
`helperTextIcon` adds an icon beside helperText, error, or warning.

Co-authored-by: Kerstin Reichinger <kerstin.reichinger@vivid-planet.com>
  • Loading branch information
kerstin97 and kerstinreichinger-vivid authored Dec 9, 2024
1 parent aa02ca1 commit 589b0b9
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 11 deletions.
17 changes: 17 additions & 0 deletions .changeset/purple-birds-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@comet/admin-theme": minor
"@comet/admin": minor
---

Enhance `FieldContainer` with `secondaryHelperText` prop and `helperTextIcon` prop

- `helperTextIcon` displays an icon alongside the text for `helperText`, `error` or `warning`.
- `secondaryHelperText` provides an additional helper text positioned beneath the input field, aligned to the bottom-right corner.

**Example:**

```tsx
<FieldContainer label="Helper Text Icon" helperTextIcon={<Info />} helperText="Helper Text with icon" secondaryHelperText="0/100">
<InputBase onChange={handleChange} value={value} placeholder="Placeholder" />
</FieldContainer>
```
94 changes: 83 additions & 11 deletions packages/admin/admin/src/form/FieldContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FormControl, FormHelperText, FormLabel, formLabelClasses, inputBaseClasses, useThemeProps } from "@mui/material";
import { ComponentsOverrides, css } from "@mui/material/styles";
import { FormControl, FormHelperText, FormLabel, formLabelClasses, inputBaseClasses, svgIconClasses, useThemeProps } from "@mui/material";
import { ComponentsOverrides, css, Theme } from "@mui/material/styles";
import { PropsWithChildren, ReactNode, useEffect, useRef } from "react";

import { createComponentSlot } from "../helpers/createComponentSlot";
Expand All @@ -13,6 +13,9 @@ export type FieldContainerProps = ThemedComponentBaseProps<{
error: typeof FormHelperText;
warning: typeof FormHelperText;
helperText: typeof FormHelperText;
secondaryHelperText: typeof FormHelperText;
helperTextsWrapper: "div";
helperTextContent: "span";
}> & {
variant?: "vertical" | "horizontal";
forceVerticalContainerSize?: number;
Expand All @@ -25,6 +28,8 @@ export type FieldContainerProps = ThemedComponentBaseProps<{
scrollTo?: boolean;
fieldMargin?: "always" | "never" | "onlyIfNotLast";
helperText?: ReactNode;
secondaryHelperText?: ReactNode;
helperTextIcon?: ReactNode;
};

export type FieldContainerClassKey =
Expand All @@ -44,7 +49,10 @@ export type FieldContainerClassKey =
| "error"
| "hasWarning"
| "warning"
| "helperText";
| "helperText"
| "secondaryHelperText"
| "helperTextsWrapper"
| "helperTextContent";

type OwnerState = Pick<FieldContainerProps, "fullWidth" | "disabled" | "required" | "fieldMargin" | "variant"> & {
hasError: boolean;
Expand Down Expand Up @@ -209,29 +217,75 @@ const InputContainer = createComponentSlot("div")<FieldContainerClassKey, OwnerS
`,
);

const getCommonHelperTextStyles = (theme: Theme) => css`
display: flex;
gap: ${theme.spacing(1)};
> .${svgIconClasses.root} {
width: 12px;
}
`;

const Error = createComponentSlot(FormHelperText)<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "error",
})();
})(
({ theme }) => css`
${getCommonHelperTextStyles(theme)}
`,
);

const Warning = createComponentSlot(FormHelperText)<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "warning",
})(
({ theme }) => css`
${getCommonHelperTextStyles(theme)}
color: ${theme.palette.warning.main};
`,
);

const HelperText = createComponentSlot(FormHelperText)<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "helperText",
})(
({ theme }) => css`
${getCommonHelperTextStyles(theme)}
color: ${theme.palette.grey[300]};
`,
);

const SecondaryHelperText = createComponentSlot(FormHelperText)<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "secondaryHelperText",
})(
({ theme }) => css`
color: ${theme.palette.grey[300]};
margin-left: auto;
&.Mui-disabled {
color: ${theme.palette.grey[300]};
}
`,
);

const HelperTextsWrapper = createComponentSlot("div")<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "helperTextsWrapper",
})(
({ theme }) => css`
display: flex;
align-items: center;
justify-content: space-between;
gap: ${theme.spacing(3)};
`,
);

const HelperTextContent = createComponentSlot("span")<FieldContainerClassKey>({
componentName: "FormFieldContainer",
slotName: "helperTextContent",
})();

export const FieldContainer = (inProps: PropsWithChildren<FieldContainerProps>) => {
const {
variant = "vertical",
Expand All @@ -244,6 +298,8 @@ export const FieldContainer = (inProps: PropsWithChildren<FieldContainerProps>)
children,
warning,
helperText,
secondaryHelperText,
helperTextIcon,
scrollTo = false,
fieldMargin = "onlyIfNotLast",
slotProps,
Expand Down Expand Up @@ -282,13 +338,29 @@ export const FieldContainer = (inProps: PropsWithChildren<FieldContainerProps>)
</Label>
<InputContainer ownerState={ownerState} {...slotProps?.inputContainer}>
{children}
{hasError && (
<Error error {...slotProps?.error}>
{error}
</Error>
)}
{hasWarning && !hasError && <Warning {...slotProps?.warning}>{warning}</Warning>}
{helperText && !hasError && !hasWarning && <HelperText {...slotProps?.helperText}>{helperText}</HelperText>}
<HelperTextsWrapper {...slotProps?.helperTextsWrapper}>
{hasError && (
<Error error {...slotProps?.error}>
{Boolean(helperTextIcon) && helperTextIcon}
<HelperTextContent {...slotProps?.helperTextContent}>{error}</HelperTextContent>
</Error>
)}
{hasWarning && !hasError && (
<Warning {...slotProps?.warning}>
{Boolean(helperTextIcon) && helperTextIcon}
<HelperTextContent {...slotProps?.helperTextContent}>{warning}</HelperTextContent>
</Warning>
)}
{Boolean(helperText) && !hasError && !hasWarning && (
<HelperText {...slotProps?.helperText}>
{Boolean(helperTextIcon) && helperTextIcon}
<HelperTextContent {...slotProps?.helperTextContent}>{helperText}</HelperTextContent>
</HelperText>
)}
{Boolean(secondaryHelperText) && (
<SecondaryHelperText {...slotProps?.secondaryHelperText}>{secondaryHelperText}</SecondaryHelperText>
)}
</HelperTextsWrapper>
</InputContainer>
</InnerContainer>
</Root>
Expand Down
11 changes: 11 additions & 0 deletions storybook/src/docs/form/components/FieldContainer.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FieldContainer } from "@comet/admin";
import { Info } from "@comet/admin-icons";
import { InputBase } from "@mui/material";
import * as React from "react";

Expand Down Expand Up @@ -35,6 +36,16 @@ export const Basic = {
<FieldContainer label="Helper" helperText="This is a helper">
<InputBase onChange={handleChange} value={value} placeholder="Placeholder" />
</FieldContainer>
<br />
<FieldContainer label="Secondary Helper Text" secondaryHelperText={`${value.length}/100`}>
<InputBase onChange={handleChange} value={value} placeholder="Placeholder" />
</FieldContainer>
<FieldContainer label="Multiple Helper Texts" helperText="Helper Text" secondaryHelperText={`${value.length}/100`}>
<InputBase onChange={handleChange} value={value} placeholder="Placeholder" />
</FieldContainer>
<FieldContainer label="Helper Text Icon" helperTextIcon={<Info />} helperText="Helper Text with icon">
<InputBase onChange={handleChange} value={value} placeholder="Placeholder" />
</FieldContainer>
</form>
);
},
Expand Down

0 comments on commit 589b0b9

Please sign in to comment.