11'use client'
22
3- import { css , keyframes } from '@emotion/react'
4- import styled from '@emotion/styled'
53import { CheckIcon } from '@ultraviolet/icons'
64import type { ReactNode } from 'react'
75import { useMemo } from 'react'
86import { Bullet } from '../Bullet'
97import { Stack } from '../Stack'
108import { Text } from '../Text'
119import { useStepper } from './StepperProvider'
12-
13- const LINE_HEIGHT_SIZES = {
14- medium : 4 ,
15- small : 2 ,
16- } as const
10+ import {
11+ animationStepperContainer ,
12+ stepBullet ,
13+ stepContainer ,
14+ stepperContainerRecipe ,
15+ stepperInteractive ,
16+ stepText ,
17+ } from './styles.css'
1718
1819type StepProps = {
1920 onClick ?: ( index : number ) => void
@@ -37,157 +38,6 @@ type StepProps = {
3738 className ?: string
3839 'data-testid' ?: string
3940}
40- const loadingAnimation = ( size : 'small' | 'medium' ) => keyframes `
41- from {
42- width : 0 ;
43- }
44- to {
45- width : calc (100% - ${ size === 'small' ? '24px' : '32px' } - 8px )};
46- `
47-
48- const loadingStyle = ( size : 'small' | 'medium' ) => css `
49- animation: ${ loadingAnimation ( size ) } 1s linear infinite;
50- `
51-
52- const StyledBullet = styled ( Bullet ) < {
53- size : 'small' | 'medium'
54- isActive : boolean
55- } > `
56- transition: box-shadow 300ms;
57- min-width: ${ ( { theme, size } ) =>
58- size === 'small' ? theme . space [ 3 ] : theme . space [ 4 ] } ;
59- ${ ( { theme, isActive } ) =>
60- isActive
61- ? `background-color: ${ theme . colors . primary . backgroundStrongHover } ;
62- box-shadow: ${ theme . shadows . focusPrimary } ;`
63- : null } ;
64- `
65-
66- const StyledText = styled ( Text ) `
67- transition: text-decoration-color 250ms ease-out;
68- text-decoration-thickness: 1px;
69- text-underline-offset: 2px;
70- text-decoration-color: transparent;
71- `
72-
73- const StyledStepContainer = styled ( Stack ) < {
74- 'data-disabled' : boolean
75- 'data-interactive' : boolean
76- 'data-hide-separator' : boolean
77- 'data-label-position' : 'bottom' | 'right'
78- size : 'small' | 'medium'
79- 'data-selected' : boolean
80- 'data-done' : boolean
81- 'data-animated' : boolean
82- } > `
83- display: flex;
84- white-space: nowrap;
85- transition: text-decoration 300ms;
86-
87- &[data-interactive="true"]:not([data-disabled="true"]) {
88- cursor: pointer;
89-
90- &[data-selected="true"]:hover {
91- & > ${ StyledBullet } {
92- box-shadow: ${ ( { theme } ) => theme . shadows . focusPrimary } ;
93- & > ${ StyledText } {
94- color: ${ ( { theme } ) => theme . colors . primary . textHover } ;
95- text-decoration: underline
96- ${ ( { theme } ) => theme . colors . primary . textHover } ;
97- text-decoration-thickness: 1px;
98- }
99- }
100- }
101-
102- &[data-done="true"]:hover {
103- & > ${ StyledBullet } {
104- box-shadow: ${ ( { theme } ) => theme . shadows . focusPrimary } ;
105- }
106- & > ${ StyledText } {
107- color: ${ ( { theme } ) => theme . colors . neutral . textHover } ;
108- text-decoration: underline
109- ${ ( { theme } ) => theme . colors . neutral . textHover } ;
110- text-decoration-thickness: 1px;
111- }
112- }
113- }
114-
115- &[data-disabled="true"] {
116- cursor: not-allowed;
117-
118- & > ${ StyledText } {
119- color: ${ ( { theme } ) => theme . colors . neutral . textDisabled } ;
120- }
121-
122- & > ${ StyledBullet } {
123- background-color: ${ ( { theme } ) =>
124- theme . colors . neutral . backgroundDisabled } ;
125- box-shadow: none;
126- color: ${ ( { theme } ) => theme . colors . neutral . textDisabled } ;
127- border-color: ${ ( { theme } ) => theme . colors . neutral . borderDisabled } ;
128- }
129- }
130-
131- &:not([data-hide-separator="true"]):not([data-label-position="right"]) {
132- flex-direction: column;
133- flex: 1;
134-
135- & > ${ StyledText } {
136- margin-top: ${ ( { theme } ) => theme . space [ 1 ] } ;
137- }
138-
139- &:not(:last-child){
140- &:after {
141- content: "";
142- position: relative;
143- align-self: baseline;
144- border-radius: ${ ( { theme } ) => theme . radii . default } ;
145- top: ${ ( { theme } ) => theme . space [ 2 ] } ;
146- width: calc(100% - ${ ( { theme, size } ) => ( size === 'small' ? theme . space [ 5 ] : theme . space [ 6 ] ) } );
147- left: calc(50% + 25px);
148- order: -1;
149- height: ${ ( { size } ) =>
150- size === 'small'
151- ? LINE_HEIGHT_SIZES . small
152- : LINE_HEIGHT_SIZES . medium } px;
153- }
154-
155- &[data-done="true"]:after {
156- background-color: ${ ( { theme } ) =>
157- theme . colors . primary . backgroundStrong } ;
158- }
159- &[data-selected="true"][data-animated="true"]:after {
160- ${ ( { size } ) => loadingStyle ( size ) }
161- background-color: ${ ( { theme } ) =>
162- theme . colors . primary . backgroundStrong } ;
163-
164- }
165- }
166- &:not(:last-child){
167- &::before {
168- content: "";
169- position: relative;
170- align-self: baseline;
171- border-radius: ${ ( { theme } ) => theme . radii . default } ;
172- background-color: ${ ( { theme } ) =>
173- theme . colors . neutral . backgroundStrong } ;
174- top: 20px;
175- width: calc(
176- 100% - ${ ( { theme, size } ) => ( size === 'small' ? theme . space [ 5 ] : theme . space [ 6 ] ) } );
177- left: calc(50% + 25px);
178- order: -1;
179- height: ${ ( { size } ) =>
180- size === 'small'
181- ? LINE_HEIGHT_SIZES . small
182- : LINE_HEIGHT_SIZES . medium } px;
183- }
184- }
185-
186- &:last-child {
187- margin-top: ${ ( { theme } ) => theme . space [ 1 ] } ;
188- }
189- }
190- `
19141
19242export const Step = ( {
19343 index = 0 ,
@@ -198,74 +48,88 @@ export const Step = ({
19848 className,
19949 'data-testid' : dataTestId ,
20050} : StepProps ) => {
201- const currentState = useStepper ( )
202- const isActive = index === currentState . step
203- const isDone = index < currentState . step
51+ const {
52+ separator,
53+ labelPosition,
54+ animated,
55+ size,
56+ interactive,
57+ step,
58+ setStep,
59+ } = useStepper ( )
60+ const isActive = index === step
61+ const isDone = index < step
62+ const separatorBottom = separator && labelPosition === 'bottom'
63+ const interactiveDone = isDone && interactive
20464
20565 const textVariant = useMemo ( ( ) => {
206- if ( currentState . size === 'medium' ) {
66+ if ( size === 'medium' ) {
20767 return isActive ? 'bodyStrong' : 'body'
20868 }
20969
21070 return isActive ? 'bodySmallStrong' : 'bodySmall'
211- } , [ currentState . size , isActive ] )
71+ } , [ size , isActive ] )
21272
21373 return (
214- < StyledStepContainer
74+ < Stack
21575 alignItems = "center"
216- className = { className ?? 'step' }
217- data-animated = { currentState . animated }
218- data-disabled = { disabled }
219- data-done = { isDone }
220- data-hide-separator = { ! currentState . separator }
221- data-interactive = { currentState . interactive && isDone }
222- data-label-position = { currentState . labelPosition }
223- data-selected = { isActive }
76+ className = { `${ className ? `${ className } ` : 'step ' } ${ stepContainer } ${ separatorBottom ? stepperContainerRecipe ( { animated, disabled, done : isDone , labelPosition, separator, size } ) : '' } ${ isActive && separator && animated ? animationStepperContainer [ size ] : '' } ${ interactiveDone && ! disabled ? stepperInteractive [ isActive ? 'active' : 'inactive' ] : '' } ` }
22477 data-testid = { dataTestId ?? `stepper-step-${ index } ` }
225- direction = { currentState . labelPosition === 'right' ? 'row' : 'column' }
226- gap = { currentState . labelPosition === 'right' ? 1 : 0 }
78+ direction = { labelPosition === 'right' ? 'row' : 'column' }
79+ gap = { labelPosition === 'right' ? 1 : 0 }
22780 justifyContent = "flex-start"
22881 onClick = { ( ) => {
229- if ( currentState . interactive && ! disabled ) {
230- if ( index < currentState . step ) {
231- currentState . setStep ( index )
82+ if ( interactive && ! disabled ) {
83+ if ( index < step ) {
84+ setStep ( index )
23285 }
23386 onClick ?.( index )
23487 }
23588 } }
236- size = { currentState . size }
23789 >
23890 { isDone && ! disabled ? (
239- < StyledBullet
240- isActive = { isActive }
91+ < Bullet
92+ className = { stepBullet ( {
93+ disabled,
94+ isActive,
95+ size,
96+ } ) }
24197 prominence = "strong"
24298 sentiment = "primary"
243- size = { currentState . size }
99+ size = { size }
244100 >
245101 < CheckIcon />
246- </ StyledBullet >
102+ </ Bullet >
247103 ) : (
248- < StyledBullet
249- isActive = { isActive }
104+ < Bullet
105+ className = { stepBullet ( {
106+ disabled,
107+ isActive,
108+ size,
109+ } ) }
250110 prominence = "strong"
251111 sentiment = { isDone || isActive ? 'primary' : 'neutral' }
252- size = { currentState . size }
112+ size = { size }
253113 >
254114 { ( index + 1 ) . toString ( ) }
255- </ StyledBullet >
115+ </ Bullet >
256116 ) }
257117 { title ? (
258- < StyledText
118+ < Text
259119 as = "span"
120+ className = { stepText ( {
121+ addMarginTop : separator && labelPosition !== 'right' ,
122+ disabled,
123+ } ) }
260124 prominence = { isDone || isActive ? 'default' : 'weak' }
261125 sentiment = { isActive ? 'primary' : 'neutral' }
262126 variant = { textVariant }
263127 whiteSpace = "normal"
264128 >
265129 { title }
266- </ StyledText >
130+ </ Text >
267131 ) : null }
268132 { children ?? null }
269- </ StyledStepContainer >
133+ </ Stack >
270134 )
271135}
0 commit comments