1

so what i'm trying to do is to create an styled input that seem to have a fixed text (div / children / etc...) inside it.

one image can be self explenatory so this is how it looks like right now:

what i did so far

it seems simple, and the basic idea i quite is: i got some Wrapper component that has 2 children - the input itself & some element. like so:

const Input = (props: InputProps) => {
  return (
    <Wrapper>
      <InputStyled {...props} />
      <InnerElement>cm</InnerElement>
    </Wrapper>
  );
};

export default Input;

So where is the problem? The problem is when i'm trying to set width to this component. It destroys everything.

is looks like so: everything messed up

so the wrapper should get a width prop and keep the input and the text element inside of it. here is a codesandbox i created: https://codesandbox.io/s/reactjs-input-with-element-inside-forked-2kr6s?file=/src/App.tsx:0-1795

it'll be nice if someone understand what i'm doing wrong.

here are some files:

Input.tsx

import React, { InputHTMLAttributes, CSSProperties } from "react";
import styled from "styled-components";

export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  style?: CSSProperties;
  label?: string;
  value: string | number;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  text?: string;
  isDisabled?: boolean;
  hasError?: boolean;
  errorLabel?: string | Function;
  placeholder?: string;
  width?: string;
}

const defaultProps: InputProps = {
  text: "",
  onChange: () => null,
  value: ""
};

const Wrapper = styled.div<Partial<InputProps>>`
  outline: 1px dashed red;
  box-sizing: border-box;
  justify-self: flex-start; // This is what prevent the child item strech inside grid column
  border: 2px solid lightblue;
  background: lightblue;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  width: ${({ width }) => width};
`;

const InputStyled = styled.input<Partial<InputProps>>`
  box-sizing: border-box;
  flex: 1;
  outline: 1px dashed blue;
  padding: 8px;
  border: none;
  border-radius: 8px;
  max-width: 100%;

  :focus {
    outline: none;
  }
`;

const InnerElement = styled.div`
  outline: 1px dashed green;
  box-sizing: border-box;
  ${({ children }) =>
    children &&
    `
    padding: 0 8px;
    font-size: 13px;
  `};
`;

const Input = (props: InputProps) => {
  return (
    <Wrapper width={props.width}>
      <InputStyled {...props} />
      <InnerElement>{props.text}</InnerElement>
    </Wrapper>
  );
};

Input.defaultProps = defaultProps;

export default Input;


App.tsx

import * as React from "react";
import "./styles.css";
import Input from "./Input";
import styled from "styled-components";

const ContainerGrid = styled.div`
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-gap: 50px;
`;

export default function App() {
  const [value, setValue] = React.useState("");

  const handleChange = (e: any) => {
    setValue(e.target.value);
  };

  const renderInputWithElement = () => {
    return (
      <ContainerGrid>
        <Input
          placeholder="input with element inside"
          value={value}
          onChange={handleChange}
          width={"60px"} // Play with this
          text="inch"
        />
        <Input
          placeholder="input with element inside"
          value={value}
          onChange={handleChange}
          width={"110px"} // Play with this
          text="cm"
        />
        <Input
          placeholder="input with element inside"
          value={value}
          onChange={handleChange}
          width={"200px"} // Play with this
          text="mm"
        />
        <Input
          placeholder="input with element inside"
          value={value}
          onChange={handleChange}
          width={"100%"} // Play with this
          text="El"
        />
        <Input
          placeholder="input with element inside"
          value={value}
          onChange={handleChange}
          width={"100%"} // Play with this
        />
      </ContainerGrid>
    );
  };

  return (
    <div className="App">
      <h1>StyledCode</h1>
      <h3>Input with an element inside</h3>
      <p>
        This is kind of an illusion since input cannot have child element inside
        of it really.
      </p>
      <hr style={{ marginBottom: "32px" }} />
      {renderInputWithElement()}
    </div>
  );
}


Tamiross
  • 99
  • 2
  • 10

2 Answers2

1

I Fixed it for you:

https://codesandbox.io/s/reactjs-input-with-element-inside-forked-h2het

First problem was that your were setting the same width to Wrapper and to InputStyled (by passing ...props) Then things get messy. Obviously, both parent and children which has some other element next to it can't have same width. InputStyled's won't be rendered with width you give it, but lesser, as it's being stretched to left by InnerElement. If you need to pass it all the props, including the width, than you can just remove that width prop by setting width to unset

<InputStyled {...props} width="unset" />

However, there was another issue with padding interfering with width calculation. See answer to another question:

According to the CSS basic box model, an element's width and height are applied to its content box. Padding falls outside of that content box and increases the element's overall size.

As a result, if you set an element with padding to 100% width, its padding will make it wider than 100% of its containing element. In your context, inputs become wider than their parent.

You can go along with solution there. However, the one simple solution is to use width lesser than your padding, so width: calc(100% - 16px). This does the trick.

justdvl
  • 698
  • 4
  • 15
0

So i've found the answer, and its quite simple.

since the browser initializes an input with a width, it cause problems with the flexbox behavor - the input element can't shrink below its default width and is > forced to overflow the flex container.

So i added min-width: 0 to InputStyled. thats it.

enter image description here

@justdv thanx for your answer, you right about the width being on the wrong place, i fixed it but the sandbox wasnt updated. i missed that. thanx anyway for your time :)

here is my fixed sandbox: https://codesandbox.io/s/reactjs-input-with-element-inside-forked-44h8i?file=/src/Input.tsx

Thanx again for reading.

Tamiross
  • 99
  • 2
  • 10