panelui-native 0.38.0 → 0.41.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.
Files changed (67) hide show
  1. package/README.md +3 -1
  2. package/lib/module/components/card/index.js +172 -3
  3. package/lib/module/components/card/index.js.map +1 -1
  4. package/lib/module/components/drawer/index.js +13 -4
  5. package/lib/module/components/drawer/index.js.map +1 -1
  6. package/lib/module/components/heatmap-chart/index.js +113 -6
  7. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  8. package/lib/module/components/map/index.js +72 -12
  9. package/lib/module/components/map/index.js.map +1 -1
  10. package/lib/module/components/pagination/index.js +529 -0
  11. package/lib/module/components/pagination/index.js.map +1 -0
  12. package/lib/module/components/panelside/index.js +68 -18
  13. package/lib/module/components/panelside/index.js.map +1 -1
  14. package/lib/module/components/ring-chart/index.js +183 -45
  15. package/lib/module/components/ring-chart/index.js.map +1 -1
  16. package/lib/module/components/steps/index.js +8 -6
  17. package/lib/module/components/steps/index.js.map +1 -1
  18. package/lib/module/components/table/index.js +100 -26
  19. package/lib/module/components/table/index.js.map +1 -1
  20. package/lib/module/components/text-animation/index.js +857 -0
  21. package/lib/module/components/text-animation/index.js.map +1 -0
  22. package/lib/module/components/timeline/index.js +8 -7
  23. package/lib/module/components/timeline/index.js.map +1 -1
  24. package/lib/module/icons/index.js +25 -7
  25. package/lib/module/icons/index.js.map +1 -1
  26. package/lib/module/index.js +2 -0
  27. package/lib/module/index.js.map +1 -1
  28. package/lib/typescript/src/components/card/index.d.ts +20 -0
  29. package/lib/typescript/src/components/card/index.d.ts.map +1 -1
  30. package/lib/typescript/src/components/drawer/index.d.ts +13 -1
  31. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  32. package/lib/typescript/src/components/heatmap-chart/index.d.ts +51 -3
  33. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  34. package/lib/typescript/src/components/map/index.d.ts +37 -2
  35. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  36. package/lib/typescript/src/components/pagination/index.d.ts +285 -0
  37. package/lib/typescript/src/components/pagination/index.d.ts.map +1 -0
  38. package/lib/typescript/src/components/panelside/index.d.ts +50 -4
  39. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  40. package/lib/typescript/src/components/ring-chart/index.d.ts +75 -8
  41. package/lib/typescript/src/components/ring-chart/index.d.ts.map +1 -1
  42. package/lib/typescript/src/components/steps/index.d.ts +8 -6
  43. package/lib/typescript/src/components/steps/index.d.ts.map +1 -1
  44. package/lib/typescript/src/components/table/index.d.ts +59 -17
  45. package/lib/typescript/src/components/table/index.d.ts.map +1 -1
  46. package/lib/typescript/src/components/text-animation/index.d.ts +268 -0
  47. package/lib/typescript/src/components/text-animation/index.d.ts.map +1 -0
  48. package/lib/typescript/src/components/timeline/index.d.ts +8 -7
  49. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  50. package/lib/typescript/src/icons/index.d.ts +6 -0
  51. package/lib/typescript/src/icons/index.d.ts.map +1 -1
  52. package/lib/typescript/src/index.d.ts +6 -4
  53. package/lib/typescript/src/index.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/src/components/card/index.tsx +227 -2
  56. package/src/components/drawer/index.tsx +31 -9
  57. package/src/components/heatmap-chart/index.tsx +147 -5
  58. package/src/components/map/index.tsx +158 -14
  59. package/src/components/pagination/index.tsx +603 -0
  60. package/src/components/panelside/index.tsx +134 -19
  61. package/src/components/ring-chart/index.tsx +269 -47
  62. package/src/components/steps/index.tsx +8 -6
  63. package/src/components/table/index.tsx +146 -30
  64. package/src/components/text-animation/index.tsx +1093 -0
  65. package/src/components/timeline/index.tsx +8 -7
  66. package/src/icons/index.tsx +23 -4
  67. package/src/index.ts +28 -1
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * ```tsx
13
13
  * <HeatmapChart data={weeks}>
14
+ * <HeatmapChart.Header title="Contributions" value="1,204" legend />
14
15
  * <HeatmapChart.YAxis />
15
16
  * <HeatmapChart.XAxis />
16
17
  * <HeatmapChart.Cells />
@@ -132,7 +133,7 @@ const MONTHS = [
132
133
  * composition stays a flat list of children instead of four nested slots the
133
134
  * caller has to remember the order of.
134
135
  */
135
- type Slot = 'cells' | 'x-axis' | 'y-axis' | 'rules' | 'tooltip' | 'legend';
136
+ type Slot = 'cells' | 'x-axis' | 'y-axis' | 'rules' | 'tooltip' | 'legend' | 'header';
136
137
 
137
138
  export type HeatmapLayout = 'fluid' | 'fill';
138
139
 
@@ -566,6 +567,10 @@ const HeatmapChartRoot = forwardRef<View, HeatmapChartProps>(function HeatmapCha
566
567
  onLayout={onLayout}
567
568
  className={cn('w-full', className)}
568
569
  >
570
+ {/* Above the axis gutter as well as the grid: the header is about the
571
+ whole chart, so it starts at the chart's edge, not the grid's. */}
572
+ {parts.header}
573
+
569
574
  {parts.x ? (
570
575
  <View style={{ paddingLeft: axisWidth }}>{parts.x}</View>
571
576
  ) : null}
@@ -605,6 +610,7 @@ interface Parts {
605
610
  y: React.ReactElement<{ width?: number }> | null;
606
611
  tooltip: ReactNode[];
607
612
  legend: ReactNode[];
613
+ header: ReactNode[];
608
614
  columns: number;
609
615
  }
610
616
 
@@ -617,6 +623,7 @@ function splitParts(children: ReactNode): Parts {
617
623
  y: null,
618
624
  tooltip: [],
619
625
  legend: [],
626
+ header: [],
620
627
  columns: 0,
621
628
  };
622
629
 
@@ -630,6 +637,7 @@ function splitParts(children: ReactNode): Parts {
630
637
  else if (slot === 'rules') parts.rules.push(keyed);
631
638
  else if (slot === 'tooltip') parts.tooltip.push(keyed);
632
639
  else if (slot === 'legend') parts.legend.push(keyed);
640
+ else if (slot === 'header') parts.header.push(keyed);
633
641
  else parts.cells.push(keyed);
634
642
  });
635
643
 
@@ -753,6 +761,12 @@ export interface HeatmapXAxisProps {
753
761
  * derived. Return an empty string to leave the column unlabelled.
754
762
  */
755
763
  formatLabel?: (date: Date, column: number) => string;
764
+ /**
765
+ * Column labels, left to right. Overrides the month names — for a grid whose
766
+ * columns are not weeks, where there is no month to change and so nothing to
767
+ * emit a label on.
768
+ */
769
+ labels?: string[];
756
770
  }
757
771
 
758
772
  /**
@@ -760,13 +774,21 @@ export interface HeatmapXAxisProps {
760
774
  *
761
775
  * A label is emitted where the month changes rather than at a fixed interval,
762
776
  * because months are not the same length — spacing them evenly puts "Mar" over
763
- * a week in February.
777
+ * a week in February. A grid whose columns are not weeks has no such signal, so
778
+ * it passes `labels` and gets one over every column.
764
779
  */
765
- function HeatmapXAxis({ className, formatLabel }: HeatmapXAxisProps) {
780
+ function HeatmapXAxis({ className, formatLabel, labels: given }: HeatmapXAxisProps) {
766
781
  const { data, grid } = useHeatmap('HeatmapChart.XAxis');
767
782
  const step = grid.size + grid.gap;
768
783
 
769
784
  const labels = useMemo(() => {
785
+ if (given) {
786
+ return given
787
+ .slice(0, data.length)
788
+ .map((label, column) => ({ column, label }))
789
+ .filter((entry) => entry.label);
790
+ }
791
+
770
792
  const out: { column: number; label: string }[] = [];
771
793
  let lastMonth = -1;
772
794
 
@@ -784,7 +806,7 @@ function HeatmapXAxis({ className, formatLabel }: HeatmapXAxisProps) {
784
806
  // collides with the next one. Drop it when it has no room.
785
807
  if (out.length > 1 && out[1]!.column - out[0]!.column < 3) out.shift();
786
808
  return out;
787
- }, [data, formatLabel]);
809
+ }, [data, formatLabel, given]);
788
810
 
789
811
  return (
790
812
  <View className={cn('h-4', className)} style={{ width: grid.width }}>
@@ -1002,9 +1024,16 @@ function HeatmapTooltip({
1002
1024
  }
1003
1025
  HeatmapTooltip.slot = 'tooltip' as Slot;
1004
1026
 
1027
+ /*
1028
+ * A cell with no date is not a cell with no data — it is a cell in a grid that
1029
+ * is not a calendar, and it has a count like any other. Saying "No data" over
1030
+ * it reports a hole in the data that is not there. Without a date there is
1031
+ * simply nothing to say after the number, so the number is the whole label,
1032
+ * and a grid that wants a noun for it passes `formatLabel`.
1033
+ */
1005
1034
  function defaultTooltipLabel(cell: HeatmapCell) {
1035
+ if (!cell.date) return `${cell.count}`;
1006
1036
  const count = `${cell.count} ${cell.count === 1 ? 'contribution' : 'contributions'}`;
1007
- if (!cell.date) return 'No data';
1008
1037
  const date = `${MONTHS[cell.date.getMonth()]} ${cell.date.getDate()}`;
1009
1038
  return `${count} on ${date}`;
1010
1039
  }
@@ -1061,7 +1090,120 @@ function HeatmapLegend({
1061
1090
  }
1062
1091
  HeatmapLegend.slot = 'legend' as Slot;
1063
1092
 
1093
+ /* -------------------------------------------------------------------------- */
1094
+ /* Header */
1095
+ /* -------------------------------------------------------------------------- */
1096
+
1097
+ export interface HeatmapHeaderProps extends ViewProps {
1098
+ className?: string;
1099
+ /** Small line above the value — what the grid is of. */
1100
+ title?: string;
1101
+ /** The readout. The largest thing on the card, and the first thing read. */
1102
+ value?: string;
1103
+ /** One muted line under the value — a period, a total, the held cell. */
1104
+ caption?: string;
1105
+ /**
1106
+ * Draw the ramp along the trailing edge, `Less ▢▢▢▢▢ More`. The key for a
1107
+ * grid that scrolls sideways, where `HeatmapChart.Legend` under the cells
1108
+ * would scroll away with them.
1109
+ */
1110
+ legend?: boolean;
1111
+ /** Text at the low end of the ramp, when `legend` is set. */
1112
+ lessLabel?: string;
1113
+ /** Text at the high end. */
1114
+ moreLabel?: string;
1115
+ /** Trailing slot — a control, a badge, a range picker. Wins over `legend`. */
1116
+ children?: ReactNode;
1117
+ }
1118
+
1119
+ /**
1120
+ * The strip above the grid: what the chart is of, what it currently reads, and
1121
+ * what the shading means.
1122
+ *
1123
+ * It belongs to the chart rather than to the card around it because it is about
1124
+ * the *grid* — the number changes as a finger moves across the cells, and the
1125
+ * ramp is the scale the chart itself derived. The card's header is a caption on
1126
+ * the tray the chart sits in; this is the chart introducing itself.
1127
+ *
1128
+ * The value is not derived here. Take it from `onActiveCellChange` and pass the
1129
+ * formatted string down, so one header can show a total when nothing is held
1130
+ * and a day's own count when something is.
1131
+ */
1132
+ function HeatmapHeader({
1133
+ className,
1134
+ title,
1135
+ value,
1136
+ caption,
1137
+ legend = false,
1138
+ lessLabel = 'Less',
1139
+ moreLabel = 'More',
1140
+ children,
1141
+ ...props
1142
+ }: HeatmapHeaderProps) {
1143
+ const { ramp, opacities, grid, cornerRadius } = useHeatmap('HeatmapChart.Header');
1144
+ // Small enough to sit on one line beside the text, whatever the cells are.
1145
+ const size = Math.max(Math.min(grid.size, 12), 8);
1146
+
1147
+ const trailing =
1148
+ children ??
1149
+ (legend ? (
1150
+ <View className="flex-row items-center gap-1.5">
1151
+ <Text size="xs" muted>
1152
+ {lessLabel}
1153
+ </Text>
1154
+ {ramp.map((fill, level) => (
1155
+ <Svg key={level} width={size} height={size}>
1156
+ <Rect
1157
+ x={0}
1158
+ y={0}
1159
+ width={size}
1160
+ height={size}
1161
+ rx={cornerRadius}
1162
+ ry={cornerRadius}
1163
+ fill={fill}
1164
+ fillOpacity={opacities[level]}
1165
+ />
1166
+ </Svg>
1167
+ ))}
1168
+ <Text size="xs" muted>
1169
+ {moreLabel}
1170
+ </Text>
1171
+ </View>
1172
+ ) : null);
1173
+
1174
+ return (
1175
+ <View
1176
+ {...props}
1177
+ className={cn('flex-row items-start justify-between gap-3 pb-3', className)}
1178
+ >
1179
+ <View className="flex-1 gap-0.5">
1180
+ {title ? (
1181
+ <Text size="xs" muted>
1182
+ {title}
1183
+ </Text>
1184
+ ) : null}
1185
+ {value ? (
1186
+ <Text size="xl" weight="bold">
1187
+ {value}
1188
+ </Text>
1189
+ ) : null}
1190
+ {caption ? (
1191
+ <Text size="xs" muted>
1192
+ {caption}
1193
+ </Text>
1194
+ ) : null}
1195
+ </View>
1196
+ {/* Shrinkable, unlike a view's default in React Native — held rigid, the
1197
+ ramp takes the width it wants and the caption wraps around it. */}
1198
+ {trailing ? <View className="shrink pt-1">{trailing}</View> : null}
1199
+ </View>
1200
+ );
1201
+ }
1202
+ HeatmapHeader.displayName = 'HeatmapChart.Header';
1203
+ HeatmapHeader.slot = 'header' as Slot;
1204
+
1064
1205
  export const HeatmapChart = Object.assign(HeatmapChartRoot, {
1206
+ Header: HeatmapHeader,
1065
1207
  Cells: HeatmapCells,
1066
1208
  Separator: HeatmapSeparator,
1067
1209
  XAxis: HeatmapXAxis,
@@ -128,6 +128,43 @@ function useToken(name: string, fallback: string): string {
128
128
  return typeof value === 'string' && value.length > 0 ? value : fallback;
129
129
  }
130
130
 
131
+ /** True for a theme token name, as opposed to a literal colour. */
132
+ function isToken(value: string | undefined): value is string {
133
+ return typeof value === 'string' && value.startsWith('--');
134
+ }
135
+
136
+ /**
137
+ * The same colour at a given alpha, for a ramp built from one hue.
138
+ *
139
+ * Style expressions are handed to the renderer as strings, so this produces
140
+ * `rgba()` rather than reaching for a colour type the layer would not accept.
141
+ */
142
+ function withAlpha(color: string, alpha: number): string {
143
+ if (color.startsWith('#')) {
144
+ const hex = color.slice(1);
145
+ const full =
146
+ hex.length === 3
147
+ ? hex
148
+ .split('')
149
+ .map((c) => c + c)
150
+ .join('')
151
+ : hex.slice(0, 6);
152
+ const r = parseInt(full.slice(0, 2), 16);
153
+ const g = parseInt(full.slice(2, 4), 16);
154
+ const b = parseInt(full.slice(4, 6), 16);
155
+ if (Number.isNaN(r + g + b)) return color;
156
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
157
+ }
158
+
159
+ const channels = color.match(/rgba?\(([^)]+)\)/)?.[1];
160
+ if (channels) {
161
+ const [r, g, b] = channels.split(',').map((part) => part.trim());
162
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
163
+ }
164
+
165
+ return color;
166
+ }
167
+
131
168
  interface MapContextValue {
132
169
  mapRef: React.RefObject<MapRef | null>;
133
170
  cameraRef: React.RefObject<CameraRef | null>;
@@ -1025,6 +1062,28 @@ export interface MapHeatmapProps {
1025
1062
  data: unknown;
1026
1063
  /** Feature property to weight each point by. Unweighted when omitted. */
1027
1064
  weight?: string;
1065
+ /**
1066
+ * Base colour of the field — a theme token by name, or a literal. The ramp
1067
+ * is this colour at rising opacity, so density reads as *more of the same
1068
+ * thing* rather than as a change of subject.
1069
+ *
1070
+ * Defaults to `--color-chart-2`, which is a saturated accent in every theme.
1071
+ * It is deliberately not `--color-chart-1`: that is the series colour a chart
1072
+ * is about, and every theme starts it on something close to the foreground —
1073
+ * near-black in a light theme, near-white in a dark one — which over a
1074
+ * basemap is a smudge rather than a measurement.
1075
+ */
1076
+ color?: string;
1077
+ /**
1078
+ * Replace the derived ramp outright, coolest first. For the conventional
1079
+ * heat ramp, where the hue carries the reading as well as the opacity —
1080
+ * worth it when the field sits over varied terrain and one hue at five
1081
+ * opacities stops being separable from what is underneath it.
1082
+ *
1083
+ * The first stop is drawn at the lowest density, the last at the highest.
1084
+ * Density zero stays fully transparent either way.
1085
+ */
1086
+ colors?: string[];
1028
1087
  /**
1029
1088
  * Spread of a single point, in points, at street zoom. Larger blurs more.
1030
1089
  * The drawn radius shrinks as the map zooms out, so a point keeps covering
@@ -1037,6 +1096,12 @@ export interface MapHeatmapProps {
1037
1096
  opacity?: number;
1038
1097
  /** Above this zoom the layer fades out — see the note on the component. */
1039
1098
  maxZoom?: number;
1099
+ /**
1100
+ * Draw the points themselves as the field fades out, coloured from the same
1101
+ * ramp by weight. Without them, zooming past `maxZoom` leaves an empty map:
1102
+ * the layer gets out of the way, and nothing takes its place.
1103
+ */
1104
+ points?: boolean;
1040
1105
  id?: string;
1041
1106
  }
1042
1107
 
@@ -1050,30 +1115,63 @@ export interface MapHeatmapProps {
1050
1115
  *
1051
1116
  * It fades out past `maxZoom` on purpose. Zoomed far enough in, every point is
1052
1117
  * its own island and the blur says less than the points would — so the layer
1053
- * gets out of the way rather than smearing five records across a street.
1118
+ * gets out of the way rather than smearing five records across a street. Set
1119
+ * `points` and the records themselves fade in as it goes, which is the whole
1120
+ * handover: a field while the question is where, marks once it is which.
1054
1121
  *
1055
1122
  * The radius is tied to zoom for the mirror-image reason. Left as a fixed
1056
1123
  * number of screen points it would mean a different distance at every zoom:
1057
1124
  * a blur that reads as a city at street level covers half a continent once
1058
1125
  * the map is pulled out, and a field measured over land ends up sitting in
1059
1126
  * the sea. Scaling it with the projection keeps the claim the same one.
1127
+ *
1128
+ * Intensity is tied to zoom too, and in the other direction. The same points
1129
+ * are packed into fewer pixels the further out the map goes, so a constant
1130
+ * intensity saturates the whole field at world zoom and shows nothing but its
1131
+ * own ceiling.
1060
1132
  */
1061
1133
  function MapHeatmap({
1062
1134
  data,
1063
1135
  weight,
1136
+ color,
1137
+ colors,
1064
1138
  radius = 24,
1065
1139
  intensity = 1,
1066
1140
  opacity = 0.85,
1067
1141
  maxZoom = 15,
1142
+ points = false,
1068
1143
  id = 'heatmap',
1069
1144
  }: MapHeatmapProps) {
1070
- const cool = useToken('--color-muted', 'rgba(0,0,0,0.06)');
1071
- const warm = useToken('--color-chart-1', '#262626');
1072
- const hot = useToken('--color-chart-2', '#666666');
1145
+ const base = useToken(isToken(color) ? color : '--color-chart-2', '#3b82f6');
1146
+ const resolved = (isToken(color) ? base : color) ?? base;
1147
+
1148
+ /*
1149
+ * One colour at rising opacity rather than five colours, which is how every
1150
+ * other ramp in the library is built: a sequential scale is one quantity
1151
+ * getting larger, and five hues say five categories. `colors` is there for
1152
+ * the case where that is not enough.
1153
+ */
1154
+ const ramp = colors?.length
1155
+ ? colors
1156
+ : [
1157
+ withAlpha(resolved, 0.25),
1158
+ withAlpha(resolved, 0.45),
1159
+ withAlpha(resolved, 0.7),
1160
+ withAlpha(resolved, 0.9),
1161
+ resolved,
1162
+ ];
1073
1163
 
1074
1164
  if (!MapLibre) return null;
1075
1165
  const { GeoJSONSource, Layer } = MapLibre;
1076
1166
 
1167
+ // Density zero is transparent rather than the coolest colour, so the map
1168
+ // shows through everywhere nothing was measured instead of the whole
1169
+ // viewport being tinted by the absence of data.
1170
+ const stops = ramp.flatMap((entry, index) => [
1171
+ 0.15 + (0.85 * index) / Math.max(1, ramp.length - 1),
1172
+ entry,
1173
+ ]);
1174
+
1077
1175
  return (
1078
1176
  <GeoJSONSource id={`${id}-source`} data={data}>
1079
1177
  <Layer
@@ -1084,7 +1182,15 @@ function MapHeatmap({
1084
1182
  heatmapWeight: weight
1085
1183
  ? ['interpolate', ['linear'], ['get', weight], 0, 0, 1, 1]
1086
1184
  : 1,
1087
- heatmapIntensity: intensity,
1185
+ heatmapIntensity: [
1186
+ 'interpolate',
1187
+ ['linear'],
1188
+ ['zoom'],
1189
+ 0,
1190
+ intensity * 0.6,
1191
+ maxZoom,
1192
+ intensity * 1.2,
1193
+ ],
1088
1194
  // `radius` is the spread at street zoom; below that it is scaled
1089
1195
  // down so it keeps covering the same ground rather than the same
1090
1196
  // number of pixels. The stops are the projection: each zoom level
@@ -1101,21 +1207,13 @@ function MapHeatmap({
1101
1207
  14,
1102
1208
  radius,
1103
1209
  ],
1104
- // Transparent at zero rather than the coolest colour, so the map
1105
- // shows through everywhere nothing was measured instead of the whole
1106
- // viewport being tinted by the absence of data.
1107
1210
  heatmapColor: [
1108
1211
  'interpolate',
1109
1212
  ['linear'],
1110
1213
  ['heatmap-density'],
1111
1214
  0,
1112
1215
  'rgba(0,0,0,0)',
1113
- 0.2,
1114
- cool,
1115
- 0.6,
1116
- warm,
1117
- 1,
1118
- hot,
1216
+ ...stops,
1119
1217
  ],
1120
1218
  heatmapOpacity: [
1121
1219
  'interpolate',
@@ -1128,6 +1226,52 @@ function MapHeatmap({
1128
1226
  ],
1129
1227
  }}
1130
1228
  />
1229
+
1230
+ {/* The handover, in the two zoom levels the field spends fading: the
1231
+ marks arrive as it leaves, so there is never a zoom with neither. */}
1232
+ {points ? (
1233
+ <Layer
1234
+ id={`${id}-points`}
1235
+ type="circle"
1236
+ minzoom={maxZoom - 2}
1237
+ style={{
1238
+ circleRadius: weight
1239
+ ? ['interpolate', ['linear'], ['get', weight], 0, 3, 1, 9]
1240
+ : 5,
1241
+ circleColor: weight
1242
+ ? [
1243
+ 'interpolate',
1244
+ ['linear'],
1245
+ ['get', weight],
1246
+ 0,
1247
+ ramp[0] as string,
1248
+ 1,
1249
+ ramp[ramp.length - 1] as string,
1250
+ ]
1251
+ : (ramp[ramp.length - 1] as string),
1252
+ circleStrokeWidth: 1,
1253
+ circleStrokeColor: 'rgba(255,255,255,0.75)',
1254
+ circleOpacity: [
1255
+ 'interpolate',
1256
+ ['linear'],
1257
+ ['zoom'],
1258
+ maxZoom - 2,
1259
+ 0,
1260
+ maxZoom,
1261
+ 0.85,
1262
+ ],
1263
+ circleStrokeOpacity: [
1264
+ 'interpolate',
1265
+ ['linear'],
1266
+ ['zoom'],
1267
+ maxZoom - 2,
1268
+ 0,
1269
+ maxZoom,
1270
+ 0.6,
1271
+ ],
1272
+ }}
1273
+ />
1274
+ ) : null}
1131
1275
  </GeoJSONSource>
1132
1276
  );
1133
1277
  }