panelui-native 0.103.1 → 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/checkbox/index.js +8 -2
- package/lib/module/components/checkbox/index.js.map +1 -1
- 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/components/radio-group/index.js +11 -3
- package/lib/module/components/radio-group/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/checkbox/index.d.ts +9 -0
- package/lib/typescript/src/components/checkbox/index.d.ts.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/components/radio-group/index.d.ts +9 -0
- package/lib/typescript/src/components/radio-group/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/checkbox/index.tsx +17 -2
- 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/components/radio-group/index.tsx +27 -4
- 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';
|
|
@@ -162,11 +162,20 @@ const RadioGroupRoot = forwardRef<View, RadioGroupProps>(
|
|
|
162
162
|
RadioGroupRoot.displayName = 'RadioGroup';
|
|
163
163
|
|
|
164
164
|
export interface RadioGroupItemProps {
|
|
165
|
+
/**
|
|
166
|
+
* Styles the pressable row — layout, border, fill. It does not reach the
|
|
167
|
+
* label: text colour is not inherited from a view in React Native, so use
|
|
168
|
+
* `labelClassName` for that.
|
|
169
|
+
*/
|
|
165
170
|
className?: string;
|
|
166
171
|
value: string;
|
|
167
172
|
label?: string;
|
|
173
|
+
/** Classes for the label text, merged over the defaults — e.g. `text-black`. */
|
|
174
|
+
labelClassName?: string;
|
|
168
175
|
/** Secondary line under the label. Most at home in the `card` variant. */
|
|
169
176
|
description?: string;
|
|
177
|
+
/** Classes for the description text, merged over the defaults. */
|
|
178
|
+
descriptionClassName?: string;
|
|
170
179
|
disabled?: boolean;
|
|
171
180
|
/** Hide the disc entirely — for a card whose selected fill is enough. */
|
|
172
181
|
hideIndicator?: boolean;
|
|
@@ -175,7 +184,17 @@ export interface RadioGroupItemProps {
|
|
|
175
184
|
|
|
176
185
|
const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
177
186
|
(
|
|
178
|
-
{
|
|
187
|
+
{
|
|
188
|
+
className,
|
|
189
|
+
value,
|
|
190
|
+
label,
|
|
191
|
+
labelClassName,
|
|
192
|
+
description,
|
|
193
|
+
descriptionClassName,
|
|
194
|
+
disabled: itemDisabled,
|
|
195
|
+
hideIndicator,
|
|
196
|
+
children,
|
|
197
|
+
},
|
|
179
198
|
ref
|
|
180
199
|
) => {
|
|
181
200
|
const context = useContext(RadioGroupContext);
|
|
@@ -216,9 +235,11 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
216
235
|
// so children given instead of the prop are dressed the same way rather
|
|
217
236
|
// than reaching the pressable bare.
|
|
218
237
|
const labelled = label ? (
|
|
219
|
-
<Text className={slots.label()}>{label}</Text>
|
|
238
|
+
<Text className={slots.label({ className: labelClassName })}>{label}</Text>
|
|
220
239
|
) : (
|
|
221
|
-
textChildren(children, (text) =>
|
|
240
|
+
textChildren(children, (text) => (
|
|
241
|
+
<Text className={slots.label({ className: labelClassName })}>{text}</Text>
|
|
242
|
+
))
|
|
222
243
|
);
|
|
223
244
|
|
|
224
245
|
// The card lays its text out first so the disc sits at the trailing edge;
|
|
@@ -229,7 +250,9 @@ const RadioGroupItem = forwardRef<View, RadioGroupItemProps>(
|
|
|
229
250
|
<View className="flex-1 gap-1">
|
|
230
251
|
{labelled}
|
|
231
252
|
{description ? (
|
|
232
|
-
<Text className={slots.description()}>
|
|
253
|
+
<Text className={slots.description({ className: descriptionClassName })}>
|
|
254
|
+
{description}
|
|
255
|
+
</Text>
|
|
233
256
|
) : null}
|
|
234
257
|
</View>
|
|
235
258
|
{indicator}
|
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,
|