|
| 1 | +import { StyleProp, StyleSheet, ViewStyle } from 'react-native'; |
| 2 | + |
| 3 | +const STYLE_GROUPS = { |
| 4 | + borderRadiiStyles: { |
| 5 | + borderRadius: true, |
| 6 | + borderTopLeftRadius: true, |
| 7 | + borderTopRightRadius: true, |
| 8 | + borderBottomLeftRadius: true, |
| 9 | + borderBottomRightRadius: true, |
| 10 | + } as const, |
| 11 | + outerStyles: { |
| 12 | + borderColor: true, |
| 13 | + borderWidth: true, |
| 14 | + margin: true, |
| 15 | + marginBottom: true, |
| 16 | + marginEnd: true, |
| 17 | + marginHorizontal: true, |
| 18 | + marginLeft: true, |
| 19 | + marginRight: true, |
| 20 | + marginStart: true, |
| 21 | + marginTop: true, |
| 22 | + marginVertical: true, |
| 23 | + width: true, |
| 24 | + height: true, |
| 25 | + } as const, |
| 26 | + innerStyles: { |
| 27 | + alignSelf: true, |
| 28 | + display: true, |
| 29 | + flexBasis: true, |
| 30 | + flexGrow: true, |
| 31 | + flexShrink: true, |
| 32 | + maxHeight: true, |
| 33 | + maxWidth: true, |
| 34 | + minHeight: true, |
| 35 | + minWidth: true, |
| 36 | + zIndex: true, |
| 37 | + } as const, |
| 38 | + applyToAllStyles: { |
| 39 | + flex: true, |
| 40 | + position: true, |
| 41 | + left: true, |
| 42 | + right: true, |
| 43 | + top: true, |
| 44 | + bottom: true, |
| 45 | + start: true, |
| 46 | + end: true, |
| 47 | + } as const, |
| 48 | +} as const; |
| 49 | + |
| 50 | +type BorderRadiiKey = keyof typeof STYLE_GROUPS.borderRadiiStyles; |
| 51 | +type OuterKey = keyof typeof STYLE_GROUPS.outerStyles; |
| 52 | +type InnerKey = keyof typeof STYLE_GROUPS.innerStyles; |
| 53 | +type ApplyToAllKey = keyof typeof STYLE_GROUPS.applyToAllStyles; |
| 54 | + |
| 55 | +type BorderRadiiStyles = Pick<ViewStyle, BorderRadiiKey>; |
| 56 | +type OuterStyles = Pick<ViewStyle, OuterKey>; |
| 57 | +type InnerStyles = Pick<ViewStyle, InnerKey>; |
| 58 | +type ApplyToAllStyles = Pick<ViewStyle, ApplyToAllKey>; |
| 59 | +type RestStyles = Omit< |
| 60 | + ViewStyle, |
| 61 | + BorderRadiiKey | OuterKey | InnerKey | ApplyToAllKey |
| 62 | +>; |
| 63 | + |
| 64 | +type GroupedStyles = { |
| 65 | + borderRadiiStyles: BorderRadiiStyles; |
| 66 | + outerStyles: OuterStyles; |
| 67 | + innerStyles: InnerStyles; |
| 68 | + applyToAllStyles: ApplyToAllStyles; |
| 69 | + restStyles: RestStyles; |
| 70 | +}; |
| 71 | + |
| 72 | +const groupByStyle = (styles: ViewStyle): GroupedStyles => { |
| 73 | + const borderRadiiStyles = {} as Record<string, unknown>; |
| 74 | + const outerStyles = {} as Record<string, unknown>; |
| 75 | + const innerStyles = {} as Record<string, unknown>; |
| 76 | + const applyToAllStyles = {} as Record<string, unknown>; |
| 77 | + const restStyles = {} as Record<string, unknown>; |
| 78 | + |
| 79 | + let key: keyof ViewStyle; |
| 80 | + |
| 81 | + for (key in styles) { |
| 82 | + if (key in STYLE_GROUPS.borderRadiiStyles) { |
| 83 | + borderRadiiStyles[key] = styles[key]; |
| 84 | + } else if (key in STYLE_GROUPS.outerStyles) { |
| 85 | + outerStyles[key] = styles[key]; |
| 86 | + } else if (key in STYLE_GROUPS.innerStyles) { |
| 87 | + innerStyles[key] = styles[key]; |
| 88 | + } else if (key in STYLE_GROUPS.applyToAllStyles) { |
| 89 | + applyToAllStyles[key] = styles[key]; |
| 90 | + } else { |
| 91 | + restStyles[key] = styles[key]; |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + return { |
| 96 | + borderRadiiStyles, |
| 97 | + outerStyles, |
| 98 | + innerStyles, |
| 99 | + applyToAllStyles, |
| 100 | + restStyles, |
| 101 | + }; |
| 102 | +}; |
| 103 | + |
| 104 | +// if borderWidth was specified it will adjust the border radii |
| 105 | +// to remain the same curvature for both inner and outer views |
| 106 | +// https://twitter.com/lilykonings/status/1567317037126680576 |
| 107 | +const shrinkBorderRadiiByBorderWidth = ( |
| 108 | + borderRadiiStyles: BorderRadiiStyles, |
| 109 | + borderWidth: number |
| 110 | +) => { |
| 111 | + const newBorderRadiiStyles = { ...borderRadiiStyles }; |
| 112 | + |
| 113 | + let borderRadiusType: BorderRadiiKey; |
| 114 | + |
| 115 | + for (borderRadiusType in newBorderRadiiStyles) { |
| 116 | + newBorderRadiiStyles[borderRadiusType] = |
| 117 | + (newBorderRadiiStyles[borderRadiusType] as number) - borderWidth; |
| 118 | + } |
| 119 | + |
| 120 | + return newBorderRadiiStyles; |
| 121 | +}; |
| 122 | + |
| 123 | +export function splitStyleProp<T extends ViewStyle>( |
| 124 | + style?: StyleProp<T> |
| 125 | +): { |
| 126 | + outerStyles: T; |
| 127 | + innerStyles: T; |
| 128 | + restStyles: T; |
| 129 | +} { |
| 130 | + const resolvedStyle = StyleSheet.flatten((style ?? {}) as ViewStyle); |
| 131 | + |
| 132 | + let outerStyles = {} as T; |
| 133 | + let innerStyles = { overflow: 'hidden', flexGrow: 1 } as T; |
| 134 | + let restStyles = { flexGrow: 1 } as T; |
| 135 | + |
| 136 | + const styleGroups = groupByStyle(resolvedStyle); |
| 137 | + |
| 138 | + outerStyles = { |
| 139 | + ...outerStyles, |
| 140 | + ...styleGroups.borderRadiiStyles, |
| 141 | + ...styleGroups.applyToAllStyles, |
| 142 | + ...styleGroups.outerStyles, |
| 143 | + }; |
| 144 | + innerStyles = { |
| 145 | + ...innerStyles, |
| 146 | + ...styleGroups.applyToAllStyles, |
| 147 | + ...styleGroups.innerStyles, |
| 148 | + }; |
| 149 | + restStyles = { |
| 150 | + ...restStyles, |
| 151 | + ...styleGroups.restStyles, |
| 152 | + ...styleGroups.applyToAllStyles, |
| 153 | + }; |
| 154 | + |
| 155 | + // if borderWidth was specified it adjusts border radii |
| 156 | + // to remain the same curvature for both inner and outer views |
| 157 | + if (styleGroups.outerStyles.borderWidth != null) { |
| 158 | + const { borderWidth } = styleGroups.outerStyles; |
| 159 | + |
| 160 | + const innerBorderRadiiStyles = shrinkBorderRadiiByBorderWidth( |
| 161 | + { ...styleGroups.borderRadiiStyles }, |
| 162 | + borderWidth |
| 163 | + ); |
| 164 | + |
| 165 | + innerStyles = { ...innerStyles, ...innerBorderRadiiStyles }; |
| 166 | + } |
| 167 | + |
| 168 | + return { outerStyles, innerStyles, restStyles }; |
| 169 | +} |
0 commit comments