Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/mantine.dev/src/mdx/data/mdx-core-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ export const MDX_CORE_DATA: Record<string, Frontmatter> = {
title: 'Stepper',
package: '@mantine/core',
slug: '/core/stepper',
props: ['Stepper', 'StepperStep'],
props: ['Stepper', 'StepperStep', 'StepperRoot', 'StepperSteps', 'StepperContent'],
styles: ['Stepper'],
componentPrefix: 'Stepper',
description: 'Display content divided into a steps sequence',
Expand Down
23 changes: 23 additions & 0 deletions apps/mantine.dev/src/pages/core/stepper.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ export default Layout(MDX_DATA.Stepper);

<Demo data={StepperDemos.usage} />

## Compound components

`Stepper` component children must follow a strict structure: `Stepper.Step` and `Stepper.Completed`
components must be direct children of the `Stepper` component. If you need full control over the layout,
use compound components instead:

- `Stepper.Root` accepts the same props as `Stepper` and renders its children as is
- `Stepper.Steps` renders the steps list, `Stepper.Step` components must be its direct children
- `Stepper.Content` renders its children when the step with the associated 0-based `step` index is active
- `Stepper.Completed` renders its children when `active` is greater than or equal to its `step` prop; `step` is required with `Stepper.Root` and set automatically by `Stepper`

<Demo data={StepperDemos.compound} />

`useStepperContext` hook provides the current step index and the total number of steps. The `stepsCount`
value is detected automatically from `Stepper.Steps` children in client-side applications. Set the
`stepsCount` prop on `Stepper.Root` only when server-side rendering the step count.

Unlike the `Stepper` component, compound components do not restrict the layout structure –
you can wrap them with any elements. For example, you can pin step indicators to the top of
the screen while the content below is scrolled independently:

<Demo data={StepperDemos.compoundSticky} />

## Allow step select

To disable step selection, set the `allowStepSelect` prop on the `Stepper.Step` component.
Expand Down
100 changes: 100 additions & 0 deletions packages/@docs/demos/src/demos/core/Stepper/Stepper.demo.compound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useState } from 'react';
import { Button, Group, Stepper, Text, useStepperContext } from '@mantine/core';
import { MantineDemo } from '@mantinex/demo';

const code = `
import { useState } from 'react';
import { Stepper, Button, Group, Text, useStepperContext } from '@mantine/core';

function StepCount() {
const { active, stepsCount } = useStepperContext();
return <Text>Step {Math.min(active + 1, stepsCount)} of {stepsCount}</Text>;
}

function Demo() {
const [active, setActive] = useState(0);
const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));

return (
<>
<Stepper.Root active={active} onStepClick={setActive}>
<StepCount />
<Stepper.Steps>
<Stepper.Step label="First step" description="Create an account" />
<Stepper.Step label="Second step" description="Verify email" />
<Stepper.Step label="Final step" description="Get full access" />
</Stepper.Steps>

<Stepper.Content step={0}>
Step 1 content: Create an account
</Stepper.Content>
<Stepper.Content step={1}>
Step 2 content: Verify email
</Stepper.Content>
<Stepper.Content step={2}>
Step 3 content: Get full access
</Stepper.Content>
<Stepper.Completed step={3}>
Completed, click back button to get to previous step
</Stepper.Completed>
</Stepper.Root>

<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>
Back
</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
</>
);
}
`;

function StepCount() {
const { active, stepsCount } = useStepperContext();
return (
<Text>
Step {Math.min(active + 1, stepsCount)} of {stepsCount}
</Text>
);
}

function Demo() {
const [active, setActive] = useState(0);
const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));

return (
<>
<Stepper.Root active={active} onStepClick={setActive}>
<StepCount />
<Stepper.Steps>
<Stepper.Step label="First step" description="Create an account" />
<Stepper.Step label="Second step" description="Verify email" />
<Stepper.Step label="Final step" description="Get full access" />
</Stepper.Steps>

<Stepper.Content step={0}>Step 1 content: Create an account</Stepper.Content>
<Stepper.Content step={1}>Step 2 content: Verify email</Stepper.Content>
<Stepper.Content step={2}>Step 3 content: Get full access</Stepper.Content>
<Stepper.Completed step={3}>
Completed, click back button to get to previous step
</Stepper.Completed>
</Stepper.Root>

<Group justify="center" mt="xl">
<Button variant="default" onClick={prevStep}>
Back
</Button>
<Button onClick={nextStep}>Next step</Button>
</Group>
</>
);
}

export const compound: MantineDemo = {
type: 'code',
component: Demo,
code,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { useState } from 'react';
import { Box, ScrollArea, Stack, Stepper, Text } from '@mantine/core';
import { MantineDemo } from '@mantinex/demo';

const code = `
import { useState } from 'react';
import { Box, ScrollArea, Stack, Stepper, Text } from '@mantine/core';

function Demo() {
const [active, setActive] = useState(0);

return (
<Stepper.Root active={active} onStepClick={setActive}>
<Box pos="sticky" top={0}>
<Stepper.Steps>
<Stepper.Step label="Step 1" />
<Stepper.Step label="Step 2" />
<Stepper.Step label="Step 3" />
</Stepper.Steps>
</Box>

<ScrollArea h={100}>
<Stepper.Content step={0}>
<Stack p="md">
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</Text>
<Text>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</Text>
<Text>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
</Text>
<Text>
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>
</Stack>
</Stepper.Content>
<Stepper.Content step={1}>
<Stack p="md">
<Text>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.
</Text>
<Text>
Totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
</Text>
<Text>
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores.
</Text>
<Text>
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit.
</Text>
</Stack>
</Stepper.Content>
<Stepper.Content step={2}>
<Stack p="md">
<Text>
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti.
</Text>
<Text>
Et harum quidem rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae itaque earum.
</Text>
<Text>
Rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
</Text>
<Text>
Sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
</Text>
</Stack>
</Stepper.Content>
</ScrollArea>
</Stepper.Root>
);
}
`;

function Demo() {
const [active, setActive] = useState(0);

return (
<Stepper.Root active={active} onStepClick={setActive}>
<Box pos="sticky" top={0}>
<Stepper.Steps>
<Stepper.Step label="Step 1" />
<Stepper.Step label="Step 2" />
<Stepper.Step label="Step 3" />
</Stepper.Steps>
</Box>

<ScrollArea h={100}>
<Stepper.Content step={0}>
<Stack p="md">
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
</Text>
<Text>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
ea commodo consequat.
</Text>
<Text>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
nulla pariatur.
</Text>
<Text>
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
</Text>
</Stack>
</Stepper.Content>
<Stepper.Content step={1}>
<Stack p="md">
<Text>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
laudantium.
</Text>
<Text>
Totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto
beatae vitae dicta sunt explicabo.
</Text>
<Text>
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia
consequuntur magni dolores.
</Text>
<Text>
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci
velit.
</Text>
</Stack>
</Stepper.Content>
<Stepper.Content step={2}>
<Stack p="md">
<Text>
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium
voluptatum deleniti atque corrupti.
</Text>
<Text>
Et harum quidem rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint
et molestiae non recusandae itaque earum.
</Text>
<Text>
Rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias
consequatur aut perferendis doloribus asperiores repellat.
</Text>
<Text>
Sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam
quaerat voluptatem.
</Text>
</Stack>
</Stepper.Content>
</ScrollArea>
</Stepper.Root>
);
}

export const compoundSticky: MantineDemo = {
type: 'code',
component: Demo,
code,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ export const Demo_usage = {
render: renderDemo(demos.usage),
};

export const Demo_compound = {
name: '⭐ Demo: compound',
render: renderDemo(demos.compound),
};

export const Demo_compoundSticky = {
name: '⭐ Demo: compoundSticky',
render: renderDemo(demos.compoundSticky),
};

export const Demo_configurator = {
name: '⭐ Demo: configurator',
render: renderDemo(demos.configurator),
Expand Down
2 changes: 2 additions & 0 deletions packages/@docs/demos/src/demos/core/Stepper/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { usage } from './Stepper.demo.usage';
export { compound } from './Stepper.demo.compound';
export { compoundSticky } from './Stepper.demo.compoundSticky';
export { configurator } from './Stepper.demo.configurator';
export { icons } from './Stepper.demo.icons';
export { iconSizeConfigurator } from './Stepper.demo.iconSizeConfigurator';
Expand Down
24 changes: 21 additions & 3 deletions packages/@mantine/core/src/components/Stepper/Stepper.context.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import { createSafeContext, GetStylesApi } from '../../core';
import type { StepperFactory } from './Stepper';
import { use } from 'react';
import { createSafeContext, GetStylesApi, MantineColor } from '../../core';
import type { StepFragmentComponent, StepperRootFactory } from './StepperRoot/StepperRoot';

export interface StepperContextValue {
getStyles: GetStylesApi<StepperFactory>;
getStyles: GetStylesApi<StepperRootFactory>;
orientation: 'horizontal' | 'vertical' | undefined;
iconPosition: 'left' | 'right' | undefined;
active: number;
stepsCount: number;
/** @internal */
setStepsCount: (count: number) => void;
onStepClick?: (stepIndex: number) => void;
allowNextStepsSelect: boolean | undefined;
icon?: React.ReactNode | StepFragmentComponent;
completedIcon?: React.ReactNode | StepFragmentComponent;
progressIcon?: React.ReactNode | StepFragmentComponent;
color: MantineColor | undefined;
iconSize: number | string | undefined;
wrap: boolean | undefined;
keepMounted: boolean | undefined;
}

export const [StepperProvider, useStepperContext] = createSafeContext<StepperContextValue>(
'Stepper component was not found in tree'
);

export function useOptionalStepperContext() {
return use(StepperProvider);
}
Loading