|
| 1 | +import type { PropsWithChildren } from 'react'; |
| 2 | +import Animated, { type CSSAnimationKeyframes } from 'react-native-reanimated'; |
| 3 | +// TODO: Fix me |
| 4 | +// eslint-disable-next-line @typescript-eslint/ban-ts-comment |
| 5 | +// @ts-ignore RNSVG doesn't export types for web, see https://github.com/software-mansion/react-native-svg/pull/2801 |
| 6 | +import type { PatternProps } from 'react-native-svg'; |
| 7 | +import { Defs, Pattern, Rect, Svg } from 'react-native-svg'; |
| 8 | + |
| 9 | +import { ExamplesScreen } from '@/apps/css/components'; |
| 10 | +import { colors } from '@/theme'; |
| 11 | + |
| 12 | +const TypedDefs = Defs as React.ComponentType<PropsWithChildren>; |
| 13 | +const AnimatedPattern = Animated.createAnimatedComponent(Pattern); |
| 14 | + |
| 15 | +type PatternExtraProps = { |
| 16 | + patternUnits?: 'userSpaceOnUse' | 'objectBoundingBox'; |
| 17 | + tileWidth?: number; |
| 18 | + tileHeight?: number; |
| 19 | +}; |
| 20 | + |
| 21 | +// With userSpaceOnUse, numeric values are in viewBox user units, but percentages resolve |
| 22 | +// relative to the SVG element's physical layout size in screen pixels. These two coordinate |
| 23 | +// spaces only align when the SVG element's physical size matches the viewBox dimensions. |
| 24 | +const MIXED_UNITS_NOTE = |
| 25 | + "**Note:** With `userSpaceOnUse`, mixing absolute and percentage values is not fully supported — the absolute value is in viewBox user units, but the percentage resolves relative to the SVG element's physical layout size in screen pixels. These coordinate spaces only align when the two happen to match."; |
| 26 | + |
| 27 | +export default function PatternExample() { |
| 28 | + return ( |
| 29 | + <ExamplesScreen< |
| 30 | + { |
| 31 | + keyframes: CSSAnimationKeyframes<PatternProps>; |
| 32 | + props?: PatternExtraProps; |
| 33 | + }, |
| 34 | + PatternProps |
| 35 | + > |
| 36 | + buildAnimation={({ keyframes }) => ({ |
| 37 | + animationDirection: 'alternate', |
| 38 | + animationDuration: '2s', |
| 39 | + animationIterationCount: 'infinite', |
| 40 | + animationName: keyframes, |
| 41 | + animationTimingFunction: 'ease-in-out', |
| 42 | + })} |
| 43 | + renderExample={({ animation, props }) => ( |
| 44 | + <Svg height={100} viewBox="0 0 100 100" width={100}> |
| 45 | + <TypedDefs> |
| 46 | + <AnimatedPattern |
| 47 | + animatedProps={animation} |
| 48 | + height={props?.tileHeight ?? 20} |
| 49 | + id="stripes" |
| 50 | + patternUnits={props?.patternUnits ?? 'userSpaceOnUse'} |
| 51 | + width={props?.tileWidth ?? 20}> |
| 52 | + <Rect fill={colors.primary} height={10} width={20} /> |
| 53 | + <Rect fill={colors.primaryLight} height={10} width={20} y={10} /> |
| 54 | + </AnimatedPattern> |
| 55 | + </TypedDefs> |
| 56 | + <Rect fill="url(#stripes)" height={100} width={100} /> |
| 57 | + </Svg> |
| 58 | + )} |
| 59 | + tabs={[ |
| 60 | + { |
| 61 | + name: 'Position', |
| 62 | + sections: [ |
| 63 | + { |
| 64 | + examples: [ |
| 65 | + { |
| 66 | + description: |
| 67 | + 'With `userSpaceOnUse`, x is in the viewBox coordinate space. Animates from 5 to 20 viewBox user units.', |
| 68 | + keyframes: { from: { x: 5 }, to: { x: 20 } }, |
| 69 | + props: { |
| 70 | + patternUnits: 'userSpaceOnUse', |
| 71 | + tileHeight: 20, |
| 72 | + tileWidth: 20, |
| 73 | + }, |
| 74 | + title: 'Numeric (userSpaceOnUse)', |
| 75 | + }, |
| 76 | + { |
| 77 | + description: |
| 78 | + "With `objectBoundingBox`, x is a fraction of the bounding box of the element referencing the pattern. `0.05` = 5% and `0.2` = 20% of the referencing element's width.", |
| 79 | + keyframes: { from: { x: 0.05 }, to: { x: 0.2 } }, |
| 80 | + props: { |
| 81 | + patternUnits: 'objectBoundingBox', |
| 82 | + tileHeight: 0.2, |
| 83 | + tileWidth: 0.2, |
| 84 | + }, |
| 85 | + title: 'Numeric (objectBoundingBox)', |
| 86 | + }, |
| 87 | + { |
| 88 | + description: |
| 89 | + "Percentage values resolve relative to the SVG element's physical layout width in screen pixels.", |
| 90 | + keyframes: { from: { x: '5%' }, to: { x: '20%' } }, |
| 91 | + title: 'Percentage', |
| 92 | + }, |
| 93 | + { |
| 94 | + description: MIXED_UNITS_NOTE, |
| 95 | + keyframes: { from: { x: 15 }, to: { x: '30%' } }, |
| 96 | + title: 'Mixed', |
| 97 | + }, |
| 98 | + ], |
| 99 | + title: 'X Offset', |
| 100 | + }, |
| 101 | + { |
| 102 | + examples: [ |
| 103 | + { |
| 104 | + description: |
| 105 | + 'With `userSpaceOnUse`, y is in the viewBox coordinate space. Animates from 5 to 20 viewBox user units.', |
| 106 | + keyframes: { from: { y: 5 }, to: { y: 20 } }, |
| 107 | + props: { |
| 108 | + patternUnits: 'userSpaceOnUse', |
| 109 | + tileHeight: 20, |
| 110 | + tileWidth: 20, |
| 111 | + }, |
| 112 | + title: 'Numeric (userSpaceOnUse)', |
| 113 | + }, |
| 114 | + { |
| 115 | + description: |
| 116 | + "With `objectBoundingBox`, y is a fraction of the bounding box of the element referencing the pattern. `0.05` = 5% and `0.2` = 20% of the referencing element's height.", |
| 117 | + keyframes: { from: { y: 0.05 }, to: { y: 0.2 } }, |
| 118 | + props: { |
| 119 | + patternUnits: 'objectBoundingBox', |
| 120 | + tileHeight: 0.2, |
| 121 | + tileWidth: 0.2, |
| 122 | + }, |
| 123 | + title: 'Numeric (objectBoundingBox)', |
| 124 | + }, |
| 125 | + { |
| 126 | + description: |
| 127 | + "Percentage values resolve relative to the SVG element's physical layout height in screen pixels.", |
| 128 | + keyframes: { from: { y: '5%' }, to: { y: '20%' } }, |
| 129 | + title: 'Percentage', |
| 130 | + }, |
| 131 | + { |
| 132 | + description: MIXED_UNITS_NOTE, |
| 133 | + keyframes: { from: { y: 15 }, to: { y: '30%' } }, |
| 134 | + title: 'Mixed', |
| 135 | + }, |
| 136 | + ], |
| 137 | + title: 'Y Offset', |
| 138 | + }, |
| 139 | + ], |
| 140 | + }, |
| 141 | + { |
| 142 | + name: 'Size', |
| 143 | + sections: [ |
| 144 | + { |
| 145 | + examples: [ |
| 146 | + { |
| 147 | + description: |
| 148 | + 'With `userSpaceOnUse`, tile width is in the viewBox coordinate space. Animates from 15 to 30 viewBox user units.', |
| 149 | + keyframes: { from: { width: 15 }, to: { width: 30 } }, |
| 150 | + props: { patternUnits: 'userSpaceOnUse' }, |
| 151 | + title: 'Numeric (userSpaceOnUse)', |
| 152 | + }, |
| 153 | + { |
| 154 | + description: |
| 155 | + "With `objectBoundingBox`, tile width is a fraction of the bounding box of the element referencing the pattern. `0.15` = 15% and `0.3` = 30% of the referencing element's width.", |
| 156 | + keyframes: { from: { width: 0.15 }, to: { width: 0.3 } }, |
| 157 | + props: { patternUnits: 'objectBoundingBox' }, |
| 158 | + title: 'Numeric (objectBoundingBox)', |
| 159 | + }, |
| 160 | + { |
| 161 | + description: |
| 162 | + "Percentage values resolve relative to the SVG element's physical layout width in screen pixels.", |
| 163 | + keyframes: { |
| 164 | + from: { width: '15%' }, |
| 165 | + to: { width: '30%' }, |
| 166 | + }, |
| 167 | + title: 'Percentage', |
| 168 | + }, |
| 169 | + { |
| 170 | + description: MIXED_UNITS_NOTE, |
| 171 | + keyframes: { from: { width: 15 }, to: { width: '30%' } }, |
| 172 | + title: 'Mixed', |
| 173 | + }, |
| 174 | + ], |
| 175 | + title: 'Tile Width', |
| 176 | + }, |
| 177 | + { |
| 178 | + examples: [ |
| 179 | + { |
| 180 | + description: |
| 181 | + 'With `userSpaceOnUse`, tile height is in the viewBox coordinate space. Animates from 15 to 30 viewBox user units.', |
| 182 | + keyframes: { from: { height: 15 }, to: { height: 30 } }, |
| 183 | + props: { patternUnits: 'userSpaceOnUse' }, |
| 184 | + title: 'Numeric (userSpaceOnUse)', |
| 185 | + }, |
| 186 | + { |
| 187 | + description: |
| 188 | + "With `objectBoundingBox`, tile height is a fraction of the bounding box of the element referencing the pattern. `0.15` = 15% and `0.3` = 30% of the referencing element's height.", |
| 189 | + keyframes: { from: { height: 0.15 }, to: { height: 0.3 } }, |
| 190 | + props: { patternUnits: 'objectBoundingBox' }, |
| 191 | + title: 'Numeric (objectBoundingBox)', |
| 192 | + }, |
| 193 | + { |
| 194 | + description: |
| 195 | + "Percentage values resolve relative to the SVG element's physical layout height in screen pixels.", |
| 196 | + keyframes: { |
| 197 | + from: { height: '15%' }, |
| 198 | + to: { height: '30%' }, |
| 199 | + }, |
| 200 | + title: 'Percentage', |
| 201 | + }, |
| 202 | + { |
| 203 | + description: MIXED_UNITS_NOTE, |
| 204 | + keyframes: { from: { height: 15 }, to: { height: '30%' } }, |
| 205 | + title: 'Mixed', |
| 206 | + }, |
| 207 | + ], |
| 208 | + title: 'Tile Height', |
| 209 | + }, |
| 210 | + ], |
| 211 | + }, |
| 212 | + { |
| 213 | + name: 'Units', |
| 214 | + sections: [ |
| 215 | + { |
| 216 | + examples: [ |
| 217 | + { |
| 218 | + description: |
| 219 | + 'With `patternUnits: "userSpaceOnUse"`, the pattern tile is defined in absolute user coordinates. Animating to `"objectBoundingBox"` reinterprets those same values as fractions of the bounding box, causing an abrupt jump.', |
| 220 | + keyframes: { |
| 221 | + from: { |
| 222 | + height: 20, |
| 223 | + patternUnits: 'userSpaceOnUse', |
| 224 | + width: 20, |
| 225 | + }, |
| 226 | + to: { |
| 227 | + height: 20, |
| 228 | + patternUnits: 'objectBoundingBox', |
| 229 | + width: 20, |
| 230 | + }, |
| 231 | + }, |
| 232 | + title: 'patternUnits interpolation', |
| 233 | + }, |
| 234 | + ], |
| 235 | + title: 'Coordinate systems', |
| 236 | + }, |
| 237 | + ], |
| 238 | + }, |
| 239 | + ]} |
| 240 | + /> |
| 241 | + ); |
| 242 | +} |
0 commit comments