Mantine Flip

Undolog

@gfazioli/mantine-flip

A Mantine component that flips the content of the component when hovered over.

Installation

yarn add @gfazioli/mantine-flip

After installation import package styles at the root of your application:

import '@gfazioli/mantine-flip/styles.css';

You can import styles within a layer @layer mantine-flip by importing @gfazioli/mantine-flip/styles.layer.css file.

import '@gfazioli/mantine-flip/styles.layer.css';

Usage

Norway

Norway Fjord Adventures

On Sale

With Fjord Tours you can explore more of the magical fjord landscapes with tours and activities on and around the fjords of Norway

Edit Widget

Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum.

Duration
import { Flip } from '@gfazioli/mantine-flip'';

function Demo() {
  return (
    <Center>
      <Flip h={200} w={400}>
        <Card shadow="sm" padding="lg" radius="md" withBorder>
          <Card.Section>
            <Image
              src="https://images.unsplash.com/photo-1527004013197-933c4bb611b3?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=720&q=80"
              height={160}
              alt="Norway"
            />
          </Card.Section>

          <Group justify="space-between" mt="md" mb="xs">
            <Text fw={500}>Norway Fjord Adventures</Text>
            <Badge color="pink" variant="light">
              On Sale
            </Badge>
          </Group>

          <Text size="sm" c="dimmed">
            With Fjord Tours you can explore more of the magical fjord landscapes with tours and
            activities on and around the fjords of Norway
          </Text>

          <Group justify="right">
            <Flip.Target>
              <Button color="blue" mt="md" radius="md">
                Edit Widget
              </Button>
            </Flip.Target>
          </Group>
        </Card>

        <Paper bg="dark" radius="md" withBorder p="lg">
          <Stack>
            <Title order={4} c="white">
              Edit Widget
            </Title>
            <Text c="gray" size="sm">
              Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, voluptatum.
            </Text>
            <Switch c="white" defaultChecked label="Display image" />
            <Switch c="white" label="Auto play" />
            <Group justify="left">
              <Flip.Target>
                <Button color="red" mt="md" radius="md">Close</Button>
              </Flip.Target>
            </Group>
          </Stack>
        </Paper>
      </Flip>
    </Center>
  );
}

IMPORTANT

The Flip component must have exactly two children. The first child is the front face, and the second child is the back face.

Uncontrolled

Front Card

The front card

Back Card

import { Flip } from '@gfazioli/mantine-flip';

function Demo() {
  return (
    <Flip h={200} w={200}>

    <Paper radius="md" withBorder p="lg" shadow="md">
      <h3>Front Card</h3>
      <Flip.Target>
        <Button>Flip Back</Button>
      </Flip.Target>
    </Paper>

    <Paper radius="md" withBorder p="lg" shadow="md">
      <h3>Back Card</h3>
      <Flip.Target>
        <Button>Flip Front</Button>
      </Flip.Target>
    </Paper>

  </Flip>
  );
}

Controlled

You may control the state of the card by setting the flipped prop to true or false. This is useful when you want to control the state of the card from the parent component.

Front Card

The front card

Back Card

function Demo() {
  const [flipped, setFlipped] = useState(false);

  return (
    <Stack>
      <Group>
        <Switch checked={flipped}
                onChange={(event) => setFlipped(event.currentTarget.checked)}
                label="Show settings" />
      </Group>

    <Flip h={200} w={400} flipped={flipped}>

      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Front Card</h3>
        <p>The front card</p>
        <Group justify="right">
          <Button onClick={()=>setFlipped(true)}>Show Settings</Button>
        </Group>
      </Paper>

      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Back Card</h3>
        <Button onClick={()=>setFlipped(false)} variant="outline">Back to Front</Button>
      </Paper>

    </Flip>
    </Stack>
  );
}

Initial face and Flip.Target

For controlled Flip components, you will use useState to determine which side of the component to initially show. Additionally, you will not need to use Flip.Target for buttons, but you can control them manually to keep the states synchronized.

Default Flipped (face)

You may display the initial face of the card by setting the defaultFlipped prop to true.

Front Card

The front card

Back Card

In this case the defaultFlipped prop is set to true, and the back card is visible

import { Flip } from '@gfazioli/mantine-flip';

function Demo() {
  return (
    <Flip h={200} w={400} defaultFlipped={true}>

      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Front Card</h3>
            <p>The front card</p>
            <Group justify="right">
              <Flip.Target>
                  <Button>Flip to Back</Button>
              </Flip.Target>
            </Group>
      </Paper>

      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Back Card</h3>
        <p>In this case the <Code>defaultFlipped</Code> prop is set to <Code>true</Code>, and the back card is visible</p>
          <Flip.Target>
            <Button variant="outline">Flip to Front</Button>
          </Flip.Target>
      </Paper>

    </Flip>
  );
}

Direction

You can change the flip rotation direction to vertical using the direction prop.

Front Side

Click the button below to flip vertically

Back Side

Click the button below to flip back

import { Flip } from '@gfazioli/mantine-flip';
import { Button, Group, Paper, Text } from '@mantine/core';

function Demo() {
  return (
    <Group justify="center">
      <Flip w={200} h={300} direction="vertical">
        <Paper shadow="md" p="xl" withBorder w="100%" h="100%">
          <Text>Front Side</Text>
          <Text size="sm" c="dimmed" mt="md">
            Click the button below to flip vertically
          </Text>
          <Flip.Target>
            <Button fullWidth mt="xl">
              Flip Vertical
            </Button>
          </Flip.Target>
        </Paper>

        <Paper shadow="md" p="xl" withBorder w="100%" h="100%">
          <Text>Back Side</Text>
          <Text size="sm" c="dimmed" mt="md">
            Click the button below to flip back
          </Text>
          <Flip.Target>
            <Button fullWidth mt="xl" color="red">
              Flip Back
            </Button>
          </Flip.Target>
        </Paper>
      </Flip>
    </Group>
  );
}

Custom Flip Transitions

You can customize the flip transition direction using directionFlipIn and directionFlipOut props.

Custom Transitions

Flip In: Positive
Flip Out: Negative

Back Side

Notice the different rotation direction

import { Flip } from '@gfazioli/mantine-flip';
import { Button, Group, Paper, Text } from '@mantine/core';

function Demo() {
  return (
    <Group justify="center">
      <Flip w={300} h={200} directionFlipIn="positive" directionFlipOut="negative">
        <Paper shadow="md" p="xl" withBorder w="100%" h="100%">
          <Text fw={700}>Custom Transitions</Text>
          <Text size="sm" mt="xs">
            Flip In: Positive
            <br />
            Flip Out: Negative
          </Text>
          <Flip.Target>
            <Button mt="md">Flip Me</Button>
          </Flip.Target>
        </Paper>

        <Paper shadow="md" p="xl" withBorder w="100%" h="100%" bg="var(--mantine-color-blue-light)">
          <Text fw={700}>Back Side</Text>
          <Text size="sm" mt="xs">
            Notice the different rotation direction
          </Text>
          <Flip.Target>
            <Button mt="md" variant="white">
              Flip Back
            </Button>
          </Flip.Target>
        </Paper>
      </Flip>
    </Group>
  );
}

Flip Target

You can use Flip.Target to make any element clickable to toggle the flip state. This is useful when you want to trigger the flip from a button or a specific area of the card.

This card has multiple targets

Text Target

Back Side

import { Flip } from '@gfazioli/mantine-flip';
import { Button, Group, Paper, Text } from '@mantine/core';

function Demo() {
  return (
    <Group justify="center">
      <Flip w={300} h={200}>
        <Paper shadow="md" p="xl" withBorder w="100%" h="100%">
          <Text>This card has multiple targets</Text>
          
          <Group mt="xl">
            <Flip.Target>
              <Button>Button Target</Button>
            </Flip.Target>
            
            <Flip.Target>
              <Text style={{ cursor: 'pointer' }} td="underline" c="blue">
                Text Target
              </Text>
            </Flip.Target>
          </Group>
        </Paper>

        <Paper shadow="md" p="xl" withBorder w="100%" h="100%">
          <Text>Back Side</Text>
          <Flip.Target>
            <Button mt="xl" color="gray">
              Go Back
            </Button>
          </Flip.Target>
        </Paper>
      </Flip>
    </Group>
  );
}

Styled Flip component

You can style the Flip component using the classNames prop to target specific inner elements. This allows for granular customization of the component's appearance.

Front Card

Back Card

import { Flip } from '@gfazioli/mantine-flip';

function Demo() {
  return (
    <Flip h={200} w={200}{{props}}>
      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Front Card</h3>
        <Flip.Target>
          <Button>Show Back</Button>
        </Flip.Target>
      </Paper>

      <Paper radius="md" withBorder p="lg" shadow="md">
        <h3>Back Card</h3>
        <Flip.Target>
          <Button>Show Front</Button>
        </Flip.Target>
      </Paper>
    </Flip>
  );
}

Styles API

Flip supports Styles API, you can add styles to any inner element of the component with classNames prop. Follow Styles API documentation to learn more.

You can customize the appearance of the Flip component using the Styles API. This allows you to modify styles for various parts of the component, such as the container, front face, back face, and other inner elements.

Front Card

Back Card

Component Styles API

Hover over selectors to highlight corresponding elements

/*
 * Hover over selectors to apply outline styles
 *
 */

Example: Credit card

The Bank

Press ESC to flip back

function Demo() {
  const [flipped, { toggle, close, open }] = useDisclosure(false);

  const theme = useMantineTheme();
  const { colorScheme } = useMantineColorScheme();
  useHotkeys([['escape', close]]);


  function FrontCreditCard() {
    return (
      <Paper
        onClick={() => open()}
        style={{ cursor: 'pointer' }}
        bg={getGradient({ deg: 180, from: 'blue', to: 'cyan.7' }, theme)}
        w={400}
        h={220}
        withBorder
        shadow="md"
        p={30}
        radius="md"
      >
        <Stack>
          <Title order={3} c="white" style={{ textAlign: 'right' }}>
            The Bank
          </Title>
          <Paper
            w={60}
            h={40}
            radius="sm"
            shadow="none"
            p={10}
            bg={getGradient({ deg: 45, from: 'yellow.1', to: 'yellow.6' }, theme)}
          />
        </Stack>
      </Paper>
    );
  }

  function BackCreditCard() {
    return (
      <Paper
        bg={
          colorScheme === 'light'
            ? getGradient({ deg: 180, from: 'gray.1', to: 'indigo.1' }, theme)
            : getGradient({ deg: 180, from: 'dark.8', to: 'dark.9' }, theme)
        }
        w={400}
        h={220}
        withBorder
        shadow="md"
        p={30}
        radius="md"
      >
        <Stack>
          <TextInput
            label="Number"
            onKeyUp={(event) => {
              if (event.key === 'Escape') {
                event.preventDefault();
                close();
              }
            }}
            type="tel"
            pattern="[0-9\s]{13,19}"
            autoComplete="cc-number"
            maxLength={16}
            placeholder="xxxx xxxx xxxx xxxx"
            required
          />
          <Group>
            <MonthPickerInput
              w={100}
              required
              label="Expire"
              placeholder="MM/YY"
              valueFormat="MM/YY"
            />
            <TextInput w={100} label="CVV" placeholder="" required />
          </Group>
          <Group justify="right">
            <Text fs={'italic'} c="dimmed" size="xs">
              Press ESC to flip back
            </Text>
          </Group>
        </Stack>
      </Paper>
    );
  }

  return (
    <Container size={420} my={40}>
      <Flip h={200} w={400} flipped={flipped} mt={30}>
        <FrontCreditCard />
        <BackCreditCard />
      </Flip>
    </Container>
  );
}

Example: Signin form

Click on "Create account" to flip the card.

Welcome back!

Do not have an account yet?

function Demo() {

  const [flipped, setFlipped] = useState(false);

  function SignIn() {
    return (
        <Paper withBorder shadow="md" p={30} radius="md">
          <TextInput label="Email" placeholder="you@mantine.dev" required />
          <PasswordInput label="Password" placeholder="Your password" required mt="md" />
          <Group justify="space-between" mt="lg">
            <Checkbox label="Remember me" />
            <Anchor component="button" size="sm">
              Forgot password?
            </Anchor>
          </Group>
          <Button fullWidth mt="xl">
            Sign in
          </Button>
        </Paper>
    );
  }

  function SignUp() {
    return (
        <Paper withBorder shadow="md" p={30} radius="md">
          <TextInput label="Email" placeholder="you@mantine.dev" required />
          <PasswordInput label="Password" placeholder="Your password" required mt="md" />
          <PasswordInput label="Confirm Password" placeholder="Your password" required mt="md" />
          <Button fullWidth mt="xl">
            Sign Up
          </Button>
        </Paper>
    );
  }

  return (
    <Container size={420} my={40}>
      <Title ta="center" className={classes.title}>
      {!flipped ? "Welcome back!" : "Create an account"}
      </Title>
      
      {!flipped ?
        <Text c="dimmed" size="sm" ta="center" mt={5}>
          Do not have an account yet?{' '}
          <Anchor size="sm" component="button" onClick={()=>setFlipped(true)}>
            Create account
          </Anchor>
        </Text> :
        <Text c="dimmed" size="sm" ta="center" mt={5}>
          Do you already have an account?{' '}
          <Anchor size="sm" component="button" onClick={()=>setFlipped(false)}>
            Sign in
          </Anchor>
        </Text>
      }
      
      <Flip h={200} w={400} flipped={flipped} mt={30}>
        <SignIn />
        <SignUp />
      </Flip>
      
    </Container>
  );
}

Example: Profile Card

Jane Fingerlicker

Fullstack Engineer

34K

Followers

187

Following

1.9K

Posts

Contact Info

jane@fingerlicker.dev

Socials

React
TypeScript
Mantine
import { Flip } from '@gfazioli/mantine-flip';
import { Avatar, Badge, Button, Card, Center, Group, Text, ActionIcon, useMantineTheme } from '@mantine/core';
import { IconBrandGithub, IconBrandTwitter, IconBrandLinkedin, IconX } from '@tabler/icons-react';

function Demo() {
  const theme = useMantineTheme();

  return (
    <Center>
      <Flip w={300} h={400}>
        {/* Front Face */}
        <Card shadow="sm" padding="lg" radius="md" withBorder h="100%">
          <Card.Section>
            <div
              style={{
                height: 100,
                backgroundColor: theme.colors.blue[6],
              }}
            />
          </Card.Section>

          <Avatar
            src="https://raw.githubusercontent.com/mantinedev/mantine/master/.demo/avatars/avatar-1.png"
            size={80}
            radius={80}
            mx="auto"
            mt={-30}
            style={{ border: `2px solid ${theme.white}` }}
          />

          <Text ta="center" fz="lg" fw={500} mt="sm">
            Jane Fingerlicker
          </Text>
          <Text ta="center" c="dimmed" fz="sm">
            Fullstack Engineer
          </Text>

          <Group mt="md" justify="center" gap={30}>
            <div style={{ textAlign: 'center' }}>
              <Text ta="center" fw={500} fz="lg">
                34K
              </Text>
              <Text ta="center" fz="xs" c="dimmed">
                Followers
              </Text>
            </div>
            <div style={{ textAlign: 'center' }}>
              <Text ta="center" fw={500} fz="lg">
                187
              </Text>
              <Text ta="center" fz="xs" c="dimmed">
                Following
              </Text>
            </div>
            <div style={{ textAlign: 'center' }}>
              <Text ta="center" fw={500} fz="lg">
                1.9K
              </Text>
              <Text ta="center" fz="xs" c="dimmed">
                Posts
              </Text>
            </div>
          </Group>

          <Flip.Target>
            <Button fullWidth radius="md" mt="xl" size="md" variant="default">
              View Profile
            </Button>
          </Flip.Target>
        </Card>

        {/* Back Face */}
        <Card shadow="sm" padding="lg" radius="md" withBorder h="100%" style={{ display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
            <div>
                <Group justify="space-between" mb="xs">
                    <Text fw={500}>Contact Info</Text>
                    <Flip.Target>
                        <ActionIcon variant="subtle" color="gray">
                            <IconX size={16} />
                        </ActionIcon>
                    </Flip.Target>
                </Group>
                
                <Text size="sm" c="dimmed" mb="md">
                    jane@fingerlicker.dev
                </Text>

                <Text fw={500} mb="xs">Socials</Text>
                <Group gap="md">
                    <ActionIcon size="lg" variant="default" radius="xl">
                        <IconBrandGithub size={18} />
                    </ActionIcon>
                    <ActionIcon size="lg" variant="default" radius="xl">
                        <IconBrandTwitter size={18} />
                    </ActionIcon>
                    <ActionIcon size="lg" variant="default" radius="xl">
                        <IconBrandLinkedin size={18} />
                    </ActionIcon>
                </Group>
            </div>

            <Group gap="xs" mt="xl">
                <Badge color="pink" variant="light">React</Badge>
                <Badge color="blue" variant="light">TypeScript</Badge>
                <Badge color="cyan" variant="light">Mantine</Badge>
            </Group>
            
            <Flip.Target>
                <Button fullWidth mt="md">
                    Back to Card
                </Button>
            </Flip.Target>
        </Card>
      </Flip>
    </Center>
  );
}

Example: Grid Widget

Given that the Flip component behaves like a MacOS Widget, it is important to understand where and how to set the dimensions to avoid overflow issues.

Test 1

Test 1

Test 1 description

Test 1

Test 1 description

Additional information about Test 1.

Category
Test 2

Test 2

Test 2 description

Test 2

Test 2 description

Additional information about Test 2.

Category
Test 3

Test 3

Test 3 description

Test 3

Test 3 description

Additional information about Test 3.

Category
Test 4

Test 4

Test 4 description

Test 4

Test 4 description

Additional information about Test 4.

Category
Test 5

Test 5

Test 5 description

Test 5

Test 5 description

Additional information about Test 5.

Category
Test 6

Test 6

Test 6 description

Test 6

Test 6 description

Additional information about Test 6.

Category
Test 7

Test 7

Test 7 description

Test 7

Test 7 description

Additional information about Test 7.

Category
Test 8

Test 8

Test 8 description

Test 8

Test 8 description

Additional information about Test 8.

Category
Test 9

Test 9

Test 9 description

Test 9

Test 9 description

Additional information about Test 9.

Category
import { Flip } from '@gfazioli/mantine-flip';
import { Badge, Button, Card, Flex, Image, SimpleGrid, Stack, Text, Title } from '@mantine/core';

import testData from './data';

function Demo() {
  const WIDGET_HEIGHT = 360;
  const IMAGE_HEIGHT = 200;

  return (
    <SimpleGrid cols={3} verticalSpacing="lg">
      {testData.map((item) => (
        <Flip key={item.title} h={WIDGET_HEIGHT} directionFlipIn="positive">
          <Card h={WIDGET_HEIGHT} shadow="sm" radius="md" withBorder>
            <Card.Section>
              <Image h={IMAGE_HEIGHT} src={item.image} alt={item.title} />
            </Card.Section>

            <Stack mt={8}>
              <Title>{item.title}</Title>
              <Text>{item.description}</Text>
              <Flex justify="right">
                <Flip.Target>
                  <Button>More info</Button>
                </Flip.Target>
              </Flex>
            </Stack>
          </Card>
          <Card h={WIDGET_HEIGHT} shadow="sm" radius="md" withBorder>
            <Stack mt={8} justify="space-between" h="100%">
              <Stack>
                <Title>{item.title}</Title>
                <Text>{item.description}</Text>
                <Text>Additional information about {item.title}.</Text>
                <Badge color="green" variant="light">
                  Category
                </Badge>
              </Stack>

              <Flex justify="right">
                <Flip.Target>
                  <Button size="xs" rightSection={<IconArrowRight />} variant="outline">
                    Back
                  </Button>
                </Flip.Target>
              </Flex>
            </Stack>
          </Card>
        </Flip>
      ))}
    </SimpleGrid>
  );
}