Repository: marigold-ui/marigold (ts)

Issue:
TextField and TextArea don't support variant and size
FieldBase has `variant` and `size` props but they are unused!

Pull request:
fix: <TextField>/<TextArea> support variant/size
closes #2011

Patch:
diff --git a/.changeset/slow-meals-drop.md b/.changeset/slow-meals-drop.md
new file mode 100644
index 000000000..e7b84d290
--- /dev/null
+++ b/.changeset/slow-meals-drop.md
@@ -0,0 +1,6 @@
+---
+"@marigold/docs": minor
+"@marigold/components": minor
+---
+
+fix: <TextField>/<TextArea> support variant/size
diff --git a/docs/content/components/text-area.mdx b/docs/content/components/text-area.mdx
index b405de6f1..c29350cd8 100644
--- a/docs/content/components/text-area.mdx
+++ b/docs/content/components/text-area.mdx
@@ -1,5 +1,5 @@
 ---
-title: Textarea
+title: TextArea
 figma: https://www.figma.com/file/DFKyTGHAoDxOsUBPszLLxP/%F0%9F%8F%B5%EF%B8%8FMarigold?node-id=454%3A1
 ---
 
diff --git a/packages/components/src/Field/FieldBase.tsx b/packages/components/src/Field/FieldBase.tsx
index 93ee6b176..a985223df 100644
--- a/packages/components/src/Field/FieldBase.tsx
+++ b/packages/components/src/Field/FieldBase.tsx
@@ -44,7 +44,13 @@ export const FieldBase = ({
   return (
     <Stack>
       {label && (
-        <Label {...labelProps} {...stateProps} required={required}>
+        <Label
+          required={required}
+          variant={variant}
+          size={size}
+          {...labelProps}
+          {...stateProps}
+        >
           {label}
         </Label>
       )}
@@ -52,6 +58,8 @@ export const FieldBase = ({
       {hasHelpText && (
         <HelpText
           {...stateProps}
+          variant={variant}
+          size={size}
           disabled={disabled}
           description={description}
           descriptionProps={descriptionProps}
diff --git a/packages/components/src/Field/HelpText.tsx b/packages/components/src/Field/HelpText.tsx
index 623bdfd4e..67674cc91 100644
--- a/packages/components/src/Field/HelpText.tsx
+++ b/packages/components/src/Field/HelpText.tsx
@@ -47,6 +47,7 @@ export const HelpText = ({
 
   return (
     <Box
+      {...(hasErrorMessage ? errorMessageProps : descriptionProps)}
       {...props}
       __baseCSS={{ display: 'flex', alignItems: 'center', gap: 4 }}
       css={styles.container}
@@ -57,10 +58,10 @@ export const HelpText = ({
             role="presentation"
             size={(styles?.icon?.size as any) || 16}
           />
-          <Box {...errorMessageProps}>{errorMessage}</Box>
+          {errorMessage}
         </>
       ) : (
-        <Box {...descriptionProps}>{description}</Box>
+        <>{description}</>
       )}
     </Box>
   );
diff --git a/packages/components/src/TextArea/TextArea.tsx b/packages/components/src/TextArea/TextArea.tsx
index 104b38b25..004769ac8 100644
--- a/packages/components/src/TextArea/TextArea.tsx
+++ b/packages/components/src/TextArea/TextArea.tsx
@@ -41,10 +41,12 @@ export type CustomTextAreEvents =
 export interface TextAreaProps
   extends Omit<
       ComponentProps<'textarea'>,
-      'value' | 'defaultValue' | CustomTextAreEvents
+      'value' | 'defaultValue' | CustomTextAreEvents | 'size'
     >,
     Pick<AriaTextFieldProps, CustomTextAreEvents>,
     Pick<FieldBaseProps, 'label' | 'description' | 'error' | 'errorMessage'> {
+  variant?: string;
+  size?: string;
   value?: string;
   defaultValue?: string;
 }
@@ -56,6 +58,8 @@ export const TextArea = ({
   required,
   readOnly,
   error,
+  variant,
+  size,
   ...props
 }: TextAreaProps) => {
   const { label, description, errorMessage } = props;
@@ -84,7 +88,7 @@ export const TextArea = ({
     error,
   });
 
-  const styles = useComponentStyles('TextArea', {});
+  const styles = useComponentStyles('TextArea', { variant, size });
   return (
     <FieldBase
       label={label}
@@ -96,10 +100,11 @@ export const TextArea = ({
       errorMessage={errorMessage}
       errorMessageProps={errorMessageProps}
       stateProps={stateProps}
+      variant={variant}
+      size={size}
     >
       <Box
         as="textarea"
-        variant="textArea"
         css={styles}
         ref={ref}
         {...inputProps}
diff --git a/packages/components/src/TextField/TextField.tsx b/packages/components/src/TextField/TextField.tsx
index a7423899c..17b946801 100644
--- a/packages/components/src/TextField/TextField.tsx
+++ b/packages/components/src/TextField/TextField.tsx
@@ -15,7 +15,7 @@ import { Input } from '../Input';
 export interface TextFieldProps
   extends Omit<
       ComponentProps<'input'>,
-      'value' | 'defaultValue' | 'onChange' | 'onFocus' | 'onBlur'
+      'value' | 'defaultValue' | 'onChange' | 'onFocus' | 'onBlur' | 'size'
     >,
     /**
      * `react-aria` has a slightly different API for `onChange`, `onFocus`
@@ -23,6 +23,8 @@ export interface TextFieldProps
      */
     Pick<AriaTextFieldProps, 'onChange' | 'onFocus' | 'onBlur'>,
     Pick<FieldBaseProps, 'label' | 'description' | 'error' | 'errorMessage'> {
+  variant?: string;
+  size?: string;
   value?: string;
   defaultValue?: string;
 }
@@ -34,6 +36,8 @@ export const TextField = ({
   required,
   readOnly,
   error,
+  variant,
+  size,
   ...props
 }: TextFieldProps) => {
   const { label, description, errorMessage } = props;
@@ -72,9 +76,13 @@ export const TextField = ({
       errorMessage={errorMessage}
       errorMessageProps={errorMessageProps}
       stateProps={stateProps}
+      variant={variant}
+      size={size}
     >
       <Input
         ref={ref}
+        variant={variant}
+        size={size}
         /**
          * We use `size` for styles which is a string, not like
          * the regular HTML attribute, which is a number


Test patch:
diff --git a/packages/components/src/Field/FieldBase.test.tsx b/packages/components/src/Field/FieldBase.test.tsx
index 5244a024c..7131451b1 100644
--- a/packages/components/src/Field/FieldBase.test.tsx
+++ b/packages/components/src/Field/FieldBase.test.tsx
@@ -3,9 +3,49 @@ import { AriaTextFieldOptions, useTextField } from '@react-aria/textfield';
 import { render, screen, within } from '@testing-library/react';
 
 import { FieldBase } from './FieldBase';
+import { ThemeProvider } from '@marigold/system';
 
 // Setup
 // ---------------
+const theme = {
+  colors: {
+    blue: '#1c7ed6',
+  },
+  fontSizes: {
+    'small-1': 12,
+  },
+  components: {
+    Label: {
+      variant: {
+        blue: {
+          color: 'blue',
+        },
+      },
+      size: {
+        small: {
+          fontSize: 'small-1',
+        },
+      },
+    },
+    HelpText: {
+      variant: {
+        blue: {
+          container: {
+            color: 'blue',
+          },
+        },
+      },
+      size: {
+        small: {
+          container: {
+            fontSize: 'small-1',
+          },
+        },
+      },
+    },
+  },
+};
+
 interface MockedTestFieldProps extends AriaTextFieldOptions<'input'> {
   disabled?: boolean;
   required?: boolean;
@@ -98,3 +138,24 @@ test('field label shows requried indicator', () => {
   const requiredIcon = within(label).getByRole('presentation');
   expect(requiredIcon).toBeInTheDocument();
 });
+
+test('passes down variant and size', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <FieldBase
+        label="Label"
+        description="Description"
+        variant="blue"
+        size="small"
+      >
+        <input type="text" />
+      </FieldBase>
+    </ThemeProvider>
+  );
+
+  const label = screen.getByText('Label');
+  expect(label).toHaveStyle(`color: ${theme.colors.blue}`);
+
+  const helptext = screen.getByText('Description');
+  expect(helptext).toHaveStyle(`color: ${theme.colors.blue}`);
+});
diff --git a/packages/components/src/TextArea/TextArea.test.tsx b/packages/components/src/TextArea/TextArea.test.tsx
index 5f2215821..1a8569b17 100644
--- a/packages/components/src/TextArea/TextArea.test.tsx
+++ b/packages/components/src/TextArea/TextArea.test.tsx
@@ -6,12 +6,57 @@ import { ThemeProvider } from '@marigold/system';
 import { TextArea } from './TextArea';
 
 const theme = {
-  fonts: {
-    body: 'Inter Regular',
-    fancy: 'Roboto',
+  colors: {
+    blue: '#00f',
+    lime: '#82c91e',
   },
-  textArea: {
-    fontFamily: 'body',
+  fontSizes: {
+    'small-1': 12,
+  },
+  components: {
+    Label: {
+      variant: {
+        lime: {
+          color: 'lime',
+        },
+      },
+      size: {
+        small: {
+          fontSize: 'small-1',
+        },
+      },
+    },
+    HelpText: {
+      variant: {
+        lime: {
+          container: {
+            color: 'lime',
+          },
+        },
+      },
+      size: {
+        small: {
+          container: {
+            fontSize: 'small-1',
+          },
+        },
+      },
+    },
+    TextArea: {
+      base: {
+        borderColor: 'blue',
+      },
+      variant: {
+        lime: {
+          color: 'lime',
+        },
+      },
+      size: {
+        small: {
+          fontSize: 'small-1',
+        },
+      },
+    },
   },
 };
 
@@ -23,14 +68,40 @@ test('renders an textarea', () => {
   expect(textArea instanceof HTMLTextAreaElement).toBeTruthy();
 });
 
-test('can be styled via variant', () => {
+test('textarea can be styled via "TextArea" styles', () => {
   render(
     <ThemeProvider theme={theme}>
-      <TextArea label="A Label" data-testid="textarea" />
+      <TextArea label="Label" data-testid="text-area" />
     </ThemeProvider>
   );
-  const textArea = screen.getByTestId('textarea');
-  expect(textArea).toHaveStyle(`font-family: ${theme.fonts.body}`);
+  const textArea = screen.getByTestId('text-area');
+  expect(textArea).toHaveStyle(`border-color: ${theme.colors.blue}`);
+});
+
+test('passes down variant and size', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextArea
+        data-testid="text-area"
+        label="Label"
+        description="Description"
+        variant="lime"
+        size="small"
+      />
+    </ThemeProvider>
+  );
+
+  const textArea = screen.getByTestId('text-area');
+  expect(textArea).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(textArea).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
+
+  const label = screen.getByText('Label');
+  expect(label).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(label).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
+
+  const description = screen.getByText('Description');
+  expect(description).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(description).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
 });
 
 test('supports disabled', () => {
diff --git a/packages/components/src/TextField/TextField.test.tsx b/packages/components/src/TextField/TextField.test.tsx
index 5828375d3..8ce2cf10c 100644
--- a/packages/components/src/TextField/TextField.test.tsx
+++ b/packages/components/src/TextField/TextField.test.tsx
@@ -8,12 +8,54 @@ import userEvent from '@testing-library/user-event';
 const theme = {
   colors: {
     blue: '#00f',
+    lime: '#82c91e',
+  },
+  fontSizes: {
+    'small-1': 12,
   },
   components: {
+    Label: {
+      variant: {
+        lime: {
+          color: 'lime',
+        },
+      },
+      size: {
+        small: {
+          fontSize: 'small-1',
+        },
+      },
+    },
+    HelpText: {
+      variant: {
+        lime: {
+          container: {
+            color: 'lime',
+          },
+        },
+      },
+      size: {
+        small: {
+          container: {
+            fontSize: 'small-1',
+          },
+        },
+      },
+    },
     Input: {
       base: {
         borderColor: 'blue',
       },
+      variant: {
+        lime: {
+          color: 'lime',
+        },
+      },
+      size: {
+        small: {
+          fontSize: 'small-1',
+        },
+      },
     },
   },
 };
@@ -44,6 +86,32 @@ test('input can be styled via "Input" styles', () => {
   expect(textField).toHaveStyle(`border-color: ${theme.colors.blue}`);
 });
 
+test('passes down variant and size', () => {
+  render(
+    <ThemeProvider theme={theme}>
+      <TextField
+        data-testid="text-field"
+        label="Label"
+        description="Description"
+        variant="lime"
+        size="small"
+      />
+    </ThemeProvider>
+  );
+
+  const textField = screen.getByTestId('text-field');
+  expect(textField).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(textField).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
+
+  const label = screen.getByText('Label');
+  expect(label).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(label).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
+
+  const description = screen.getByText('Description');
+  expect(description).toHaveStyle(`color: ${theme.colors.lime}`);
+  expect(description).toHaveStyle(`font-size: ${theme.fontSizes['small-1']}px`);
+});
+
 test('supports disabled', () => {
   render(<TextField label="A Label" disabled data-testid="text-field" />);