Repository: marigold-ui/marigold
Pull request #1990 (2022-04-13 11:10:13)

refa: Input Component restyled and restructure

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

Latest commit: 6a8931a75465522254c9e19f8b5ddda384b3b29b

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/input-resture?filename=.changeset/loud-ligers-destroy.md&value=---%0A%22%40marigold%2Fcomponents%22%3A%20patch%0A%22%40marigold%2Ftheme-b2b%22%3A%20patch%0A%22%40marigold%2Ftheme-core%22%3A%20patch%0A%22%40marigold%2Ftheme-unicorn%22%3A%20patch%0A---%0A%0Arefa%3A%20Input%20Component%20restyled%20and%20restructure%0A)



Patch:
diff --git a/.changeset/loud-ligers-destroy.md b/.changeset/loud-ligers-destroy.md
new file mode 100644
index 000000000..55457cda1
--- /dev/null
+++ b/.changeset/loud-ligers-destroy.md
@@ -0,0 +1,8 @@
+---
+"@marigold/components": patch
+"@marigold/theme-b2b": patch
+"@marigold/theme-core": patch
+"@marigold/theme-unicorn": patch
+---
+
+refa: Input Component restyled and restructure
diff --git a/packages/components/src/Input/Input.stories.tsx b/packages/components/src/Input/Input.stories.tsx
index c0f2a87c1..dca7fd4b3 100644
--- a/packages/components/src/Input/Input.stories.tsx
+++ b/packages/components/src/Input/Input.stories.tsx
@@ -1,7 +1,6 @@
 import React from 'react';
 import type { Meta, ComponentStory } from '@storybook/react';
 import { Input } from './Input';
-import { Label } from '../Label';
 
 export default {
   title: 'Components/Input',
@@ -10,12 +9,7 @@ export default {
       control: {
         type: 'text',
       },
-      description: '?',
-      table: {
-        defaultValue: {
-          summary: '__default',
-        },
-      },
+      description: 'Input variant',
     },
     type: {
       control: {
@@ -37,18 +31,16 @@ export default {
         'week',
       ],
       defaultValue: 'text',
-      table: {
-        defaultValue: {
-          summary: 'text',
-        },
+    },
+    pattern: {
+      control: {
+        type: 'text',
       },
+      description: 'Pattern for the input',
     },
   },
 } as Meta;
 
 export const Basic: ComponentStory<typeof Input> = args => (
-  <Label htmlFor="input">
-    Label
-    <Input id="input" placeholder="Placeholder..." {...args} />
-  </Label>
+  <Input placeholder="Placeholder..." {...args} />
 );
diff --git a/packages/components/src/Input/Input.tsx b/packages/components/src/Input/Input.tsx
index 435f0408d..6fc363caa 100755
--- a/packages/components/src/Input/Input.tsx
+++ b/packages/components/src/Input/Input.tsx
@@ -1,25 +1,27 @@
-import React from 'react';
+import React, { forwardRef } from 'react';
 import { ComponentProps } from '@marigold/types';
-import { Box } from '../Box';
+import { Box, ThemeExtension, useComponentStyles } from '@marigold/system';
 
 // Theme Extension
 // ---------------
-export interface InputThemeExtension<Value> {
-  input?: {
-    [key: string]: Value;
-  };
-}
+export interface InputThemeExtension extends ThemeExtension<'Input'> {}
 
 // Props
 // ---------------
-export interface InputProps extends ComponentProps<'input'> {
+export interface InputOwnProps extends Omit<ComponentProps<'input'>, 'size'> {
+  size?: string;
   variant?: string;
 }
 
+export interface InputProps
+  extends Omit<React.ComponentPropsWithRef<'input'>, 'size'>,
+    InputOwnProps {}
+
 // Component
 // ---------------
-export const Input: React.FC<InputProps> = ({
-  variant = '',
-  type = 'text',
-  ...props
-}) => <Box {...props} as="input" type={type} variant={`input.${variant}`} />;
+export const Input = forwardRef<HTMLInputElement, InputOwnProps>(
+  ({ variant, size, type = 'text', ...props }: InputOwnProps, ref) => {
+    const styles = useComponentStyles('Input', { variant, size });
+    return <Box {...props} ref={ref} as="input" type={type} css={styles} />;
+  }
+);
diff --git a/packages/components/src/TextField/TextField.tsx b/packages/components/src/TextField/TextField.tsx
index 09d1c1187..a9449069c 100644
--- a/packages/components/src/TextField/TextField.tsx
+++ b/packages/components/src/TextField/TextField.tsx
@@ -4,10 +4,11 @@ import { useTextField } from '@react-aria/textfield';
 import { AriaTextFieldProps } from '@react-types/textfield';
 import { useFocusRing } from '@react-aria/focus';
 
-import { Box, useComponentStyles, useStateProps } from '@marigold/system';
+import { useStateProps } from '@marigold/system';
 import { ComponentProps } from '@marigold/types';
 
 import { FieldBase, FieldBaseProps } from '../Field';
+import { Input } from '../Input';
 
 // Props
 // ---------------
@@ -60,8 +61,6 @@ export const TextField = ({
     error,
   });
 
-  const styles = useComponentStyles('Field', {}, { parts: ['input'] });
-
   return (
     <FieldBase
       label={label}
@@ -74,12 +73,13 @@ export const TextField = ({
       errorMessageProps={errorMessageProps}
       stateProps={stateProps}
     >
-      <Box
-        as="input"
-        variant="input"
+      <Input
         ref={ref}
-        css={styles.input}
-        {...inputProps}
+        /**
+         * We use `size` for styles which is a string, not like
+         * the regular HTML attribute, which is a number
+         */
+        {...(inputProps as any)}
         {...focusProps}
         {...hoverProps}
         {...stateProps}
diff --git a/packages/components/src/theme.ts b/packages/components/src/theme.ts
index 6363a222b..2332f40a8 100644
--- a/packages/components/src/theme.ts
+++ b/packages/components/src/theme.ts
@@ -9,6 +9,7 @@ import type { RootThemeExtension } from './Provider';
 import type { BadgeThemeExtension } from './Badge';
 import type { HelpTextThemeExtension } from './Field/HelpText';
 import type { ImageThemeExtension } from './Image';
+import type { InputThemeExtension } from './Input';
 import type { LabelThemeExtension } from './Field/Label';
 
 // Old Styling Solution
@@ -16,7 +17,6 @@ import { type ButtonThemeExtension } from './Button';
 import { type CardThemeExtension } from './Card';
 import { type CheckboxThemeExtension } from './Checkbox';
 import { type DividerThemeExtension } from './Divider';
-import { type InputThemeExtension } from './Input';
 import { type LabelThemeExtension as LegacyLabelThemeExtension } from './Label';
 import { type LinkThemeExtension } from './Link';
 import { type MenuThemeExtension } from './Menu';
@@ -35,6 +35,7 @@ interface ComponentStyles
   extends BadgeThemeExtension,
     HelpTextThemeExtension,
     ImageThemeExtension,
+    InputThemeExtension,
     LabelThemeExtension {}
 
 export interface Theme
@@ -44,7 +45,6 @@ export interface Theme
     CardThemeExtension<CSSObject>,
     CheckboxThemeExtension<CSSObject>,
     DividerThemeExtension<CSSObject>,
-    InputThemeExtension<CSSObject>,
     LegacyLabelThemeExtension<CSSObject>,
     LinkThemeExtension<CSSObject>,
     MenuThemeExtension<CSSObject>,
diff --git a/themes/theme-b2b/src/component.ts b/themes/theme-b2b/src/component.ts
index 0c571f0ca..407b90fa4 100644
--- a/themes/theme-b2b/src/component.ts
+++ b/themes/theme-b2b/src/component.ts
@@ -408,34 +408,7 @@ export const component = {
       bg: 'gray50',
     },
   },
-  input: {
-    __default: {
-      color: 'text',
-      border: 'none',
-      borderRadius: 'small',
-      boxShadow: '0 0 0 1px ' + colors.gray40,
-      outline: 'none',
-      padding: '0 8px',
-      lineHeight: 'large',
-      ':focus': {
-        boxShadow: '0 0 0 2px ' + colors.blue60,
-      },
-      ':disabled': {
-        bg: 'gray20',
-        color: 'gray40',
-        cursor: 'not-allowed',
-      },
-    },
-    error: {
-      color: 'text',
-      border: 'none',
-      borderRadius: 'small',
-      boxShadow: '0 0 0 1px ' + colors.red60,
-      outline: 'none',
-      padding: '0 8px',
-      lineHeight: 'large',
-    },
-  },
+
   label: {
     above: {
       fontSize: 'xxsmall',
diff --git a/themes/theme-b2b/src/components/Input.style.ts b/themes/theme-b2b/src/components/Input.style.ts
new file mode 100644
index 000000000..3217d6af0
--- /dev/null
+++ b/themes/theme-b2b/src/components/Input.style.ts
@@ -0,0 +1,33 @@
+import type { Theme } from '@marigold/components';
+import { colors } from '../colors';
+
+export const Input: Theme['components']['Input'] = {
+  base: {
+    color: 'text',
+    lineHeight: 'large',
+
+    border: '1px solid',
+    borderColor: 'gray40',
+    borderRadius: 'small',
+    outline: 'none',
+
+    py: 'none',
+    px: 'xsmall',
+
+    '&:focus': {
+      borderColor: 'blue60',
+      boxShadow: `0 0 0 1px ${colors.blue60}`,
+    },
+
+    '&:disabled': {
+      bg: 'gray20',
+      color: 'gray40',
+      cursor: 'not-allowed',
+    },
+
+    '&:error': {
+      borderColor: 'red60',
+      boxShadow: `0 0 0 1px ${colors.red60}`,
+    },
+  },
+};
diff --git a/themes/theme-b2b/src/components/index.ts b/themes/theme-b2b/src/components/index.ts
index 853e48e07..123ea2bf5 100644
--- a/themes/theme-b2b/src/components/index.ts
+++ b/themes/theme-b2b/src/components/index.ts
@@ -1,4 +1,5 @@
 export * from './Badge.style';
 export * from './HelpText.style';
 export * from './Image.style';
+export * from './Input.style';
 export * from './Label.style';
diff --git a/themes/theme-core/src/component.ts b/themes/theme-core/src/component.ts
index 30d53e797..8b1a62296 100644
--- a/themes/theme-core/src/component.ts
+++ b/themes/theme-core/src/component.ts
@@ -412,34 +412,6 @@ export const component = {
       height: '2px',
     },
   },
-  input: {
-    __default: {
-      color: 'text',
-      border: 'none',
-      borderRadius: 'small',
-      boxShadow: '0 0 0 1px #aaa',
-      outline: 'none',
-      padding: '0 4px',
-      lineHeight: '24px',
-      ':focus': {
-        boxShadow: '0 0 0 2px ' + colors.blue60,
-      },
-      ':disabled': {
-        bg: colors.gray20,
-        color: colors.gray40,
-        cursor: 'not-allowed',
-      },
-    },
-    error: {
-      color: 'text',
-      border: 'none',
-      borderRadius: 'small',
-      boxShadow: '0 0 0 1px #f33',
-      outline: 'none',
-      padding: '0 4px',
-      lineHeight: '24px',
-    },
-  },
   label: {
     above: {
       fontSize: 'xxsmall',
diff --git a/themes/theme-core/src/components/Input.style.ts b/themes/theme-core/src/components/Input.style.ts
new file mode 100644
index 000000000..60a0cc777
--- /dev/null
+++ b/themes/theme-core/src/components/Input.style.ts
@@ -0,0 +1,5 @@
+import type { Theme } from '@marigold/components';
+
+export const Input: Theme['components']['Input'] = {
+  base: {},
+};
diff --git a/themes/theme-core/src/components/index.ts b/themes/theme-core/src/components/index.ts
index 853e48e07..123ea2bf5 100644
--- a/themes/theme-core/src/components/index.ts
+++ b/themes/theme-core/src/components/index.ts
@@ -1,4 +1,5 @@
 export * from './Badge.style';
 export * from './HelpText.style';
 export * from './Image.style';
+export * from './Input.style';
 export * from './Label.style';
diff --git a/themes/theme-unicorn/src/component.ts b/themes/theme-unicorn/src/component.ts
index a2ffd5a96..6c2099bb9 100644
--- a/themes/theme-unicorn/src/component.ts
+++ b/themes/theme-unicorn/src/component.ts
@@ -404,36 +404,6 @@ export const component = {
       bg: 'gray50',
     },
   },
-  input: {
-    __default: {
-      fontFamily: 'body',
-      color: 'text',
-      border: 'none',
-      borderRadius: '2px',
-      boxShadow: '0 0 0 1px ' + colors.gray40,
-      outline: 'none',
-      padding: '0 8px',
-      lineHeight: 'large',
-      ':focus': {
-        boxShadow: '0 0 0 2px ' + colors.blue60,
-      },
-      ':disabled': {
-        bg: 'gray20',
-        color: 'gray40',
-        cursor: 'not-allowed',
-      },
-    },
-    error: {
-      fontFamily: 'body',
-      color: 'text',
-      border: 'none',
-      borderRadius: '2px',
-      boxShadow: '0 0 0 1px ' + colors.red60,
-      outline: 'none',
-      padding: '0 8px',
-      lineHeight: 'large',
-    },
-  },
   label: {
     above: {
       fontSize: 'xsmall',
diff --git a/themes/theme-unicorn/src/components/Input.style.ts b/themes/theme-unicorn/src/components/Input.style.ts
new file mode 100644
index 000000000..8f951dc6d
--- /dev/null
+++ b/themes/theme-unicorn/src/components/Input.style.ts
@@ -0,0 +1,33 @@
+import type { Theme } from '@marigold/components';
+import { colors } from '../colors';
+
+export const Input: Theme['components']['Input'] = {
+  base: {
+    color: 'text',
+    lineHeight: 'large',
+
+    border: '1px solid',
+    borderColor: 'gray40',
+    borderRadius: 'small',
+    outline: 'none',
+
+    py: 'none',
+    px: 'xsmall',
+
+    '&:focus': {
+      borderColor: 'purple60',
+      boxShadow: `0 0 0 1px ${colors.purple60}`,
+    },
+
+    '&:disabled': {
+      bg: 'gray20',
+      color: 'gray40',
+      cursor: 'not-allowed',
+    },
+
+    '&:error': {
+      borderColor: 'red60',
+      boxShadow: `0 0 0 1px ${colors.red60}`,
+    },
+  },
+};
diff --git a/themes/theme-unicorn/src/components/index.ts b/themes/theme-unicorn/src/components/index.ts
index 853e48e07..123ea2bf5 100644
--- a/themes/theme-unicorn/src/components/index.ts
+++ b/themes/theme-unicorn/src/components/index.ts
@@ -1,4 +1,5 @@
 export * from './Badge.style';
 export * from './HelpText.style';
 export * from './Image.style';
+export * from './Input.style';
 export * from './Label.style';


Test patch:
diff --git a/packages/components/src/Input/Input.test.tsx b/packages/components/src/Input/Input.test.tsx
index 9b2cebc38..c9f867541 100644
--- a/packages/components/src/Input/Input.test.tsx
+++ b/packages/components/src/Input/Input.test.tsx
@@ -8,12 +8,16 @@ const theme = {
     body: 'Inter',
     forms: 'Roboto',
   },
-  input: {
-    __default: {
-      fontFamily: 'body',
-    },
-    input2: {
-      fontFamily: 'forms',
+  components: {
+    Input: {
+      base: {
+        fontFamily: 'body',
+      },
+      variant: {
+        robo: {
+          fontFamily: 'forms',
+        },
+      },
     },
   },
 };
@@ -32,7 +36,7 @@ test('supports default variant and themeSection', () => {
 test('accepts other variant than default', () => {
   render(
     <ThemeProvider theme={theme}>
-      <Input title="input" variant="input2" />
+      <Input title="input" variant="robo" />
     </ThemeProvider>
   );
   const input = screen.getByTitle('input');
@@ -61,14 +65,3 @@ test('supports custom prop', () => {
 
   expect(input).toHaveAttribute('placeholder');
 });
-
-test('accepts custom styles prop className', () => {
-  render(
-    <ThemeProvider theme={theme}>
-      <Input className="custom-class-name" title="input" />
-    </ThemeProvider>
-  );
-  const input = screen.getByTitle(/input/);
-
-  expect(input.className).toMatch('custom-class-name');
-});
diff --git a/packages/components/src/TextField/TextField.test.tsx b/packages/components/src/TextField/TextField.test.tsx
index 6d98d8276..5828375d3 100644
--- a/packages/components/src/TextField/TextField.test.tsx
+++ b/packages/components/src/TextField/TextField.test.tsx
@@ -9,8 +9,12 @@ const theme = {
   colors: {
     blue: '#00f',
   },
-  input: {
-    borderColor: 'blue',
+  components: {
+    Input: {
+      base: {
+        borderColor: 'blue',
+      },
+    },
   },
 };
 
@@ -30,7 +34,7 @@ test('allows to change the input type', () => {
   expect(textField).toHaveAttribute('type', 'email');
 });
 
-test('input can be styled via variant', () => {
+test('input can be styled via "Input" styles', () => {
   render(
     <ThemeProvider theme={theme}>
       <TextField label="A Label" data-testid="text-field" />