Hello, World!

This is your first blog post.

📅 January 1, 2023 (over 1 year ago) - 📖 13 min read - 👀 10 views

Hello, World!

  1. Create a new file .env.local and add environment variables with those values. .env.local
.env.local
carrot
banana
awewome

datePublisheddsadsa

const [age, setAge] = useState(50)
const [name, setName] = useState('Taylor')
const something = 1
const something = 1
const something = 1
const something = 1
 
const something = 1
const something = 1
const something = 1
const something = 1
const something = 1
CSS modules
You want class names scoped to the corresponding component.
You want to use CSS variables to reuse values.
You need your application to work without JavaScript.
You want the lowest barrier to entry.
You want to use any language, not just JavaScript.
You might not use CSS modules if...
You don't want to configure a toolchain like Webpack.
You need to distribute a package on NPM.
You want nesting support by default (requires PostCSS).
You don't want to context switch between files.

As you walk through the office at work reading the news on your phone, you enter an elevator. You had just attempted to load a new page only to be greeted with a painful loading spinner. No one likes this experience.

It's inevitable that some users of your application will have slow connections. A well thought out design accounts for varying internet speeds and displays a loading state to the user. However, making the user stare at a spinning wheel for an extended period of time can drastically increase bounce rates. What if there was a better way?

Skeleton Screens

Skeleton screens build anticipation for the content that is going to appear whereas loading spinners (and progress bars) put the focus on the wait time that the user has to endure. Apple has agreed with this idea enough to incorporate skeleton screens into their iOS Human Interface Guidelines. They recommend displaying an outline of the initial application without text or any elements that will change. This can improve the feel of any action taking longer than a few hundred milliseconds.

Examples

By now, you've probably seen some examples of skeleton screens in your daily browsing without even noticing. For example - Facebook shows users gray circles and lines to represent the contents of a post in their timeline.

It's not just Facebook either. LinkedIn has also re-designed their layout to use placeholders.

You can trick your users into thinking your website loads faster using skeleton screens. Let's look at how you can actually create this effect using some simple HTML and Scss.

Building a Placeholder

First, let's create the base structure. In this example, the placeholder is supposed to represent a text area. We'll use BEM (Base - Element - Modifier) naming for our classes.

  1. Create a project in Firebase.
  2. In the Firebase console, open Settings > Service Accounts.
  3. Click Generate New Private Key, then confirm by clicking Generate Key.
  4. Download and open the JSON file containing your service account.
  5. Create a new file .env.local and add environment variables with those values.
.env.local
NEXT_PUBLIC_FIREBASE_PROJECT_ID=replace-me
FIREBASE_CLIENT_EMAIL=replace-me
FIREBASE_PRIVATE_KEY=replace-me

You can now fetch data from Firebase directly inside a Server Component in the app directory:

app/page.tsx
import 'server-only'
import { notFound } from 'next/navigation'
import * as admin from 'firebase-admin'
 
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    }),
  })
}
 
const db = admin.firestore()
 
export default async function Page() {
  const user = await db.collection('users').doc('leerob').get()
 
  if (!user.exists) {
    notFound()
  }
 
  return <div>Hello, {user.data().name}!</div>
}
<div class="text-input__loading">
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
  <div class="text-input__loading--line"></div>
</div>

Each line should be about the same height as our text. We can use CSS animation to create a pulsating effect.

&--line {
  height: 10px;
  margin: 10px;
  animation: pulse 1s infinite ease-in-out;
}

Next, let's define how our pulse animation should work. We can modify the opacity of the background color using rgba to provide an opacity between 0.0 and 1.0.

@keyframes pulse {
  0% {
    background-color: rgba(165, 165, 165, 0.1);
  }
  50% {
    background-color: rgba(165, 165, 165, 0.3);
  }
  100% {
    background-color: rgba(165, 165, 165, 0.1);
  }
}

We also want to vary the width of each loading line. Let's create a Sass mixin to apply the given content to each nth-child in a list.

@mixin nth-children($points...) {
  @each $point in $points {
    &:nth-child(#{$point}) {
      @content;
    }
  }
}

We can use the newly created mixin to change the width of all 10 children div elements.

@include nth-children(1, 5, 9) {
  width: 150px;
}
@include nth-children(2, 6, 10) {
  width: 250px;
}
@include nth-children(3, 7) {
  width: 50px;
}
@include nth-children(4, 8) {
  width: 100px;
}

Final Result 🎉

Loading Placeholder Final Result

You can view the code and a live example on CodePen. There's also a React library called react-placeholder that achieves the same effect.

Further Reading:

As well as a few others tools preconfigured:

Getting Started

Clone the design system example locally or from GitHub:

npx degit vercel/turborepo/examples/design-system design-system
cd design-system
yarn install
git init . && git add . && git commit -m "Init"

Useful Commands

  • yarn build - Build all packages including the Storybook site
  • yarn dev - Run all packages locally and preview with Storybook
  • yarn lint - Lint all packages
  • yarn changeset - Generate a changeset
  • yarn clean - Clean up all node_modules and dist folders (runs each package's clean script)

Turborepo

Turborepo is a high-performance build system for JavaScript and TypeScript codebases. It was designed after the workflows used by massive software engineering organizations to ship code at scale. Turborepo abstracts the complex configuration needed for monorepos and provides fast, incremental builds with zero-configuration remote caching.

Using Turborepo simplifes managing your design system monorepo, as you can have a single lint, build, test, and release process for all packages. Learn more about how monorepos improve your development workflow.

Apps & Packages

This Turborepo includes the following packages and applications:

  • apps/docs: Component documentation site with Storybook
  • packages/@acme/core: Core React components
  • packages/@acme/utils: Shared React utilities
  • packages/@acme/tsconfig: Shared tsconfig.jsons used throughout the Turborepo
  • packages/eslint-preset-acme: ESLint preset

Each package and app is 100% TypeScript. Yarn Workspaces enables us to "hoist" dependencies that are shared between packages to the root package.json. This means smaller node_modules folders and a better local dev experience. To install a dependency for the entire monorepo, use the -W workspaces flag with yarn add.

This example sets up your .gitignore to exclude all generated files, other folders like node_modules used to store your dependencies.

Compilation

To make the core library code work across all browsers, we need to compile the raw TypeScript and React code to plain JavaScript. We can accomplish this with tsup, which uses esbuild to greatly improve performance.

Running yarn build from the root of the Turborepo will run the build command defined in each package's package.json file. Turborepo runs each build in parallel and caches & hashes the output to speed up future builds.

For acme-core, the build command is the following:

tsup src/index.tsx --format esm,cjs --dts --external react

tsup compiles src/index.tsx, which exports all of the components in the design system, into both ES Modules and CommonJS formats as well as their TypeScript types. The package.json for acme-core then instructs the consumer to select the correct format:

acme-core/package.json
{
  "name": "@acme/core",
  "version": "0.0.0",
  "main": "./dist/index.js",
  "module": "./dist/index.mjs",
  "types": "./dist/index.d.ts",
  "sideEffects": false
}

Run yarn build to confirm compilation is working correctly. You should see a folder acme-core/dist which contains the compiled output.

acme-core
└── dist
    ├── index.t.ts  <-- Types
    ├── index.js    <-- CommonJS version
    └── index.mjs   <-- ES Modules version

Components

Each file inside of acme-core/src is a component inside our design system. For example:

acme-core/src/Button.tsx
import * as React from 'react'
 
export interface ButtonProps {
  children: React.ReactNode
}
 
export function Button(props: ButtonProps) {
  return <button>{props.children}</button>
}
 
Button.displayName = 'Button'

When adding a new file, ensure the component is also exported from the entry index.tsx file:

acme-core/src/index.tsx
import * as React from 'react'
export { Button, type ButtonProps } from './Button'
// Add new component exports here

Storybook

Storybook provides us with an interactive UI playground for our components. This allows us to preview our components in the browser and instantly see changes when developing locally. This example preconfigures Storybook to:

  • Use Vite to bundle stories instantly (in milliseconds)
  • Automatically find any stories inside the stories/ folder
  • Support using module path aliases like @acme/core for imports
  • Write MDX for component documentation pages

For example, here's the included Story for our Button component:

apps/docs/stories/button.stories.mdx
import { Button } from '@acme/core/src';
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
 
<Meta title="Components/Button" component={Button} />
 
# Button
 
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget consectetur tempor, nisl nunc egestas nisi, euismod aliquam nisl nunc euismod.
 
## Props
 
<Props of={Box} />
 
## Examples
 
<Preview>
  <Story name="Default">
    <Button>Hello</Button>
  </Story>
</Preview>
picture of me
Written by Nathan Brachotte

I'm a Product Engineer at heart, I've helped many companies build great team culture and craft high-performance, customer-centric, well-architected apps.
Always aiming for that UI & UX extra touch