Repository: marigold-ui/marigold
Pull request #1927 (2022-03-29 07:11:59)

refa: improvement to`<HelpText>` API

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: c8dbbe5b322359207d971c48e2adb977a14ed370

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/marigold-ui/marigold/new/update-helptext?filename=.changeset/lemon-hats-cheat.md&value=---%0A%22%40marigold%2Fcomponents%22%3A%20patch%0A---%0A%0Arefa%3A%20improvement%20to%60%3CHelpText%3E%60%20API%0A)



Patch:
diff --git a/packages/components/src/Field-Next/HelpText.stories.tsx b/packages/components/src/Field-Next/HelpText.stories.tsx
new file mode 100644
index 000000000..7078c4e18
--- /dev/null
+++ b/packages/components/src/Field-Next/HelpText.stories.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import type { Meta, ComponentStory } from '@storybook/react';
+import { HelpText } from './HelpText';
+
+export default {
+  title: 'Field/HelpText',
+  argTypes: {
+    description: {
+      control: {
+        type: 'text',
+      },
+      description: 'The description',
+      defaultValue: 'This is a help text description',
+    },
+    errorMessage: {
+      control: {
+        type: 'text',
+      },
+      description: 'The error message',
+      defaultValue: 'Something went wrong',
+    },
+    error: {
+      control: {
+        type: 'boolean',
+      },
+      description: 'Whether the help text is an error',
+      defaultValue: false,
+    },
+  },
+} as Meta;
+
+export const Basic: ComponentStory<typeof HelpText> = args => (
+  <HelpText {...args} />
+);
diff --git a/packages/components/src/Field-Next/HelpText.tsx b/packages/components/src/Field-Next/HelpText.tsx
new file mode 100644
index 000000000..d68db9d07
--- /dev/null
+++ b/packages/components/src/Field-Next/HelpText.tsx
@@ -0,0 +1,59 @@
+import React, { HTMLAttributes, ReactNode } from 'react';
+import { Exclamation } from '@marigold/icons';
+import { useTheme } from '@marigold/system';
+
+import { Box } from '../Box';
+
+// Theme Extension
+// ---------------
+export interface HelpTextThemeExtension<Value> {
+  helpText?: {
+    description: Value;
+    error: Value;
+    icon: {
+      size: number | string;
+    };
+  };
+}
+
+// Props
+// ---------------
+export interface HelpTextProps {
+  description?: ReactNode;
+  descriptionProps?: HTMLAttributes<HTMLElement>;
+  error?: boolean;
+  errorMessage?: ReactNode;
+  errorMessageProps?: HTMLAttributes<HTMLElement>;
+}
+
+// Component
+// ---------------
+export const HelpText = ({
+  description,
+  descriptionProps,
+  error,
+  errorMessage,
+  errorMessageProps,
+  ...props
+}: HelpTextProps) => {
+  const { get } = useTheme();
+  const iconSize = get('helpText.icon.size');
+  const isErrorMessage = errorMessage && error;
+
+  return (
+    <Box
+      {...props}
+      variant={`helpText.${isErrorMessage ? 'error' : 'description'}`}
+      __baseCSS={{ display: 'flex', alignItems: 'center', gap: 4 }}
+    >
+      {isErrorMessage ? (
+        <>
+          <Exclamation role="presentation" size={iconSize} />
+          <Box {...errorMessageProps}>{errorMessage}</Box>
+        </>
+      ) : (
+        <Box {...descriptionProps}>{description}</Box>
+      )}
+    </Box>
+  );
+};
diff --git a/packages/components/src/Field-Next/HelperText.stories.tsx b/packages/components/src/Field-Next/HelperText.stories.tsx
deleted file mode 100644
index a6f9856f8..000000000
--- a/packages/components/src/Field-Next/HelperText.stories.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import type { Meta, ComponentStory } from '@storybook/react';
-import { HelperText } from './HelperText';
-
-export default {
-  title: 'Field/HelperText',
-  argTypes: {
-    children: {
-      control: {
-        type: 'text',
-      },
-      description: 'Text of the label',
-      defaultValue: 'This is the helper text!',
-    },
-    variant: {
-      control: {
-        type: 'select',
-      },
-      options: ['description', 'error'],
-      description: 'Choose between description and error',
-      defaultValue: 'description',
-    },
-  },
-} as Meta;
-
-export const Basic: ComponentStory<typeof HelperText> = ({
-  children,
-  ...args
-}) => <HelperText {...args}>{children}</HelperText>;
diff --git a/packages/components/src/Field-Next/HelperText.tsx b/packages/components/src/Field-Next/HelperText.tsx
deleted file mode 100644
index 1daa31342..000000000
--- a/packages/components/src/Field-Next/HelperText.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-import { Exclamation } from '@marigold/icons';
-
-import { Box } from '../Box';
-
-// Theme Extension
-// ---------------
-export interface HelperTextThemeExtension<Value> {
-  helperText?: {
-    description: Value;
-    error: Value;
-    icon: {
-      size: number | string;
-    };
-  };
-}
-
-// Props
-// ---------------
-export interface HelperTextProps {
-  variant?: 'description' | 'error';
-}
-
-// Component
-// ---------------
-export const HelperText: React.FC<HelperTextProps> = ({
-  variant = 'description',
-  children,
-}) => (
-  <Box
-    variant={`helperText.${variant}`}
-    __baseCSS={{ display: 'flex', alignItems: 'center', gap: 4 }}
-  >
-    {variant === 'error' ? <Exclamation role="presentation" /> : null}
-    {children}
-  </Box>
-);


Test patch:
diff --git a/packages/components/src/Field-Next/HelpText.test.tsx b/packages/components/src/Field-Next/HelpText.test.tsx
new file mode 100644
index 000000000..838d23a79
--- /dev/null
+++ b/packages/components/src/Field-Next/HelpText.test.tsx
@@ -0,0 +1,126 @@
+import React from 'react';
+import { render, screen, within } from '@testing-library/react';
+import { ThemeProvider } from '@marigold/system';
+import { HelpText } from './HelpText';
+
+const theme = {
+  sizes: {
+    none: 0,
+    small: 14,
+  },
+  colors: {
+    text: 'black',
+    error: 'red',
+  },
+  helpText: {
+    description: {
+      color: 'text',
+    },
+    error: {
+      color: 'error',
+    },
+    icon: {
+      size: 'small',
+    },
+  },
+};
+
+test('render description', () => {
+  render(<HelpText description="This is a help text description" />);
+
+  const element = screen.getByText('This is a help text description');
+  expect(element).toBeInTheDocument();
+});
+
+test('render description even if erorr message is defined', () => {
+  render(
+    <HelpText
+      description="This is a help text description"
+      errorMessage="Something went wrong"
+    />
+  );
+
+  const element = screen.getByText('This is a help text description');
+  expect(element).toBeInTheDocument();
+
+  const error = screen.queryByText('Something went wrong');
+  expect(error).not.toBeInTheDocument();
+});
+
+test('uses description variant by default', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <HelpText
+        data-testid="help-text"
+        description="This is a help text description"
+      />
+    </ThemeProvider>
+  );
+
+  const element = screen.getByTestId('help-text');
+  expect(element).toHaveStyle(`color: ${theme.colors.text}`);
+});
+
+test('renders error message when error is set', () => {
+  render(
+    <HelpText
+      error={true}
+      description="This is a help text description"
+      errorMessage="Something went wrong"
+    />
+  );
+
+  const error = screen.getByText('Something went wrong');
+  expect(error).toBeInTheDocument();
+
+  const descrption = screen.queryByText('This is a help text description');
+  expect(descrption).not.toBeInTheDocument();
+});
+
+test('uses error variant when error is set', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <HelpText
+        data-testid="help-text"
+        error={true}
+        description="This is a help text description"
+        errorMessage="Something went wrong"
+      />
+    </ThemeProvider>
+  );
+
+  const element = screen.getByTestId('help-text');
+  expect(element).toHaveStyle(`color: ${theme.colors.error}`);
+});
+
+test('renders icon when when error message is shown', () => {
+  render(
+    <HelpText
+      data-testid="help-text"
+      error={true}
+      description="This is a help text description"
+      errorMessage="Something went wrong"
+    />
+  );
+
+  const element = screen.getByTestId('help-text');
+  const icon = within(element).getByRole(/presentation/);
+  expect(icon).toBeInTheDocument();
+});
+
+test('icon can be sized via theme', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <HelpText
+        data-testid="help-text"
+        error={true}
+        description="This is a help text description"
+        errorMessage="Something went wrong"
+      />
+    </ThemeProvider>
+  );
+
+  const element = screen.getByTestId('help-text');
+  const icon = within(element).getByRole(/presentation/);
+  expect(icon).toHaveStyle(`width: ${theme.sizes.small}px`);
+});
diff --git a/packages/components/src/Field-Next/HelperText.test.tsx b/packages/components/src/Field-Next/HelperText.test.tsx
deleted file mode 100644
index eee607816..000000000
--- a/packages/components/src/Field-Next/HelperText.test.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import { render, screen, within } from '@testing-library/react';
-import { ThemeProvider } from '@marigold/system';
-import { HelperText } from './HelperText';
-
-const theme = {
-  colors: {
-    text: 'black',
-    error: 'red',
-  },
-  helperText: {
-    description: {
-      color: 'text',
-    },
-    error: {
-      color: 'error',
-    },
-  },
-};
-
-test('supports description variant', () => {
-  render(
-    <ThemeProvider theme={theme}>
-      <HelperText>label</HelperText>
-    </ThemeProvider>
-  );
-  const label = screen.getByText(/label/);
-  expect(label).toHaveStyle(`color: black`);
-});
-
-test('supports error variant', () => {
-  render(
-    <ThemeProvider theme={theme}>
-      <HelperText variant="error">label</HelperText>
-    </ThemeProvider>
-  );
-  const label = screen.getByText(/label/);
-  expect(label).toHaveStyle(`color: red`);
-});
-
-test('renders icon when variant is error', () => {
-  render(
-    <ThemeProvider theme={theme}>
-      <HelperText variant="error">label</HelperText>
-    </ThemeProvider>
-  );
-  const label = screen.getByText(/label/);
-  const icon = within(label).getByRole(/presentation/);
-  expect(icon).toBeInTheDocument();
-});