panelui-native 0.103.2 → 0.104.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/README.md +1 -0
- package/lib/module/components/flow/index.js +64 -55
- package/lib/module/components/flow/index.js.map +1 -1
- package/lib/module/components/leaderboard/index.js +442 -0
- package/lib/module/components/leaderboard/index.js.map +1 -0
- package/lib/module/components/leaderboard/leaderboard-rank.js +65 -0
- package/lib/module/components/leaderboard/leaderboard-rank.js.map +1 -0
- package/lib/module/components/map/index.js +114 -50
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
- package/lib/typescript/src/components/leaderboard/index.d.ts +63 -0
- package/lib/typescript/src/components/leaderboard/index.d.ts.map +1 -0
- package/lib/typescript/src/components/leaderboard/leaderboard-rank.d.ts +41 -0
- package/lib/typescript/src/components/leaderboard/leaderboard-rank.d.ts.map +1 -0
- package/lib/typescript/src/components/map/index.d.ts +30 -5
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/flow/index.tsx +65 -51
- package/src/components/leaderboard/index.tsx +535 -0
- package/src/components/leaderboard/leaderboard-rank.ts +84 -0
- package/src/components/map/index.tsx +130 -44
- package/src/index.ts +10 -0
|
@@ -46,7 +46,14 @@ import {
|
|
|
46
46
|
import { Pressable, StyleSheet, View, type PressableProps, type ViewProps } from 'react-native';
|
|
47
47
|
import { useCSSVariable } from 'uniwind';
|
|
48
48
|
import { tv } from 'tailwind-variants';
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
CompassIcon,
|
|
51
|
+
CrosshairIcon,
|
|
52
|
+
IconColorProvider,
|
|
53
|
+
MaximizeIcon,
|
|
54
|
+
MinusIcon,
|
|
55
|
+
PlusIcon,
|
|
56
|
+
} from '../../icons';
|
|
50
57
|
import { Text, textChildren } from '../../primitives/text';
|
|
51
58
|
import { cn } from '../../utils/cn';
|
|
52
59
|
import {
|
|
@@ -64,6 +71,7 @@ import {
|
|
|
64
71
|
type MapRef,
|
|
65
72
|
type PixelPoint,
|
|
66
73
|
type StyleSpecification,
|
|
74
|
+
type ViewPadding,
|
|
67
75
|
type ViewState,
|
|
68
76
|
} from './maplibre';
|
|
69
77
|
import {
|
|
@@ -74,7 +82,15 @@ import {
|
|
|
74
82
|
import { asMapLayer, partitionMapChildren } from './map-children';
|
|
75
83
|
|
|
76
84
|
export { hasMapLibre, CARTO_SOURCE };
|
|
77
|
-
export type {
|
|
85
|
+
export type {
|
|
86
|
+
BasemapSource,
|
|
87
|
+
BasemapTokens,
|
|
88
|
+
LngLat,
|
|
89
|
+
LngLatBounds,
|
|
90
|
+
PixelPoint,
|
|
91
|
+
ViewPadding,
|
|
92
|
+
ViewState,
|
|
93
|
+
};
|
|
78
94
|
export type { MapFeatureAccessibility } from './map-accessibility';
|
|
79
95
|
|
|
80
96
|
const mapVariants = tv({
|
|
@@ -179,6 +195,8 @@ interface MapContextValue {
|
|
|
179
195
|
cameraRef: React.RefObject<CameraRef | null>;
|
|
180
196
|
/** The style has loaded and the map is drawing. */
|
|
181
197
|
ready: boolean;
|
|
198
|
+
/** Move the camera back to where the map opened. */
|
|
199
|
+
recenter(duration?: number): void;
|
|
182
200
|
}
|
|
183
201
|
|
|
184
202
|
interface AccessibleFeatureGroup {
|
|
@@ -207,15 +225,41 @@ export function useMap(): MapContextValue {
|
|
|
207
225
|
/** Set by `Map.Marker`, so a popup inside one knows where it is. */
|
|
208
226
|
const MarkerContext = createContext<LngLat | null>(null);
|
|
209
227
|
|
|
228
|
+
/** How `fitBounds` frames its box. */
|
|
229
|
+
export interface MapFitBoundsOptions {
|
|
230
|
+
/**
|
|
231
|
+
* Points kept clear around the box. One number for every side, or per side
|
|
232
|
+
* — for a sheet or a toolbar covering part of the map. Defaults to 48.
|
|
233
|
+
*/
|
|
234
|
+
padding?: number | ViewPadding;
|
|
235
|
+
/** Animation length in milliseconds. 0 jumps. Defaults to 900. */
|
|
236
|
+
duration?: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
210
239
|
export interface MapHandle {
|
|
211
240
|
/** Move the camera, animating unless `duration` is 0. */
|
|
212
241
|
flyTo(options: { center: LngLat; zoom?: number; duration?: number }): void;
|
|
213
|
-
/**
|
|
214
|
-
|
|
242
|
+
/**
|
|
243
|
+
* Frame a bounding box, `[west, south, east, north]`. The second argument is
|
|
244
|
+
* either the padding in points or a `MapFitBoundsOptions`.
|
|
245
|
+
*/
|
|
246
|
+
fitBounds(bounds: LngLatBounds, options?: number | MapFitBoundsOptions): void;
|
|
247
|
+
/**
|
|
248
|
+
* Return to the opening view — the `bounds`, or `center` and `zoom`, the map
|
|
249
|
+
* was mounted with. Animates unless `duration` is 0.
|
|
250
|
+
*/
|
|
251
|
+
recenter(duration?: number): void;
|
|
215
252
|
/** Where the map is looking right now. */
|
|
216
253
|
getViewState(): Promise<ViewState | null>;
|
|
217
254
|
}
|
|
218
255
|
|
|
256
|
+
/** Expands a padding shorthand to the four sides the camera takes. */
|
|
257
|
+
function toViewPadding(padding: number | ViewPadding): ViewPadding {
|
|
258
|
+
return typeof padding === 'number'
|
|
259
|
+
? { top: padding, right: padding, bottom: padding, left: padding }
|
|
260
|
+
: padding;
|
|
261
|
+
}
|
|
262
|
+
|
|
219
263
|
export interface MapProps extends Omit<ViewProps, 'children'> {
|
|
220
264
|
children?: ReactNode;
|
|
221
265
|
/** Initial centre, `[longitude, latitude]`. */
|
|
@@ -361,29 +405,52 @@ const MapRoot = forwardRef<MapHandle, MapProps>(function MapRoot(
|
|
|
361
405
|
]
|
|
362
406
|
);
|
|
363
407
|
|
|
408
|
+
// The opening view, kept from the first render. The props describe where
|
|
409
|
+
// the map starts, not where it is, so a later change to them must not move
|
|
410
|
+
// the target `recenter` returns to without the caller asking.
|
|
411
|
+
const initialView = useRef({ center, zoom, bearing, pitch, bounds }).current;
|
|
412
|
+
|
|
413
|
+
const recenter = useCallback(
|
|
414
|
+
(duration = 600) => {
|
|
415
|
+
const camera = cameraRef.current;
|
|
416
|
+
if (!camera) return;
|
|
417
|
+
if (initialView.bounds) {
|
|
418
|
+
camera.fitBounds(initialView.bounds, { duration });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
camera.flyTo({
|
|
422
|
+
center: initialView.center ?? [0, 20],
|
|
423
|
+
zoom: initialView.zoom,
|
|
424
|
+
bearing: initialView.bearing ?? 0,
|
|
425
|
+
pitch: initialView.pitch ?? 0,
|
|
426
|
+
duration,
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
[initialView]
|
|
430
|
+
);
|
|
431
|
+
|
|
364
432
|
useImperativeHandle(
|
|
365
433
|
ref,
|
|
366
434
|
() => ({
|
|
367
435
|
flyTo: ({ center: to, zoom: z, duration = 900 }) =>
|
|
368
436
|
cameraRef.current?.flyTo({ center: to, zoom: z, duration }),
|
|
369
|
-
fitBounds: (box,
|
|
437
|
+
fitBounds: (box, options) => {
|
|
438
|
+
const { padding = 48, duration = 900 } =
|
|
439
|
+
typeof options === 'number' ? { padding: options } : (options ?? {});
|
|
370
440
|
cameraRef.current?.fitBounds(box, {
|
|
371
|
-
padding:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
},
|
|
377
|
-
duration: 900,
|
|
378
|
-
}),
|
|
441
|
+
padding: toViewPadding(padding),
|
|
442
|
+
duration,
|
|
443
|
+
});
|
|
444
|
+
},
|
|
445
|
+
recenter,
|
|
379
446
|
getViewState: async () => (await mapRef.current?.getViewState()) ?? null,
|
|
380
447
|
}),
|
|
381
|
-
[]
|
|
448
|
+
[recenter]
|
|
382
449
|
);
|
|
383
450
|
|
|
384
451
|
const context = useMemo(
|
|
385
|
-
() => ({ mapRef, cameraRef, ready, registerAccessibleFeatures }),
|
|
386
|
-
[ready, registerAccessibleFeatures]
|
|
452
|
+
() => ({ mapRef, cameraRef, ready, recenter, registerAccessibleFeatures }),
|
|
453
|
+
[ready, recenter, registerAccessibleFeatures]
|
|
387
454
|
);
|
|
388
455
|
|
|
389
456
|
if (!hasMapLibre || !MapLibre) {
|
|
@@ -722,6 +789,11 @@ export interface MapControlsProps {
|
|
|
722
789
|
locate?: boolean;
|
|
723
790
|
/** Reset bearing and pitch to north and flat. */
|
|
724
791
|
compass?: boolean;
|
|
792
|
+
/**
|
|
793
|
+
* Return to the view the map opened on — its `bounds`, or its `center` and
|
|
794
|
+
* `zoom`. The same move as `MapHandle.recenter()`.
|
|
795
|
+
*/
|
|
796
|
+
recenter?: boolean;
|
|
725
797
|
className?: string;
|
|
726
798
|
/** Called with the located coordinate, so a caller can react to it. */
|
|
727
799
|
onLocate?: (lngLat: LngLat) => void;
|
|
@@ -762,11 +834,16 @@ function MapControls({
|
|
|
762
834
|
zoom = true,
|
|
763
835
|
locate = false,
|
|
764
836
|
compass = false,
|
|
837
|
+
recenter = false,
|
|
765
838
|
className,
|
|
766
839
|
onLocate,
|
|
767
840
|
}: MapControlsProps) {
|
|
768
841
|
const slots = mapVariants({ position });
|
|
769
|
-
const { mapRef, cameraRef } = useMap();
|
|
842
|
+
const { mapRef, cameraRef, recenter: returnToStart } = useMap();
|
|
843
|
+
// Every glyph takes the theme's icon colour from here rather than from its
|
|
844
|
+
// own default, which was chosen for wherever that icon first appeared — the
|
|
845
|
+
// minus sign's is white, for a checked checkbox, and vanished on the card.
|
|
846
|
+
const iconTint = useToken('--color-muted-foreground', '#737373');
|
|
770
847
|
|
|
771
848
|
// Read the live zoom rather than tracking it in state: the map is moved by
|
|
772
849
|
// gestures too, and a counter kept alongside it drifts on the first pinch.
|
|
@@ -816,33 +893,42 @@ function MapControls({
|
|
|
816
893
|
}, [mapRef, cameraRef]);
|
|
817
894
|
|
|
818
895
|
return (
|
|
819
|
-
<
|
|
820
|
-
{
|
|
821
|
-
|
|
822
|
-
<
|
|
823
|
-
<
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
<
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
<
|
|
834
|
-
<
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
<
|
|
841
|
-
<
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
896
|
+
<IconColorProvider color={iconTint}>
|
|
897
|
+
<View className={slots.controls({ className })}>
|
|
898
|
+
{zoom ? (
|
|
899
|
+
<View className={slots.group()}>
|
|
900
|
+
<ControlButton label="Zoom in" onPress={() => nudgeZoom(1)}>
|
|
901
|
+
<PlusIcon size={16} />
|
|
902
|
+
</ControlButton>
|
|
903
|
+
<View className="h-px bg-border" />
|
|
904
|
+
<ControlButton label="Zoom out" onPress={() => nudgeZoom(-1)}>
|
|
905
|
+
<MinusIcon size={16} strokeWidth={2} />
|
|
906
|
+
</ControlButton>
|
|
907
|
+
</View>
|
|
908
|
+
) : null}
|
|
909
|
+
{recenter ? (
|
|
910
|
+
<View className={slots.group()}>
|
|
911
|
+
<ControlButton label="Recenter map" onPress={() => returnToStart()}>
|
|
912
|
+
<MaximizeIcon size={16} />
|
|
913
|
+
</ControlButton>
|
|
914
|
+
</View>
|
|
915
|
+
) : null}
|
|
916
|
+
{compass ? (
|
|
917
|
+
<View className={slots.group()}>
|
|
918
|
+
<ControlButton label="Face north" onPress={resetNorth}>
|
|
919
|
+
<CompassIcon size={16} />
|
|
920
|
+
</ControlButton>
|
|
921
|
+
</View>
|
|
922
|
+
) : null}
|
|
923
|
+
{locate ? (
|
|
924
|
+
<View className={slots.group()}>
|
|
925
|
+
<ControlButton label="Show my location" onPress={handleLocate}>
|
|
926
|
+
<CrosshairIcon size={16} />
|
|
927
|
+
</ControlButton>
|
|
928
|
+
</View>
|
|
929
|
+
) : null}
|
|
930
|
+
</View>
|
|
931
|
+
</IconColorProvider>
|
|
846
932
|
);
|
|
847
933
|
}
|
|
848
934
|
MapControls.displayName = 'Map.Controls';
|
package/src/index.ts
CHANGED
|
@@ -340,6 +340,7 @@ export {
|
|
|
340
340
|
CARTO_SOURCE,
|
|
341
341
|
type MapProps,
|
|
342
342
|
type MapHandle,
|
|
343
|
+
type MapFitBoundsOptions,
|
|
343
344
|
type MapMarkerProps,
|
|
344
345
|
type MapLabelProps,
|
|
345
346
|
type MapPopupProps,
|
|
@@ -356,6 +357,7 @@ export {
|
|
|
356
357
|
type BasemapTokens,
|
|
357
358
|
type LngLat,
|
|
358
359
|
type LngLatBounds,
|
|
360
|
+
type ViewPadding,
|
|
359
361
|
type ViewState,
|
|
360
362
|
} from './components/map';
|
|
361
363
|
export {
|
|
@@ -870,6 +872,14 @@ export {
|
|
|
870
872
|
type KpiGoodDirection,
|
|
871
873
|
type KpiTone,
|
|
872
874
|
} from './components/kpi';
|
|
875
|
+
export {
|
|
876
|
+
Leaderboard,
|
|
877
|
+
rankEntries,
|
|
878
|
+
type LeaderboardEntry,
|
|
879
|
+
type LeaderboardPodium,
|
|
880
|
+
type LeaderboardProps,
|
|
881
|
+
type LeaderboardRanked,
|
|
882
|
+
} from './components/leaderboard';
|
|
873
883
|
export { Label, type LabelProps, type LabelTextProps } from './components/label';
|
|
874
884
|
export {
|
|
875
885
|
LineChart,
|