Archive » Overriding Design Components

Overriding Design Components

 

📘 The Framer book – 🖍 Overrides – 🎨 Design components

Only in legacy projects

Design components were replaced by Smart Components. So when you now create a component on the canvas (⌘K / Ctrl+K), it’ll be a smart one. Existing design components will continue to work as before, but only in legacy projects (projects created before the launch of Framer for Developers).

The examples on this page also use the older legacy overrides; simply because the new HOC overrides don’t work in a legacy project.

Overriding component instances

We’ll override a few instances of this first design component: a ‘Buenos Aires’ card with a picture of the Obilisco.

I added custom properties for the photo and title and dubbed them Picture and Name.

Overriding custom properties

Now that Picture and Name became properties, I can change them with overrides.

In the preview: the primary component and three instances with different overrides

But you might say: “Why even use overrides to do this? You can change the image and text directly in the Properties Panel, without writing code!”

Well, with an override I can pull in an online image, like this one from Flickr:

export function Kyiv(props): Override {
    return {
        Name: "Kyiv",
        Picture:
            "https://live.staticflickr.com/4527/27119793019_7f465c3ec8_z.jpg",
    }
}

(I’m not using the full-size image but one of the smaller versions.)

And I can use global parameters. I can, for instance, change the image format or size of all the pictures at once, by putting those values in a data object.

const appState = Data({
    format: "jpg",
    width: 315 * 2,
    height: 160 * 2,
})

export function Amsterdam(props): Override {
    return {
        Name: "Amsterdam",
        Picture:
            "https://images.unsplash.com/photo-1459679749680-18eb1eb37418?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9" +
            `&fm=${appState.format}&w=${appState.width}&h=${appState.height}&fit=crop&crop=edges`,
    }
}

export function Antwerp(props): Override {
    return {
        Name: "Antwerp",
        Picture:
            "https://images.unsplash.com/photo-1556654953-3719bfc8db16?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9" +
            `&fm=${appState.format}&w=${appState.width}&h=${appState.height}&fit=crop&fp-y=.7`,
    }
}

These Amsterdam and Antwerp pictures are from Unsplash.

The Unsplash API uses imgix to dynamically resize images. This means you can do things like cropping an image to a predefined size. You can crop with a certain focal point as the center (see the Antwerp example), crop to a face in the picture, or let the API decide how best to crop (the Amsterdam example).

Overriding common properties

And you can, of course, also override the standard frame properties. Here I changed the card’s radius to 0:

export function AntwerpBorderRadius(props): Override {
    return {
        Name: "Antwerp",
        Picture:
            "https://images.unsplash.com/photo-1556654953-3719bfc8db16?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9" +
            `&fm=${appState.format}&w=${appState.width}&h=${appState.height}&fit=crop&fp-y=.7`,
        radius: 0,
    }
}

As always, when overriding a CSS property that’s not in the <Frame> API, you can do it on style, like here with the borderTopRightRadius:

export function AntwerpBorderRadius(props): Override {
    return {
        Name: "Antwerp",
        Picture:
            "https://images.unsplash.com/photo-1556654953-3719bfc8db16?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9" +
            `&fm=${appState.format}&w=${appState.width}&h=${appState.height}&fit=crop&fp-y=.7`,
        style: {
            borderTopRightRadius: 80,
        },
    }
}

Overriding the primary

When you attach an override to a component’s primary, in other words, the design component itself, it will also be applied to all its instances.

The below override, Container(), is only applied to the primary (the first card, Cabellito). Its whileHover animates the width and height of the component and gives it a shadow.

export function Container(): Override {
    return {
        animate: { height: 70, shadow: "0px 0px 0px 0px rgba(0, 0, 0, 0)" },
        whileHover: {
            height: 160,
            width: 400,
            zIndex: 1,
            shadow: "0px 5px 13px 0px rgba(0, 0, 0, 0.5)",
        },
        transition: { ease: "easeInOut" },
    }
}

And it also changes the zIndex so that the currently active instance (or more precisely: its shadow) appears in front of its neighbors.

2. Overriding the primary

But there’s a second override, attached to the background picture inside the component. It changes the opacity and saturation of the photo, also with a whileHover.

export function Photo(): Override {
    return {
        initial: { opacity: 0.5, filter: "saturate(10%)" },
        whileHover: { opacity: 1, filter: "saturate(100%)" },
    }
}

I applied both overrides to the primary, which is enough to give all the instances the same behavior.

Cloning child frames

This is a more advanced technique which you’ll rarely need, but it’s always good to know what’s possible.

As you know, each element on the canvas can only have one override attached to it. That’s why I had to use two separate overrides in the above example. One for the component itself, and a second one for the picture inside it.

In this next example, there’s just one single override applied to the primary. It resizes the component itself, changes the saturation and opacity of the picture inside it, and hides the text.

3. Overriding child frames

Here’s the trick: every component has a children property with an array of the elements it contains. And you can override that property, you can change a component’s children.

This design component has two children: the background Picture and a Name text layer.

In the Layers Panel: the design component and its Name and Picture children

React has a function to map over this children array: React.Children.map(). And we can look at their props.name to know with which child we’re dealing. (This name property contains the name you gave the element in the Layers Panel.)

export function OverridingChildFrames(props): Override {

    return {
        // Animate the Frame itself
        …
        children: React.Children.map(props.children, child => {
            if (child.props.name === "Picture") {
                // Apply an animation to the picture
            } else if (child.props.name === "Name") {
                // Hide the text
            } else {
                // It’s some other child: do nothing
            }
        }),
    }
}

There’s only one catch: you can’t directly change a child’s properties like you can with the parent. You need to make a copy of the child, a clone (with React.cloneElement()), and change (or add) properties while doing this.

So, when the child is a "Picture", I clone it and add an animate for its opacity and saturation. And when it’s a "Name", I clone it and hide the text when needed (by setting it to "").

export function OverridingChildFrames(props): Override {
    const [hover, setHover] = React.useState(false)

    return {
        // Hover events + animating the Frame itself
        …
        children: React.Children.map(props.children, child => {
            if (child.props.name === "Picture") {
                return React.cloneElement(child as any, {
                    animate: {
                        opacity: hover ? 1 : 0.5,
                        filter: `saturate(${hover ? 100 : 10}%)`,
                    },
                })
            } else if (child.props.name === "Name") {
                return React.cloneElement(child as any, {
                    text: hover ? "" : props.text,
                })
            } else {
                return React.cloneElement(child as any)
            }
        }),
    }
}

… and in the else case, when it isn’t a "Picture" or "Name", it just gets cloned with no changes.

Notice that I’m not using onHover animations anymore. Since everything is now in one and the same override, I can use a global hover state which triggers all the animations at the same time.

export function OverridingChildFrames(props): Override {
    const [hover, setHover] = React.useState(false)

    return {
        onHoverStart() {
            setHover(true)
        },
        onHoverEnd() {
            setHover(false)
        },
        …
        // Animating the frame itself + cloning the children
    }
}

Here’s the complete override, including the animate for the container frame:

import { Override } from "framer"
import * as React from "react"

export function OverridingChildFrames(props): Override {
    const [hover, setHover] = React.useState(false)

    return {
        onHoverStart() {
            setHover(true)
        },
        onHoverEnd() {
            setHover(false)
        },
        animate: {
            height: hover ? props.height : 70,
            width: hover ? 400 : props.width,
            zIndex: hover ? 1 : 0,
            shadow: hover
                ? "0px 5px 13px 0px rgba(0, 0, 0, 0.5)"
                : "0px 5px 13px 0px rgba(0, 0, 0, 0)",
        },
        children: React.Children.map(props.children, child => {
            if (child.props.name === "Picture") {
                return React.cloneElement(child as any, {
                    animate: {
                        opacity: hover ? 1 : 0.5,
                        filter: `saturate(${hover ? 100 : 10}%)`,
                    },
                })
            } else if (child.props.name === "Name") {
                return React.cloneElement(child as any, {
                    text: hover ? "" : props.text,
                })
            } else {
                return React.cloneElement(child as any)
            }
        }),
    }
}

4 comments on “Overriding Design Components”

Leave a Reply