Skip to content

Commit 52ddbc6

Browse files
authored
Flow props standardization (#398)
* Update Form to YAML props standard * Exclude video props from YAML passthrough * Banner no longer needs the as={null} pattern for YAML props * Convert Announcement to use merged props, generalize default esc handler for Dialog in Flow, standardize dismissible prop handling across components * Use an actual Announcement flow type in Storybook, remove Dialog from Form story.
1 parent 67be7eb commit 52ddbc6

File tree

9 files changed

+128
-127
lines changed

9 files changed

+128
-127
lines changed

apps/smithy/src/stories/Announcement/Announcement.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default {
88
export const Default = {
99
args: {
1010
dismissible: true,
11-
flowId: "flow_8Ybz7lMK",
11+
flowId: "flow_vLivpwoH",
1212
modal: true,
1313
onDismiss: () => console.log("Dismissed"),
1414
},

apps/smithy/src/stories/Form/Form.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export const Default = {
3232
fieldTypes: {
3333
customTest: CustomStep,
3434
},
35-
as: Dialog,
35+
// as: Dialog,
3636
variables: {
3737
testVar: "hello world",
3838
},

packages/react/src/components/Announcement/index.tsx

Lines changed: 58 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -19,114 +19,83 @@ export interface AnnouncementProps extends FlowPropsWithoutChildren, DialogProps
1919
defaultOpen?: boolean
2020
}
2121

22-
const flowPropNames = [
23-
'dismissible',
24-
'flowId',
25-
'forceMount',
26-
'onComplete',
27-
'onDismiss',
28-
'onPrimary',
29-
'onSecondary',
30-
'variables',
31-
]
32-
3322
export function Announcement({ flowId, part, ...props }: AnnouncementProps) {
34-
const flowProps = Object.fromEntries(
35-
Object.entries(props).filter(([k]) => flowPropNames.some((name) => k === name))
36-
)
37-
const dialogProps = Object.fromEntries(
38-
Object.entries(props).filter(([k]) => flowPropNames.indexOf(k) === -1)
39-
)
40-
4123
return (
42-
<Flow as={null} flowId={flowId} {...flowProps}>
24+
<Flow
25+
as={Dialog}
26+
display="flex"
27+
flexDirection="column"
28+
flowId={flowId}
29+
gap={5}
30+
part={['announcement', part]}
31+
textAlign="center"
32+
{...props}
33+
>
4334
{({
4435
flow,
4536
handleDismiss,
4637
handlePrimary,
4738
handleSecondary,
48-
parentProps: { containerProps, dismissible },
39+
parentProps: { dismissible },
4940
step,
5041
}) => {
51-
const stepProps = step.props ?? {}
52-
5342
const primaryButtonTitle = step.primaryButton?.title ?? step.primaryButtonTitle
5443
const secondaryButtonTitle = step.secondaryButton?.title ?? step.secondaryButtonTitle
5544

5645
const disabled = step.$state.blocked
5746

58-
const { videoProps } = getVideoProps(stepProps)
47+
const { videoProps } = getVideoProps(step.props ?? {})
5948

6049
return (
61-
<Dialog
62-
part={['announcement', part]}
63-
textAlign="center"
64-
{...containerProps}
65-
{...dialogProps}
66-
onEscapeKeyDown={(e) => {
67-
if (props.dismissible === false) {
68-
e.preventDefault()
69-
return
70-
}
71-
if (typeof props.onEscapeKeyDown === 'function') {
72-
props.onEscapeKeyDown(e)
73-
}
74-
75-
if (!e.defaultPrevented) {
76-
handleDismiss(e)
77-
}
78-
}}
79-
>
80-
<Flex.Column gap={5} part="announcement-step" {...stepProps}>
81-
{dismissible && <Dialog.Dismiss onClick={handleDismiss} />}
82-
<Flex.Column gap={1} part="announcement-header">
83-
<Dialog.Title>{step.title}</Dialog.Title>
84-
<Dialog.Subtitle>{step.subtitle}</Dialog.Subtitle>
85-
</Flex.Column>
50+
<>
51+
{dismissible && <Dialog.Dismiss onClick={handleDismiss} />}
52+
<Flex.Column gap={1} part="announcement-header">
53+
<Dialog.Title>{step.title}</Dialog.Title>
54+
<Dialog.Subtitle>{step.subtitle}</Dialog.Subtitle>
55+
</Flex.Column>
8656

87-
<Dialog.Media
88-
aspectRatio="1.5"
89-
objectFit="cover"
90-
overflowClipMargin="unset"
91-
src={step.videoUri ?? step.imageUri}
92-
transform="translate3d(0, 0, 1px)"
93-
type={step.videoUri ? 'video' : 'image'}
94-
width="100%"
95-
{...videoProps}
96-
/>
57+
<Dialog.Media
58+
aspectRatio="1.5"
59+
objectFit="cover"
60+
overflowClipMargin="unset"
61+
src={step.videoUri ?? step.imageUri}
62+
transform="translate3d(0, 0, 1px)"
63+
type={step.videoUri ? 'video' : 'image'}
64+
width="100%"
65+
{...videoProps}
66+
/>
9767

98-
<Dialog.ProgressDots
99-
current={flow.getCurrentStepIndex()}
100-
total={flow.getNumberOfAvailableSteps()}
101-
/>
68+
<Dialog.ProgressDots
69+
current={flow.getCurrentStepIndex()}
70+
total={flow.getNumberOfAvailableSteps()}
71+
/>
10272

103-
<Flex.Row
104-
css={{
105-
'& > button': {
106-
flexBasis: '50%',
107-
flexGrow: 1,
108-
},
109-
}}
110-
gap={3}
111-
part="announcement-footer"
112-
>
113-
{secondaryButtonTitle && (
114-
<Dialog.Secondary
115-
disabled={disabled}
116-
onClick={handleSecondary}
117-
title={secondaryButtonTitle}
118-
/>
119-
)}
120-
{primaryButtonTitle && (
121-
<Dialog.Primary
122-
disabled={disabled}
123-
onClick={handlePrimary}
124-
title={primaryButtonTitle}
125-
/>
126-
)}
127-
</Flex.Row>
128-
</Flex.Column>
129-
</Dialog>
73+
<Flex.Row
74+
css={{
75+
'& > button': {
76+
flexBasis: '50%',
77+
flexGrow: 1,
78+
},
79+
}}
80+
gap={3}
81+
part="announcement-footer"
82+
>
83+
{secondaryButtonTitle && (
84+
<Dialog.Secondary
85+
disabled={disabled}
86+
onClick={handleSecondary}
87+
title={secondaryButtonTitle}
88+
/>
89+
)}
90+
{primaryButtonTitle && (
91+
<Dialog.Primary
92+
disabled={disabled}
93+
onClick={handlePrimary}
94+
title={primaryButtonTitle}
95+
/>
96+
)}
97+
</Flex.Row>
98+
</>
13099
)
131100
}}
132101
</Flow>

packages/react/src/components/Banner/index.tsx

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,30 @@ import { Box } from '@/components/Box'
55

66
export interface BannerProps extends FlowPropsWithoutChildren {}
77

8-
export function Banner({ dismissible, flowId, part, ...props }: BannerProps) {
8+
export function Banner({ flowId, part, ...props }: BannerProps) {
99
return (
10-
<Flow as={null} flowId={flowId} {...props}>
11-
{({
12-
handleDismiss,
13-
handlePrimary,
14-
handleSecondary,
15-
parentProps: { containerProps },
16-
step,
17-
}) => {
18-
const stepProps = step.props ?? {}
19-
10+
<Flow
11+
alignItems="center"
12+
aria-label="Banner"
13+
as={Card}
14+
borderWidth="md"
15+
display="flex"
16+
flexDirection="row"
17+
flowId={flowId}
18+
gap={3}
19+
justifyContent="flex-start"
20+
part={['banner', part]}
21+
role="complementary"
22+
{...props}
23+
>
24+
{({ handleDismiss, handlePrimary, handleSecondary, parentProps: { dismissible }, step }) => {
2025
const primaryButtonTitle = step.primaryButton?.title ?? step.primaryButtonTitle
2126
const secondaryButtonTitle = step.secondaryButton?.title ?? step.secondaryButtonTitle
2227

2328
const disabled = step.$state.blocked
2429

2530
return (
26-
<Card
27-
alignItems="center"
28-
aria-label="Banner"
29-
borderWidth="md"
30-
display="flex"
31-
flexDirection="row"
32-
gap={3}
33-
justifyContent="flex-start"
34-
part={['banner', part]}
35-
role="complementary"
36-
{...containerProps}
37-
{...stepProps}
38-
>
31+
<>
3932
{step.imageUri && (
4033
<Box
4134
as="img"
@@ -57,7 +50,7 @@ export function Banner({ dismissible, flowId, part, ...props }: BannerProps) {
5750
/>
5851
<Card.Primary disabled={disabled} title={primaryButtonTitle} onClick={handlePrimary} />
5952
{dismissible && <Card.Dismiss onClick={handleDismiss} />}
60-
</Card>
53+
</>
6154
)
6255
}}
6356
</Flow>

packages/react/src/components/Card/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ Card.Footer = ({ children, part, ...props }: BoxProps) => {
8686
}
8787

8888
Card.Header = ({ dismissible, handleDismiss, part, subtitle, title, ...props }) => {
89+
if (
90+
!dismissible &&
91+
(title == null || title?.length === 0) &&
92+
(subtitle == null || subtitle?.length === 0)
93+
) {
94+
return null
95+
}
96+
8997
return (
9098
<Flex.Row
9199
alignItems="flex-start"

packages/react/src/components/Checklist/Collapsible.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ function StepWrapper({ flow, step, ...props }: FlowChildrenProps) {
141141
}
142142

143143
export function Collapsible({
144-
dismissible,
145144
flowId,
146145
onPrimary,
147146
onSecondary,
@@ -179,6 +178,9 @@ export function Collapsible({
179178
const currentSteps = flow.getNumberOfCompletedSteps()
180179
const availableSteps = flow.getNumberOfAvailableSteps()
181180

181+
// Note: Ignore merged props from step here, Checklist steps don't control flow dismissibility
182+
const dismissible = props.dismissible || !!flow?.props?.dismissible
183+
182184
return (
183185
<>
184186
<Flex.Column gap={2}>

packages/react/src/components/Flow/index.tsx

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useStepHandlers } from '@/hooks/useStepHandlers'
99
import { useCheckForModalCollision } from '@/hooks/useModal'
1010

1111
import type { FlowProps } from '@/components/Flow/FlowProps'
12+
import { getVideoProps } from '@/components/Media/videoProps'
1213
// import { FrigadeContext } from '@/components/Provider'
1314

1415
export type {
@@ -17,6 +18,10 @@ export type {
1718
FlowPropsWithoutChildren,
1819
} from '@/components/Flow/FlowProps'
1920

21+
function isDialog(component) {
22+
return typeof component === 'function' && component.displayName === 'Dialog'
23+
}
24+
2025
export function Flow({
2126
as,
2227
children,
@@ -35,19 +40,25 @@ export function Flow({
3540
variables,
3641
})
3742

43+
const step = flow?.getCurrentStep()
44+
45+
const initialStepProps = step?.props ?? {}
46+
47+
// Discard video props when merging step props onto top-level container
48+
const { otherProps: stepProps } = getVideoProps(initialStepProps)
49+
3850
const {
39-
dismissible = false,
51+
dismissible = isDialog(as) ? true : false,
4052
forceMount = false,
4153
...mergedProps
4254
} = {
4355
...props,
4456
...(flow?.props ?? {}),
57+
...(flow?.rawData?.flowType === FlowType.CHECKLIST ? {} : stepProps),
4558
}
4659

4760
// const { hasInitialized, registerComponent, unregisterComponent } = useContext(FrigadeContext)
4861

49-
const step = flow?.getCurrentStep()
50-
5162
const { handleDismiss } = useFlowHandlers(flow, {
5263
onComplete,
5364
onDismiss,
@@ -60,11 +71,26 @@ export function Flow({
6071

6172
const isModal =
6273
mergedProps?.modal ||
63-
(typeof as === 'function' && as?.displayName === 'Dialog') ||
74+
isDialog(as) ||
6475
[FlowType.ANNOUNCEMENT, FlowType.TOUR].includes(flow?.rawData?.flowType)
6576

6677
const { hasModalCollision } = useCheckForModalCollision(flow, isModal)
6778

79+
function handleEscapeKeyDown(e) {
80+
if (dismissible === false) {
81+
e.preventDefault()
82+
return
83+
}
84+
85+
if (typeof props.onEscapeKeyDown === 'function') {
86+
props.onEscapeKeyDown(e)
87+
}
88+
89+
if (!e.defaultPrevented) {
90+
handleDismiss(e)
91+
}
92+
}
93+
6894
// useEffect(() => {
6995
// return () => {
7096
// unregisterComponent(flowId)
@@ -103,11 +129,15 @@ export function Flow({
103129

104130
const ContainerElement = as === null ? Fragment : as ?? Box
105131

106-
const containerProps = {
132+
const containerProps: Record<string, unknown> = {
107133
...mergedProps,
108134
'data-flow-id': flow.id,
109135
}
110136

137+
if (isDialog(as)) {
138+
containerProps.onEscapeKeyDown = handleEscapeKeyDown
139+
}
140+
111141
return (
112142
<ContainerElement {...(as === null ? {} : containerProps)}>
113143
{children({

0 commit comments

Comments
 (0)