Tag: Magic

Rotated Table Column Headers… Now With Fewer Magic Numbers!

Rotated <table> column headers is something that’s been covered before right here on CSS-Tricks, so shout-out to that for getting me started and helping me achieve this effect. As the article points out, if you aren’t using trigonometry to calculate your table styles, you’ll have to rely on magic numbers and your table will be brittle and any dreams of responsiveness crushed. 

Fortunately, in this case, we can take the trigonometry out and replace it with some careful geometry and our magic numbers all turn into 0 (a truly magical number).

For those in a hurry, here is the CSS (it’s very similar to the styles in the other article). Below is a thorough walk-through.

<th class="rotate"><div><span>Column Header 1</span></div></th>
table {  border-collapse: collapse;  --table-border-width: 1px; } th.rotate {   white-space: nowrap;   position: relative; 
} th.rotate > div {   /* place div at bottom left of the th parent */   position: absolute;   bottom: 0;   left: 0;   /* Make sure short labels still meet the corner of the parent otherwise you'll get a gap */   text-align: left;   /* Move the top left corner of the span's bottom-border to line up with the top left corner of the td's border-right border so that the border corners are matched    * Rotate 315 (-45) degrees about matched border corners */   transform:      translate(calc(100% - var(--table-border-width) / 2), var(--table-border-width))     rotate(315deg);   transform-origin: 0% calc(100% - var(--table-border-width));   width: 100%; 
} th.rotate > div > span {   /* make sure the bottom of the span is matched up with the bottom of the parent div */   position: absolute;   bottom: 0;   left: 0;   border-bottom: var(--table-border-width) solid gray; } td {   border-right: var(--table-border-width) solid gray;   /* make sure this is at least as wide as sqrt(2) * height of the tallest letter in your font or the headers will overlap each other*/   min-width: 30px;   padding-top: 2px;   padding-left: 5px;   text-align: right; }

Let’s unpack this table and see what’s going on. The magic starts with that funny chain of HTML tags. We’re putting a <span> inside of a <div> inside of our <th>. Is this all really necessary? Between how borders behave, the positioning flexibility we need, and what determines the width of a table column… yes, they each have a purpose and are necessary.

Let’s see what happens if we rotate the <th> directly:

<th class="rotate">Column header 1</th>
table {   border-collapse: collapse; } th.rotate {   border-bottom: 1px solid gray;   transform: rotate(315deg);   white-space: nowrap; } td {   border-right: 1px solid gray;   min-width: 30px;   padding-top: 2px;   padding-left: 5px;   text-align: right; }

Ignoring the fact that we haven’t corrected position, there are two big issues here: 

  1. The column width is still calculated from the header length which is what we were trying to avoid.
  2. Our border didn’t come with us in the rotation, because it is actually part of the table.

These problems aren’t so difficult to fix. We know that if the <th> has a child element with a border, the browser won’t treat that border as part of the table. Further, we know that absolutely-positioned elements are taken out of the document flow and won’t affect the parent’s width. Enter <div> tag, stage left…and right, I guess.

<th class="rotate"><div>Column header 1</div></th>
table {   border-collapse: collapse; } th.rotate {   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   transform: rotate(315deg);   border-bottom: 1px solid gray; } td {   border-right: 1px solid gray;   min-width: 30px;   text-align: right;   padding-top: 2px;   padding-left: 5px; }
Now our headers don’t influence the column width and the borders are rotated. We just need to line things up.

It’s easier to tell in the image with the rotated <th> elements, but that rotation is happening around the center of the element (that’s the default behavior of transform-origin). It is only another transform in x and y to get it to the right spot, but this is where we’d need trigonometry to figure out just how much x and y to line it up with the column borders. If we instead carefully choose the point to rotate the header about, and use transform-origin to select it, then we can end up with distances that are more straightforward than magic numbers.

The animation below helps illustrate what we’re going to do to avoid complicated math. The black dot in the top left of the blue border needs to match the red dot on the right border of the table column and rotate about it. Then there won’t be any gaps between the two borders.

It’s not helpful to start going somewhere if you don’t know where you are. The absolute positioning is going to help us out with this. By specifying bottom: 0; left: 0; on the <div>, it ends up at the bottom left of the parent <th>. This means the <div> border’s bottom-left corner is sitting on top of the left column border and halfway through it. From here, it’s apparent we need to move down one border width and over one cell width, but how are we going to get that responsively? It’s at this very moment you may recall that we haven’t added the <span> yet — we’re going to need it!

We’ll use the <div> to “figure out” how big the table cells are and the <span> to actually hold the text and position it absolutely as well to overflow the parent.

<th class="rotate"><div><span>Column header 1</span></div></th>
th.rotate{   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   bottom: 0;   left: 0;   width: 100%;  /* <- now the div parent is as wide as the columns */ } th.rotate > div > span {   position: absolute;   bottom: 0;   left: 0;   border-bottom: 1px solid gray; }

Great! When we set the width of the <div> to 100%, it holds the information for how big the column is regardless of what the content is in the table cells. With this in place, we can easily translate things over by the width of the <div> — but don’t forget that we need to shave off a half border width. Our translation becomes:

transform: translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));

The <div> is now in the right spot to rotate, but we have to make sure to pick the correct transform-origin. We want it to be on the top-left corner of the border, which will be on the left and up one border’s width from the bottom of our <div> element:

transform-origin: 0%, calc(100% - var(--table-border-width));

This brings us to our final style for the table header.

table {   border-collapse: collapse;   --table-border-width: 1px; } th.rotate{   white-space: nowrap;   position: relative; } th.rotate > div {   position: absolute;   bottom: 0;   left: 0;   width: 100%;   transform:     translate( calc( 100% - var(--table-border-width)/2), var(--table-border-width));     rotate(315deg);   transform-origin: 0%, calc(100% - var(--table-border-width)); } th.rotate > div > span {   position: absolute;   bottom: 0;   left: 0;   border-bottom: var(--table-border-width) solid gray; }

Note that transformations happen after everything is placed. That means the rotated headers will overflow onto everything as best they can. You will need to wrap the whole table in something to compensate for the unexpected height. I put the title and table together in a flexbox <div> and set the flex-basis of the title to a value large enough to compensate for the tall headers.

#div-with-table {   display: flex;   flex-direction: column;   justify-content: space-around; } #title {   flex-basis: 140px; }

The post Rotated Table Column Headers… Now With Fewer Magic Numbers! appeared first on CSS-Tricks.

CSS-Tricks

, , , , , ,

min(), max(), and clamp() are CSS magic!

Nice video from Kevin Powell. Here are some notes, thoughts, and stuff I learned while watching it. Right when they came out, I was mostly obsessed with font-size usage, but they are just functions, so they can be used anywhere you’d use a number, like a length.

Sometimes pretty basic usage allows for tighter code, but the change to get there feels a little mind-bending. Like how to set a max() here, you really use min().

.el {   width: 75%;   max-width: 600px;    /* tighter, but the change from max to min feels weird */   width: min(75%, 600px); }

The min() and max() functions can take more than two values, which is cool, but certainly mind-bending in its own right. It would be nice if DevTools could tell you which one it picked at any given time.

.el {   width: min(100px, 25%, 50vh, 30ch); }

You don’t need a calc() to do math inside! This is cool:

.el {   width: min(10vw + 10%, 100px); }

It’s reasonable you’d want to be setting a min and max value. You can nest the functions to do this, but it’s less mind-bendy to do with clamp().

.el {   /* Note we're always using a relative unit somewhere      so that zooming stays effective. */   font-size: clamp(0.9rem, 1vw + 1rem, 2.2rem); }

Here’s the video embedded:

Direct Link to ArticlePermalink

The post min(), max(), and clamp() are CSS magic! appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

The Magic of React-Based Multi-Step Forms

One way to deal with long, complex forms is to break them up into multiple steps. You know, answer one set of questions, move on to another, then maybe another, and so on and so forth. We often refer to these as multi-step forms (for obvious reasons), but others also take to calling it a “wizard” form.

Multi-step forms can be a great idea! By only showing a few inputs on a screen at a time, the form may feel more digestible and prevent users from feeling overwhelmed by a sea of form fields. Although I haven’t looked it up, I’m willing to say no one enjoys completing a ginormous form — that’s where multiple steps can come in handy.

The problem is that multi-step forms — while reducing perceived complexity on the front end — can feel complex and overwhelming to develop. But, I’m here to tell you that it’s not only achievable, but relatively straightforward using React as the base. So, that’s what we’re going to build together today!

Here’s the final product:

See the Pen
React Simple Wizard Form
by Nathan Sebhastian (@nathansebhastian)
on CodePen.

Let’s build it!

The easiest way to create a multi-step form is to create a container form element that contains all the steps inside of it as components. Here’s a visual showing that container (<MasterForm/>), the components inside of it (<Step1/>, <Step2/>, <Step3/>) and the way states and props are passed between them.

A diagram showing the master form component above three rectangles representing the three steps of the form, from left to right. Between the master form and the steps is a constructor, setState, and render.
<MasterForm/> serves as the container while three child components inside of it act as each step of the form.

Although it seems to be more complex than a regular form, a multi-step form still uses the same principles as a React form:

  • State is used for storing data and user inputs.
  • Component is used for writing methods and the interface.
  • Props are used for passing data and function into elements.

Instead of having one form component, we will have one parent component and three child components. In the diagram above, <MasterForm/> will send data and functions to the child components via props, and in turn, the child components will trigger a handleChange() function to set values in the state of <MasterForm/>. It’s one big happy family over here!

We’ll need a function to move the form from one step to another as well, and we’ll get to that a little later.

The step child (get it?) components will receive props from the <MasterForm/> parent component for value and onChange props.

  • <Step1/> component will render an email address input
  • <Step2/> will render a username input
  • <Step3/> will render a password input and a submit button

<MasterForm/> will supply both data and function into child components, and child components will pass user inputs back to the parent using its props.

Creating the step (child) components

First, we’ll create the form’s child components. We’re keeping things pretty barebones for this example by only using one input per step, but each step could really be as complex as we’d like. Since the child components look almost similar between one another, I’m just gonna show one of them here. But be sure to take a look at the demo for the full code.

class Step1 extends React.Component {   render() {     if (this.props.currentStep !== 1) { // Prop: The current step       return null     }     // The markup for the Step 1 UI     return(       <div className="form-group">         <label htmlFor="email">Email address</label>         <input           className="form-control"           id="email"           name="email"           type="text"           placeholder="Enter email"           value={this.props.email} // Prop: The email input data           onChange={this.props.handleChange} // Prop: Puts data into state         />       </div>     )   } }

Now we can put this child component into the form’s render() function and pass in the necessary props. Just like in React’s form documentation, we can still use handleChange() to put the user’s submitted data into state with setState(). A handleSubmit() function will run on form submit.

Next up, the parent component

Let’s make the parent component — which we’re all aware by now, we’re calling <MasterForm/> — and initialize its state and methods.

We’re using a currentStep state that will be initialized with a default value of 1, indicating the first step (<Step1/>) of the form. We’ll update the state as the form progresses to indicate the current step.

class MasterForm extends Component {   constructor(props) {     super(props)     // Set the initial input values     this.state = {       currentStep: 1, // Default is Step 1       email: '',       username: '',       password: '',      }     // Bind the submission to handleChange()      this.handleChange = this.handleChange.bind(this)   }    // Use the submitted data to set the state   handleChange(event) {     const {name, value} = event.target     this.setState({       [name]: value     })       }      // Trigger an alert on form submission   handleSubmit = (event) => {     event.preventDefault()     const { email, username, password } = this.state     alert(`Your registration detail: n        Email: $ {email} n        Username: $ {username} n       Password: $ {password}`)   }      // Render UI will go here... }

OK, that’s the baseline functionality we’re looking for. Next, we want to create the shell UI for the actual form add call the child components in it, including the required state props that will be passed from <MasterForm/> via handleChange().

render() {     return (   <React.Fragment>   <h1>A Wizard Form!</h1>   <p>Step {this.state.currentStep} </p>         <form onSubmit={this.handleSubmit}>        // Render the form steps and pass in the required props     <Step1        currentStep={this.state.currentStep}        handleChange={this.handleChange}       email={this.state.email}     />     <Step2        currentStep={this.state.currentStep}        handleChange={this.handleChange}       username={this.state.username}     />     <Step3        currentStep={this.state.currentStep}        handleChange={this.handleChange}       password={this.state.password}     />           </form>   </React.Fragment> ) }

One step at a time

So far, we’ve allowed users to fill the form fields, but we’ve provided no actual way to proceed to the next step or head back to the previous one. That calls for next and previous functions that check if the current step has a previous or next step; and if it does, push the currentStep prop up or down accordingly.

class MasterForm extends Component {   constructor(props) {     super(props)     // Bind new functions for next and previous     this._next = this._next.bind(this)     this._prev = this._prev.bind(this)   }    // Test current step with ternary   // _next and _previous functions will be called on button click   _next() {     let currentStep = this.state.currentStep     // If the current step is 1 or 2, then add one on "next" button click     currentStep = currentStep >= 2? 3: currentStep + 1     this.setState({       currentStep: currentStep     })   }        _prev() {     let currentStep = this.state.currentStep     // If the current step is 2 or 3, then subtract one on "previous" button click     currentStep = currentStep <= 1? 1: currentStep - 1     this.setState({       currentStep: currentStep     })   } }

We’ll use a get function that will check whether the current step is 1 or 3. This is because we have three-step form. Of course, we can change these checks as more steps are added to the form. We also want to display the next and previous buttons only if there actually are next and previous steps to navigate to, respectively.

// The "next" and "previous" button functions get previousButton(){   let currentStep = this.state.currentStep;   // If the current step is not 1, then render the "previous" button   if(currentStep !==1){     return (       <button          className="btn btn-secondary"          type="button" onClick={this._prev}>       Previous       </button>     )   }   // ...else return nothing   return null; }  get nextButton(){   let currentStep = this.state.currentStep;   // If the current step is not 3, then render the "next" button   if(currentStep <3){     return (       <button          className="btn btn-primary float-right"          type="button" onClick={this._next}>       Next       </button>             )   }   // ...else render nothing   return null; }

All that’s left is to render those buttons:

// Render "next" and "previous" buttons render(){   return(     <form onSubmit={this.handleSubmit}>       {/*          ... other codes       */}              {this.previousButton}       {this.nextButton}            </form>   ) }

Congrats, you’re a form wizard! 🧙

That was the last step in this multi-step tutorial on multi-step forms. Whoa, how meta! While we didn’t go deep into styling, hopefully this gives you a solid overview of how to go about making complex forms less… complex!

Here’s that final demo again so you can see all the code in it’s full and glorious context:

See the Pen
React Simple Wizard Form
by Nathan Sebhastian (@nathansebhastian)
on CodePen.

React was made for this sort of thing considering it makes use of states, property changes, reusable components and such. I know that React may seem like a high barrier to entry for some folks, but I’ve written a book that makes it a much lower hurdle. I hope you check it out!

The post The Magic of React-Based Multi-Step Forms appeared first on CSS-Tricks.

CSS-Tricks

, , ,
[Top]