react-native-football-formation 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/dist/assets/index.d.ts +13 -0
- package/dist/assets/index.d.ts.map +1 -0
- package/dist/assets/index.js +15 -0
- package/dist/components/FormationField.d.ts +5 -0
- package/dist/components/FormationField.d.ts.map +1 -0
- package/dist/components/FormationField.js +147 -0
- package/dist/components/PlayerCard.d.ts +5 -0
- package/dist/components/PlayerCard.d.ts.map +1 -0
- package/dist/components/PlayerCard.js +365 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +10 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/theme/defaultTheme.d.ts +12 -0
- package/dist/theme/defaultTheme.d.ts.map +1 -0
- package/dist/theme/defaultTheme.js +70 -0
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +17 -0
- package/dist/types/component.types.d.ts +48 -0
- package/dist/types/component.types.d.ts.map +1 -0
- package/dist/types/component.types.js +2 -0
- package/dist/types/formation.types.d.ts +59 -0
- package/dist/types/formation.types.d.ts.map +1 -0
- package/dist/types/formation.types.js +5 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/theme.types.d.ts +44 -0
- package/dist/types/theme.types.d.ts.map +1 -0
- package/dist/types/theme.types.js +5 -0
- package/dist/utils/formationCoordinates.d.ts +10 -0
- package/dist/utils/formationCoordinates.d.ts.map +1 -0
- package/dist/utils/formationCoordinates.js +311 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/transformLineup.d.ts +21 -0
- package/dist/utils/transformLineup.d.ts.map +1 -0
- package/dist/utils/transformLineup.js +126 -0
- package/package.json +60 -0
- package/src/assets/images/field.png +0 -0
- package/src/assets/images/football.png +0 -0
- package/src/assets/images/kicker.png +0 -0
- package/src/assets/images/ownGoals.png +0 -0
- package/src/assets/images/playerPlaceholder.png +0 -0
- package/src/assets/images/renewal.png +0 -0
- package/src/assets/index.ts +12 -0
- package/src/components/FormationField.tsx +182 -0
- package/src/components/PlayerCard.tsx +452 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +20 -0
- package/src/theme/defaultTheme.ts +74 -0
- package/src/theme/index.ts +1 -0
- package/src/types/component.types.ts +72 -0
- package/src/types/formation.types.ts +88 -0
- package/src/types/index.ts +3 -0
- package/src/types/theme.types.ts +48 -0
- package/src/utils/formationCoordinates.ts +335 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/transformLineup.ts +158 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# React Native Football Formation
|
|
2
|
+
|
|
3
|
+
A highly customizable React Native component for displaying football/soccer team formations with player positions, stats, and match information.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
✅ **24 Supported Formations** - All major tactical formations (4-3-3, 4-2-3-1, 3-5-2, etc.)
|
|
12
|
+
✅ **Player Statistics** - Goals, assists, cards, substitutions, own goals
|
|
13
|
+
✅ **Highly Customizable** - Theme system for colors, fonts, spacing
|
|
14
|
+
✅ **RTL Support** - Built-in support for right-to-left languages
|
|
15
|
+
✅ **TypeScript** - Full type safety and IntelliSense
|
|
16
|
+
✅ **Expo & Bare RN** - Compatible with both Expo and bare React Native projects
|
|
17
|
+
✅ **Asset Override** - Use your own field backgrounds and icons
|
|
18
|
+
✅ **Component Override** - Custom rendering for player cards and footer
|
|
19
|
+
✅ **Responsive** - Adapts to different screen sizes
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install react-native-football-formation
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
yarn add react-native-football-formation
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { FormationField } from 'react-native-football-formation';
|
|
37
|
+
|
|
38
|
+
function App() {
|
|
39
|
+
const lineup = {
|
|
40
|
+
players: [/* player data */],
|
|
41
|
+
formationUsed: '433',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return <FormationField lineup={lineup} />;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Basic Usage
|
|
49
|
+
|
|
50
|
+
### Simple Example
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
import React from 'react';
|
|
54
|
+
import { FormationField } from 'react-native-football-formation';
|
|
55
|
+
|
|
56
|
+
export default function FormationScreen() {
|
|
57
|
+
const teamLineup = {
|
|
58
|
+
players: [
|
|
59
|
+
{
|
|
60
|
+
playerId: '1',
|
|
61
|
+
matchName: 'De Gea',
|
|
62
|
+
shirtNumber: 1,
|
|
63
|
+
rating: '7.5',
|
|
64
|
+
position: 'Goalkeeper',
|
|
65
|
+
formationPlace: '1',
|
|
66
|
+
stats: [],
|
|
67
|
+
},
|
|
68
|
+
// ... 10 more players
|
|
69
|
+
],
|
|
70
|
+
formationUsed: '433',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<FormationField
|
|
75
|
+
lineup={teamLineup}
|
|
76
|
+
getPlayerPhotoUrl={(playerId) =>
|
|
77
|
+
`https://example.com/players/${playerId}.png`
|
|
78
|
+
}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### With Custom Theme
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<FormationField
|
|
88
|
+
lineup={teamLineup}
|
|
89
|
+
theme={{
|
|
90
|
+
colors: {
|
|
91
|
+
primary: '#FF0000',
|
|
92
|
+
success: '#00FF00',
|
|
93
|
+
warning: '#FFAA00',
|
|
94
|
+
},
|
|
95
|
+
typography: {
|
|
96
|
+
fontFamily: 'MyCustomFont-Regular',
|
|
97
|
+
fontFamilyBold: 'MyCustomFont-Bold',
|
|
98
|
+
},
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### With Player Interaction
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<FormationField
|
|
107
|
+
lineup={teamLineup}
|
|
108
|
+
onPlayerPress={(player) => {
|
|
109
|
+
console.log('Player tapped:', player.matchName);
|
|
110
|
+
// Navigate to player details or show modal
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### With Custom Assets
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<FormationField
|
|
119
|
+
lineup={teamLineup}
|
|
120
|
+
fieldImage={require('./assets/custom-field.png')}
|
|
121
|
+
footballIcon={require('./assets/custom-goal-icon.png')}
|
|
122
|
+
playerPlaceholder={require('./assets/custom-placeholder.png')}
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## API Reference
|
|
127
|
+
|
|
128
|
+
### FormationField Props
|
|
129
|
+
|
|
130
|
+
| Prop | Type | Default | Description |
|
|
131
|
+
|------|------|---------|-------------|
|
|
132
|
+
| `lineup` | `TeamLineup` | **required** | Team lineup data with players and formation |
|
|
133
|
+
| `width` | `number` | `screenWidth + 10` | Field width |
|
|
134
|
+
| `height` | `number` | `395` | Field height |
|
|
135
|
+
| `theme` | `Partial<FormationTheme>` | `defaultTheme` | Theme customization |
|
|
136
|
+
| `fieldImage` | `ImageSourcePropType` | default field | Background field image |
|
|
137
|
+
| `playerPlaceholder` | `ImageSourcePropType` | default avatar | Placeholder for missing player photos |
|
|
138
|
+
| `footballIcon` | `ImageSourcePropType` | default icon | Icon for goals |
|
|
139
|
+
| `kickerIcon` | `ImageSourcePropType` | default icon | Icon for assists |
|
|
140
|
+
| `renewalIcon` | `ImageSourcePropType` | default icon | Icon for substitutions |
|
|
141
|
+
| `ownGoalIcon` | `ImageSourcePropType` | default icon | Icon for own goals |
|
|
142
|
+
| `logoImage` | `ImageSourcePropType` | `undefined` | Optional logo to display |
|
|
143
|
+
| `renderPlayerCard` | `(player, width, height) => ReactNode` | `undefined` | Custom player card renderer |
|
|
144
|
+
| `renderFooter` | `(formation) => ReactNode` | `undefined` | Custom footer renderer |
|
|
145
|
+
| `onPlayerPress` | `(player) => void` | `undefined` | Player tap callback |
|
|
146
|
+
| `showLogo` | `boolean` | `false` | Show/hide logo |
|
|
147
|
+
| `showFormation` | `boolean` | `true` | Show/hide formation badge |
|
|
148
|
+
| `showRating` | `boolean` | `false` | Show/hide player ratings |
|
|
149
|
+
| `containerStyle` | `ViewStyle` | `undefined` | Container style override |
|
|
150
|
+
| `playerCardStyle` | `ViewStyle` | `undefined` | Player card style override |
|
|
151
|
+
| `playerNameStyle` | `TextStyle` | `undefined` | Player name text style override |
|
|
152
|
+
| `getPlayerPhotoUrl` | `(playerId: string) => string` | `undefined` | Function to generate player photo URLs |
|
|
153
|
+
|
|
154
|
+
### TeamLineup Type
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
interface TeamLineup {
|
|
158
|
+
players: Player[];
|
|
159
|
+
formationUsed: string; // e.g., "433", "4231", "352"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface Player {
|
|
163
|
+
rating: string;
|
|
164
|
+
playerId: string;
|
|
165
|
+
position: string;
|
|
166
|
+
matchName: string;
|
|
167
|
+
shirtNumber: number;
|
|
168
|
+
formationPlace?: string; // "1" to "11"
|
|
169
|
+
stats: PlayerStats[] | null[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
interface PlayerStats {
|
|
173
|
+
type: string; // "goals", "yellowCard", "redCard", "goalAssist", etc.
|
|
174
|
+
value: string | number;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### FormationTheme Type
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
interface FormationTheme {
|
|
182
|
+
colors: {
|
|
183
|
+
primary: string;
|
|
184
|
+
blue: string;
|
|
185
|
+
white: string;
|
|
186
|
+
text: string;
|
|
187
|
+
border: string;
|
|
188
|
+
warning: string;
|
|
189
|
+
success: string;
|
|
190
|
+
error: string;
|
|
191
|
+
newError: string;
|
|
192
|
+
formationBadge: string;
|
|
193
|
+
};
|
|
194
|
+
spacing: {
|
|
195
|
+
playerCardWidth: number;
|
|
196
|
+
playerCardHeight: number;
|
|
197
|
+
playerImageSize: number;
|
|
198
|
+
jerseyNumberSize: number;
|
|
199
|
+
iconSize: number;
|
|
200
|
+
badgeMinWidth: number;
|
|
201
|
+
badgeHeight: number;
|
|
202
|
+
};
|
|
203
|
+
typography: {
|
|
204
|
+
playerNameSize: number;
|
|
205
|
+
formationSize: number;
|
|
206
|
+
jerseyNumberSize: number;
|
|
207
|
+
goalCountSize: number;
|
|
208
|
+
fontFamily?: string;
|
|
209
|
+
fontFamilyBold?: string;
|
|
210
|
+
};
|
|
211
|
+
borderRadius: {
|
|
212
|
+
playerImage: number;
|
|
213
|
+
badge: number;
|
|
214
|
+
card: number;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Supported Formations
|
|
220
|
+
|
|
221
|
+
The component supports 24 different tactical formations:
|
|
222
|
+
|
|
223
|
+
### 4-Back Formations
|
|
224
|
+
- `4-4-2` - Classic 4-4-2
|
|
225
|
+
- `4-3-3` - 4-3-3
|
|
226
|
+
- `4-2-3-1` - 4-2-3-1 (most common modern formation)
|
|
227
|
+
- `4-1-2-1-2` - 4-1-2-1-2 Diamond
|
|
228
|
+
- `4-5-1` - 4-5-1
|
|
229
|
+
- `4-4-1-1` - 4-4-1-1
|
|
230
|
+
- `4-1-4-1` - 4-1-4-1
|
|
231
|
+
- `4-3-2-1` - 4-3-2-1 Christmas Tree
|
|
232
|
+
- `4-2-2-2` - 4-2-2-2
|
|
233
|
+
- `4-1-3-2` - 4-1-3-2
|
|
234
|
+
- `4-2-4-0` - 4-2-4-0 False 9
|
|
235
|
+
- `4-3-1-2` - 4-3-1-2
|
|
236
|
+
|
|
237
|
+
### 5-Back Formations
|
|
238
|
+
- `5-3-2` - 5-3-2
|
|
239
|
+
- `5-4-1` - 5-4-1
|
|
240
|
+
|
|
241
|
+
### 3-Back Formations
|
|
242
|
+
- `3-5-2` - 3-5-2
|
|
243
|
+
- `3-4-3` - 3-4-3
|
|
244
|
+
- `3-5-1-1` - 3-5-1-1
|
|
245
|
+
- `3-4-2-1` - 3-4-2-1
|
|
246
|
+
- `3-4-1-2` - 3-4-1-2
|
|
247
|
+
- `3-1-4-2` - 3-1-4-2
|
|
248
|
+
- `3-4-3d` - 3-4-3 Diamond
|
|
249
|
+
- `3-2-4-1` - 3-2-4-1
|
|
250
|
+
- `3-3-3-1` - 3-3-3-1
|
|
251
|
+
|
|
252
|
+
## Advanced Usage
|
|
253
|
+
|
|
254
|
+
### Custom Player Card Renderer
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<FormationField
|
|
258
|
+
lineup={teamLineup}
|
|
259
|
+
renderPlayerCard={(player, fieldWidth, fieldHeight) => (
|
|
260
|
+
<CustomPlayerCard
|
|
261
|
+
player={player}
|
|
262
|
+
width={fieldWidth}
|
|
263
|
+
height={fieldHeight}
|
|
264
|
+
onPress={() => navigateToPlayerDetails(player)}
|
|
265
|
+
/>
|
|
266
|
+
)}
|
|
267
|
+
/>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Custom Footer
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
<FormationField
|
|
274
|
+
lineup={teamLineup}
|
|
275
|
+
renderFooter={(formation) => (
|
|
276
|
+
<View style={styles.customFooter}>
|
|
277
|
+
<Text>Formation: {formation}</Text>
|
|
278
|
+
<Text>Tactical Analysis</Text>
|
|
279
|
+
</View>
|
|
280
|
+
)}
|
|
281
|
+
/>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Full Customization Example
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
<FormationField
|
|
288
|
+
lineup={teamLineup}
|
|
289
|
+
width={400}
|
|
290
|
+
height={500}
|
|
291
|
+
theme={{
|
|
292
|
+
colors: {
|
|
293
|
+
primary: '#0066CC',
|
|
294
|
+
success: '#28A745',
|
|
295
|
+
warning: '#FFC107',
|
|
296
|
+
error: '#DC3545',
|
|
297
|
+
text: '#000000',
|
|
298
|
+
white: '#FFFFFF',
|
|
299
|
+
formationBadge: '#2C3E50',
|
|
300
|
+
},
|
|
301
|
+
typography: {
|
|
302
|
+
fontFamily: 'Roboto-Regular',
|
|
303
|
+
fontFamilyBold: 'Roboto-Bold',
|
|
304
|
+
playerNameSize: 14,
|
|
305
|
+
formationSize: 16,
|
|
306
|
+
},
|
|
307
|
+
spacing: {
|
|
308
|
+
playerCardWidth: 80,
|
|
309
|
+
playerCardHeight: 60,
|
|
310
|
+
playerImageSize: 50,
|
|
311
|
+
},
|
|
312
|
+
}}
|
|
313
|
+
fieldImage={require('./assets/grass-field.png')}
|
|
314
|
+
onPlayerPress={(player) => console.log(player)}
|
|
315
|
+
showFormation={true}
|
|
316
|
+
showLogo={false}
|
|
317
|
+
getPlayerPhotoUrl={(playerId) =>
|
|
318
|
+
`https://cdn.example.com/players/${playerId}.png`
|
|
319
|
+
}
|
|
320
|
+
/>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Player Statistics
|
|
324
|
+
|
|
325
|
+
The component automatically displays player statistics when available:
|
|
326
|
+
|
|
327
|
+
- ⚽ **Goals** - Football icon with count
|
|
328
|
+
- 👟 **Assists** - Kicker icon
|
|
329
|
+
- 🟨 **Yellow Card** - Yellow card indicator
|
|
330
|
+
- 🟥 **Red Card** - Red card indicator
|
|
331
|
+
- 🔄 **Substitution** - Substitution icon
|
|
332
|
+
- ⚽🔴 **Own Goals** - Own goal icon with count
|
|
333
|
+
|
|
334
|
+
Statistics are extracted from the `stats` array in the player data:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
{
|
|
338
|
+
stats: [
|
|
339
|
+
{ type: 'goals', value: '2' },
|
|
340
|
+
{ type: 'yellowCard', value: '1' },
|
|
341
|
+
{ type: 'goalAssist', value: '1' },
|
|
342
|
+
]
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Utilities
|
|
347
|
+
|
|
348
|
+
The package exports utility functions for advanced use cases:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import {
|
|
352
|
+
transformLineupByFormationPlace,
|
|
353
|
+
hasStat,
|
|
354
|
+
getStatValue,
|
|
355
|
+
FORMATION_COORDINATES_BY_PLACE,
|
|
356
|
+
} from 'react-native-football-formation';
|
|
357
|
+
|
|
358
|
+
// Transform raw lineup data
|
|
359
|
+
const positionedPlayers = transformLineupByFormationPlace(
|
|
360
|
+
teamLineup,
|
|
361
|
+
'4-3-3',
|
|
362
|
+
(playerId) => `https://example.com/${playerId}.png`
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Check if player has a stat
|
|
366
|
+
const hasGoals = hasStat(player.stats, 'goals');
|
|
367
|
+
|
|
368
|
+
// Get stat value
|
|
369
|
+
const goalCount = getStatValue(player.stats, 'goals');
|
|
370
|
+
|
|
371
|
+
// Access formation coordinates
|
|
372
|
+
const coords = FORMATION_COORDINATES_BY_PLACE['4-3-3']['1']; // GK position
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## TypeScript Support
|
|
376
|
+
|
|
377
|
+
The package is written in TypeScript and includes full type definitions. Import types for better development experience:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import type {
|
|
381
|
+
TeamLineup,
|
|
382
|
+
Player,
|
|
383
|
+
LineupFormationPlayer,
|
|
384
|
+
FormationTheme,
|
|
385
|
+
FormationFieldProps,
|
|
386
|
+
} from 'react-native-football-formation';
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Troubleshooting
|
|
390
|
+
|
|
391
|
+
### Images not loading
|
|
392
|
+
|
|
393
|
+
Make sure you've provided either:
|
|
394
|
+
1. Player photo URLs via the `photo` field in player data, OR
|
|
395
|
+
2. A `getPlayerPhotoUrl` function prop
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
<FormationField
|
|
399
|
+
lineup={teamLineup}
|
|
400
|
+
getPlayerPhotoUrl={(playerId) =>
|
|
401
|
+
`https://your-cdn.com/players/${playerId}.png`
|
|
402
|
+
}
|
|
403
|
+
/>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Formation not displaying correctly
|
|
407
|
+
|
|
408
|
+
Ensure the `formationUsed` field matches one of the supported formations (without dashes):
|
|
409
|
+
- ✅ `"433"` or `"4-3-3"`
|
|
410
|
+
- ❌ `"4-3-3-1"` (not supported)
|
|
411
|
+
|
|
412
|
+
The component automatically handles both formats ("433" and "4-3-3").
|
|
413
|
+
|
|
414
|
+
### Players in wrong positions
|
|
415
|
+
|
|
416
|
+
Check that each player has a valid `formationPlace` field from "1" to "11":
|
|
417
|
+
- Position 1 is always the goalkeeper
|
|
418
|
+
- Positions 2-11 depend on the formation
|
|
419
|
+
|
|
420
|
+
## Examples
|
|
421
|
+
|
|
422
|
+
See the `/examples` directory for complete working examples:
|
|
423
|
+
|
|
424
|
+
- **basic/** - Simple formation display
|
|
425
|
+
- **custom-styling/** - Custom theme and styling
|
|
426
|
+
- **custom-assets/** - Custom images and icons
|
|
427
|
+
|
|
428
|
+
## Contributing
|
|
429
|
+
|
|
430
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
431
|
+
|
|
432
|
+
## License
|
|
433
|
+
|
|
434
|
+
MIT © Arbab Rafiq
|
|
435
|
+
|
|
436
|
+
## Author
|
|
437
|
+
|
|
438
|
+
**Arbab Rafiq**
|
|
439
|
+
|
|
440
|
+
## Acknowledgments
|
|
441
|
+
|
|
442
|
+
- Inspired by modern football tactical analysis tools
|
|
443
|
+
- Built for the React Native community
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
Made with ⚽ for football fans and developers
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default image assets for the Formation Field component
|
|
3
|
+
* Users can override these by passing custom ImageSourcePropType to the component
|
|
4
|
+
*/
|
|
5
|
+
export declare const defaultAssets: {
|
|
6
|
+
field: any;
|
|
7
|
+
football: any;
|
|
8
|
+
kicker: any;
|
|
9
|
+
renewal: any;
|
|
10
|
+
ownGoals: any;
|
|
11
|
+
playerPlaceholder: any;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/assets/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;;CAOzB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultAssets = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default image assets for the Formation Field component
|
|
6
|
+
* Users can override these by passing custom ImageSourcePropType to the component
|
|
7
|
+
*/
|
|
8
|
+
exports.defaultAssets = {
|
|
9
|
+
field: require('./images/field.png'),
|
|
10
|
+
football: require('./images/football.png'),
|
|
11
|
+
kicker: require('./images/kicker.png'),
|
|
12
|
+
renewal: require('./images/renewal.png'),
|
|
13
|
+
ownGoals: require('./images/ownGoals.png'),
|
|
14
|
+
playerPlaceholder: require('./images/playerPlaceholder.png'),
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormationField.d.ts","sourceRoot":"","sources":["../../src/components/FormationField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAUvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;;AA2K/C,wBAA0C"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const react_1 = __importStar(require("react"));
|
|
40
|
+
const react_native_1 = require("react-native");
|
|
41
|
+
const utils_1 = require("../utils");
|
|
42
|
+
const theme_1 = require("../theme");
|
|
43
|
+
const assets_1 = require("../assets");
|
|
44
|
+
const PlayerCard_1 = __importDefault(require("./PlayerCard"));
|
|
45
|
+
const { width: screenWidth } = react_native_1.Dimensions.get('window');
|
|
46
|
+
const FormationField = ({ lineup, width = screenWidth + 10, height = 395, theme: customTheme, fieldImage = assets_1.defaultAssets.field, playerPlaceholder, footballIcon, kickerIcon, renewalIcon, ownGoalIcon, logoImage, renderPlayerCard, renderFooter, onPlayerPress, showLogo = false, showFormation = true, showRating = false, containerStyle, playerCardStyle, playerNameStyle, getPlayerPhotoUrl, }) => {
|
|
47
|
+
const theme = (0, theme_1.mergeTheme)(customTheme);
|
|
48
|
+
const lineupTransformed = (0, react_1.useMemo)(() => {
|
|
49
|
+
if (!lineup)
|
|
50
|
+
return [];
|
|
51
|
+
const formation = lineup?.formationUsed?.split('').join('-') || '4-3-3';
|
|
52
|
+
return (0, utils_1.transformLineupByFormationPlace)(lineup, formation, getPlayerPhotoUrl);
|
|
53
|
+
}, [lineup, getPlayerPhotoUrl]);
|
|
54
|
+
const formationDisplay = lineup?.formationUsed?.split('').join('-') || '4-3-3';
|
|
55
|
+
return (<react_native_1.ImageBackground source={fieldImage} style={[
|
|
56
|
+
styles.footballField,
|
|
57
|
+
{
|
|
58
|
+
width,
|
|
59
|
+
height,
|
|
60
|
+
},
|
|
61
|
+
containerStyle,
|
|
62
|
+
]} resizeMode="cover">
|
|
63
|
+
{/* Players */}
|
|
64
|
+
{!!lineupTransformed?.length &&
|
|
65
|
+
lineupTransformed?.map(player => {
|
|
66
|
+
if (renderPlayerCard) {
|
|
67
|
+
return (<react_native_1.View key={player.playerId}>
|
|
68
|
+
{renderPlayerCard(player, width, height)}
|
|
69
|
+
</react_native_1.View>);
|
|
70
|
+
}
|
|
71
|
+
return (<PlayerCard_1.default key={player.playerId} player={player} fieldWidth={width} fieldHeight={height} theme={theme} onPress={onPlayerPress} style={playerCardStyle} nameStyle={playerNameStyle} playerPlaceholder={playerPlaceholder} footballIcon={footballIcon} kickerIcon={kickerIcon} renewalIcon={renewalIcon} ownGoalIcon={ownGoalIcon}/>);
|
|
72
|
+
})}
|
|
73
|
+
|
|
74
|
+
{/* Footer Section */}
|
|
75
|
+
{renderFooter ? (<react_native_1.View style={styles.fieldBottomWrapper}>
|
|
76
|
+
{renderFooter(formationDisplay)}
|
|
77
|
+
</react_native_1.View>) : (<react_native_1.View style={styles.fieldBottomWrapper}>
|
|
78
|
+
<react_native_1.View style={[
|
|
79
|
+
styles.fieldBottomContainer,
|
|
80
|
+
{
|
|
81
|
+
flexDirection: react_native_1.I18nManager.isRTL ? 'row-reverse' : 'row',
|
|
82
|
+
},
|
|
83
|
+
]}>
|
|
84
|
+
{/* Formation Display */}
|
|
85
|
+
{showFormation && (<react_native_1.View style={{ justifyContent: 'center' }}>
|
|
86
|
+
<react_native_1.View style={[
|
|
87
|
+
styles.formationUsedContainer,
|
|
88
|
+
{ backgroundColor: theme.colors.formationBadge },
|
|
89
|
+
]}>
|
|
90
|
+
<react_native_1.Text style={[
|
|
91
|
+
styles.formationText,
|
|
92
|
+
{
|
|
93
|
+
fontSize: theme.typography.formationSize,
|
|
94
|
+
fontFamily: theme.typography.fontFamilyBold,
|
|
95
|
+
color: theme.colors.white,
|
|
96
|
+
},
|
|
97
|
+
]}>
|
|
98
|
+
{formationDisplay}
|
|
99
|
+
</react_native_1.Text>
|
|
100
|
+
</react_native_1.View>
|
|
101
|
+
</react_native_1.View>)}
|
|
102
|
+
|
|
103
|
+
{/* Optional Logo */}
|
|
104
|
+
{showLogo && logoImage && (<react_native_1.View style={styles.logoImageContainer}>
|
|
105
|
+
<react_native_1.ImageBackground source={logoImage} style={styles.logoImage}/>
|
|
106
|
+
</react_native_1.View>)}
|
|
107
|
+
</react_native_1.View>
|
|
108
|
+
</react_native_1.View>)}
|
|
109
|
+
</react_native_1.ImageBackground>);
|
|
110
|
+
};
|
|
111
|
+
const styles = react_native_1.StyleSheet.create({
|
|
112
|
+
footballField: {
|
|
113
|
+
position: 'relative',
|
|
114
|
+
marginTop: 20,
|
|
115
|
+
},
|
|
116
|
+
fieldBottomWrapper: {
|
|
117
|
+
height: 40,
|
|
118
|
+
position: 'absolute',
|
|
119
|
+
bottom: react_native_1.Platform.OS === 'ios' ? 4 : 5,
|
|
120
|
+
},
|
|
121
|
+
fieldBottomContainer: {
|
|
122
|
+
paddingHorizontal: 10,
|
|
123
|
+
width: '100%',
|
|
124
|
+
justifyContent: 'space-between',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
},
|
|
127
|
+
formationUsedContainer: {
|
|
128
|
+
minWidth: 60,
|
|
129
|
+
minHeight: 20,
|
|
130
|
+
borderRadius: 99,
|
|
131
|
+
justifyContent: 'center',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
paddingHorizontal: 12,
|
|
134
|
+
paddingVertical: 4,
|
|
135
|
+
},
|
|
136
|
+
formationText: {
|
|
137
|
+
includeFontPadding: false,
|
|
138
|
+
},
|
|
139
|
+
logoImageContainer: {
|
|
140
|
+
justifyContent: 'center',
|
|
141
|
+
},
|
|
142
|
+
logoImage: {
|
|
143
|
+
width: 64,
|
|
144
|
+
height: 60,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
exports.default = react_1.default.memo(FormationField);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlayerCard.d.ts","sourceRoot":"","sources":["../../src/components/PlayerCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;;AAyb3C,wBAAsC"}
|