Skip to content

Commit 203eeb4

Browse files
authored
feat(components): add HOC wrapper for own styleProps (#18951)
* feat(components): add HOC wrapper for own styleProps
1 parent a4079e3 commit 203eeb4

File tree

13 files changed

+149
-49
lines changed

13 files changed

+149
-49
lines changed

app/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"@thi.ng/paths": "1.6.5",
3232
"@types/uuid": "^3.4.7",
3333
"classnames": "2.2.5",
34-
"clsx": "^2.1.1",
3534
"connected-react-router": "6.9.3",
3635
"core-js": "3.2.1",
3736
"date-fns": "2.25.0",

app/src/organisms/Desktop/ChooseRobotSlideout/AvailableRobotOption.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,13 @@ export function AvailableRobotOption(
151151
>
152152
{robotName}
153153
<Icon
154-
aria-label={iconName}
155-
marginBottom={`-${SPACING.spacing4}`}
156-
marginLeft={SPACING.spacing8}
154+
aria-label={iconName ?? 'wifi-icon'}
157155
name={iconName ?? 'wifi'}
158156
size={SIZE_1}
157+
style={{
158+
marginLeft: SPACING.spacing8,
159+
marginBottom: `-${SPACING.spacing4}`,
160+
}}
159161
/>
160162
</LegacyStyledText>
161163
</Box>

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ export function ProtocolDropTipModal({
109109
name: 'information',
110110
color: COLORS.red50,
111111
size: SPACING.spacing20,
112-
marginRight: SPACING.spacing8,
112+
style: {
113+
marginRight: SPACING.spacing8,
114+
},
113115
}
114116
}
115117

app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export function ErrorDetailsModalDesktop(
136136
name: 'information',
137137
color: COLORS.grey60,
138138
size: SPACING.spacing20,
139-
marginRight: SPACING.spacing8,
139+
style: { marginRight: SPACING.spacing8 },
140140
}
141141
}
142142

components/src/atoms/CheckboxField/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export function CheckboxField(props: CheckboxFieldProps): JSX.Element {
6565
<Icon
6666
css={value ?? false ? INNER_STYLE_VALUE : INNER_STYLE_NO_VALUE}
6767
name={value ?? false ? 'ot-checkbox' : 'checkbox-blank-outline'}
68-
width="100%"
68+
width="1.25rem"
6969
data-testid="CheckboxField_icon"
7070
/>
7171
)}

components/src/hocs/withStyleProps.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createElement } from 'react'
2+
3+
import { styleProps } from '../primitives'
4+
5+
import type { ComponentProps, ComponentType } from 'react'
6+
import type { StyleProps } from '../primitives'
7+
8+
/**
9+
* A Higher-Order Component (HOC) that enhances a component by enabling it to
10+
* accept `StyleProps`.
11+
*
12+
* This function wraps a component, processes any provided `StyleProps` into a
13+
* React style object, and merges it with the component's existing `style` prop.
14+
* Styles from the passed `style` prop will override any generated styles from `StyleProps`
15+
* in case of conflict.
16+
*
17+
* @template T - The type of the React component being wrapped.
18+
* @param Component The React component to enhance with `StyleProps`.
19+
* @returns A new component that accepts the original component's props plus `StyleProps`.
20+
*
21+
* @example
22+
* ```tsx
23+
* <Icon
24+
* name="wifi"
25+
* size="1.25rem"
26+
* color={COLORS.green50}
27+
* style={{ marginLeft: SPACING.spacing8 }}
28+
* />
29+
* ```
30+
*/
31+
32+
export function withStyleProps<T extends ComponentType<any>>(
33+
Component: T
34+
): T & ComponentType<ComponentProps<T> & StyleProps> {
35+
const ComponentWithStyleProps = ({
36+
style,
37+
...props
38+
}: ComponentProps<T> & StyleProps): JSX.Element => {
39+
const stylePropsStyles = styleProps(props)
40+
const combinedStyles = { ...stylePropsStyles, ...style }
41+
42+
return createElement(Component, {
43+
...props,
44+
style: combinedStyles,
45+
})
46+
}
47+
48+
ComponentWithStyleProps.displayName = `withStyleProps(${
49+
Component.displayName ?? Component.name
50+
})`
51+
52+
return ComponentWithStyleProps as any
53+
}

components/src/icons/Icon.tsx

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
1-
import cx from 'classnames'
2-
import { css, keyframes } from 'styled-components'
1+
import clsx from 'clsx'
32

4-
import { Svg } from '../primitives'
3+
import { withStyleProps } from '../hocs/withStyleProps'
54
import { ICON_DATA_BY_NAME } from './icon-data'
5+
import styles from './icon.module.css'
66

7-
import type { ReactNode } from 'react'
8-
import type { SvgProps } from '../primitives'
7+
import type { ReactNode, SVGProps } from 'react'
98

109
export type IconName = keyof typeof ICON_DATA_BY_NAME
1110

12-
export interface IconProps extends SvgProps {
11+
export interface IconProps extends SVGProps<SVGSVGElement> {
1312
/** name constant of the icon to display */
1413
name: IconName
15-
/** classes to apply */
16-
className?: string
1714
/** spin the icon with a CSS animation */
1815
spin?: boolean
16+
/** override default size */
17+
size?: string | number
1918
/** x attribute as a number or string (for nesting inside another SVG) */
2019
x?: number | string
2120
/** y attribute as a number or string (for nesting inside another SVG) */
@@ -28,22 +27,8 @@ export interface IconProps extends SvgProps {
2827
style?: Record<string, string | number>
2928
/** optional children */
3029
children?: ReactNode
31-
id?: string
3230
}
3331

34-
const spinAnimation = keyframes`
35-
100% {
36-
transform: rotate(360deg);
37-
}
38-
`
39-
40-
const spinStyle = css`
41-
&.spin {
42-
animation: ${spinAnimation} 0.8s steps(8) infinite;
43-
transform-origin: center;
44-
}
45-
`
46-
4732
/**
4833
* Inline SVG icon component
4934
*
@@ -52,28 +37,52 @@ const spinStyle = css`
5237
* import type { IconName } from '@opentrons/components'
5338
* ```
5439
*/
55-
export function Icon(props: IconProps): JSX.Element | null {
56-
const { name, children, className, spin, id, ...svgProps } = props
40+
function IconComponent(props: IconProps): JSX.Element | null {
41+
const {
42+
name,
43+
className,
44+
spin,
45+
size,
46+
height: rawHeight,
47+
width: rawWidth,
48+
color,
49+
transform,
50+
opacity,
51+
...svgProps
52+
} = props
5753

54+
const height = size ?? rawHeight
55+
const width = size ?? rawWidth
5856
if (!(name in ICON_DATA_BY_NAME)) {
5957
console.error(`"${name}" is not a valid Icon name`)
6058
return null
6159
}
6260

6361
const { viewBox, path } = ICON_DATA_BY_NAME[name]
6462

63+
const style = Object.fromEntries(
64+
Object.entries({
65+
color,
66+
height,
67+
width,
68+
transform,
69+
// filter undefined props
70+
}).filter(([_, value]) => value != null)
71+
)
72+
6573
return (
66-
<Svg
74+
<svg
6775
aria-hidden="true"
6876
fill="currentColor"
6977
viewBox={viewBox}
70-
className={cx(className, { spin })}
71-
css={spinStyle}
78+
className={clsx(className, { [styles.spin]: spin })}
79+
style={{ ...style }}
7280
{...svgProps}
73-
id={id}
7481
>
7582
<path aria-roledescription={name} fillRule="evenodd" d={path} />
7683
{props.children}
77-
</Svg>
84+
</svg>
7885
)
7986
}
87+
88+
export const Icon = withStyleProps(IconComponent)

components/src/icons/icon.module.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@keyframes spin {
2+
100% {
3+
transform: rotate(360deg);
4+
}
5+
}
6+
7+
.spin {
8+
animation: spin 0.8s steps(8) infinite;
9+
transform-origin: center;
10+
}

components/src/primitives/style-props.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const FLEXBOX_PROPS = [
6464

6565
const GRID_PROPS = [
6666
'columnGap',
67+
'rowGap',
6768
'gridGap',
6869
'gridTemplateAreas',
6970
'gridTemplateRows',

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"babel-loader": "^8.2.2",
7979
"babel-plugin-styled-components": "2.0.7",
8080
"babel-plugin-unassert": "^3.0.1",
81+
"clsx": "2.1.1",
8182
"concurrently": "8.2.2",
8283
"conventional-changelog": "^3.1.25",
8384
"core-js": "^3.6.4",

0 commit comments

Comments
 (0)