panelui-native 0.30.0 → 0.32.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 +4 -2
- package/lib/module/components/calendar/index.js +107 -24
- package/lib/module/components/calendar/index.js.map +1 -1
- package/lib/module/components/heatmap-chart/index.js +9 -8
- package/lib/module/components/heatmap-chart/index.js.map +1 -1
- package/lib/module/components/map/index.js +50 -4
- package/lib/module/components/map/index.js.map +1 -1
- package/lib/module/components/plan/index.js +307 -21
- package/lib/module/components/plan/index.js.map +1 -1
- package/lib/module/components/post/index.js +992 -0
- package/lib/module/components/post/index.js.map +1 -0
- package/lib/module/components/response/index.js +296 -0
- package/lib/module/components/response/index.js.map +1 -0
- package/lib/module/components/response/markdown.js +463 -0
- package/lib/module/components/response/markdown.js.map +1 -0
- package/lib/module/components/select/index.js +97 -15
- package/lib/module/components/select/index.js.map +1 -1
- package/lib/module/components/table/index.js +632 -0
- package/lib/module/components/table/index.js.map +1 -0
- package/lib/module/icons/index.js +200 -0
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/index.js +4 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/date.js +129 -22
- package/lib/module/utils/date.js.map +1 -1
- package/lib/typescript/src/components/calendar/index.d.ts +12 -3
- package/lib/typescript/src/components/calendar/index.d.ts.map +1 -1
- package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
- package/lib/typescript/src/components/map/index.d.ts +8 -1
- package/lib/typescript/src/components/map/index.d.ts.map +1 -1
- package/lib/typescript/src/components/plan/index.d.ts +121 -10
- package/lib/typescript/src/components/plan/index.d.ts.map +1 -1
- package/lib/typescript/src/components/post/index.d.ts +358 -0
- package/lib/typescript/src/components/post/index.d.ts.map +1 -0
- package/lib/typescript/src/components/response/index.d.ts +91 -0
- package/lib/typescript/src/components/response/index.d.ts.map +1 -0
- package/lib/typescript/src/components/response/markdown.d.ts +88 -0
- package/lib/typescript/src/components/response/markdown.d.ts.map +1 -0
- package/lib/typescript/src/components/select/index.d.ts +27 -2
- package/lib/typescript/src/components/select/index.d.ts.map +1 -1
- package/lib/typescript/src/components/table/index.d.ts +274 -0
- package/lib/typescript/src/components/table/index.d.ts.map +1 -0
- package/lib/typescript/src/icons/index.d.ts +40 -1
- package/lib/typescript/src/icons/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/date.d.ts +49 -5
- package/lib/typescript/src/utils/date.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/calendar/index.tsx +136 -23
- package/src/components/heatmap-chart/index.tsx +9 -9
- package/src/components/map/index.tsx +53 -4
- package/src/components/plan/index.tsx +344 -21
- package/src/components/post/index.tsx +1116 -0
- package/src/components/response/index.tsx +390 -0
- package/src/components/response/markdown.ts +489 -0
- package/src/components/select/index.tsx +132 -12
- package/src/components/table/index.tsx +747 -0
- package/src/icons/index.tsx +162 -1
- package/src/index.ts +60 -0
- package/src/utils/date.ts +146 -22
|
@@ -39,11 +39,12 @@ import {
|
|
|
39
39
|
createContext,
|
|
40
40
|
useCallback,
|
|
41
41
|
useContext,
|
|
42
|
+
useEffect,
|
|
42
43
|
useMemo,
|
|
43
44
|
useRef,
|
|
44
45
|
useState,
|
|
45
46
|
} from 'react';
|
|
46
|
-
import { Pressable, ScrollView, View, type ViewProps } from 'react-native';
|
|
47
|
+
import { AppState, Pressable, ScrollView, View, type ViewProps } from 'react-native';
|
|
47
48
|
import { tv } from 'tailwind-variants';
|
|
48
49
|
import { useCSSVariable } from 'uniwind';
|
|
49
50
|
import {
|
|
@@ -57,17 +58,20 @@ import { cn } from '../../utils/cn';
|
|
|
57
58
|
import { Popover } from '../popover';
|
|
58
59
|
import {
|
|
59
60
|
addCalendarMonths,
|
|
60
|
-
addDays,
|
|
61
61
|
calendarDayNumber,
|
|
62
62
|
calendarLongDate,
|
|
63
63
|
calendarMonthLabel,
|
|
64
64
|
calendarMonthNames,
|
|
65
65
|
calendarParts,
|
|
66
|
+
clampDate,
|
|
66
67
|
isAfter,
|
|
67
68
|
isBefore,
|
|
68
69
|
isSameCalendarMonth,
|
|
69
70
|
isSameDay,
|
|
70
71
|
isWithin,
|
|
72
|
+
localeWeekStart,
|
|
73
|
+
monthGrid,
|
|
74
|
+
normalizeWeekStart,
|
|
71
75
|
resolveCalendar,
|
|
72
76
|
startOfCalendarMonth,
|
|
73
77
|
startOfDay,
|
|
@@ -184,6 +188,35 @@ function useCalendar(component: string): CalendarContextValue {
|
|
|
184
188
|
return context;
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Today, kept current.
|
|
193
|
+
*
|
|
194
|
+
* "Today" is not a constant, and a calendar is exactly the kind of thing left
|
|
195
|
+
* on screen across the boundary that changes it — a picker open in a sheet, an
|
|
196
|
+
* app resumed the next morning. Recomputed when the app comes back to the
|
|
197
|
+
* foreground, which is when a stale answer would otherwise become visible, and
|
|
198
|
+
* held as state so the marker actually moves when it does.
|
|
199
|
+
*/
|
|
200
|
+
function useToday(): Date {
|
|
201
|
+
const [today, setToday] = useState(() => startOfDay(new Date()));
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
const refresh = () => {
|
|
205
|
+
const now = startOfDay(new Date());
|
|
206
|
+
// A new object every resume would invalidate every memo below it for a
|
|
207
|
+
// date that has not changed.
|
|
208
|
+
setToday((current) => (current.getTime() === now.getTime() ? current : now));
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const subscription = AppState.addEventListener('change', (state) => {
|
|
212
|
+
if (state === 'active') refresh();
|
|
213
|
+
});
|
|
214
|
+
return () => subscription.remove();
|
|
215
|
+
}, []);
|
|
216
|
+
|
|
217
|
+
return today;
|
|
218
|
+
}
|
|
219
|
+
|
|
187
220
|
/** Whether a rule, a list or a span rules this day out. */
|
|
188
221
|
function isDisabled(
|
|
189
222
|
date: Date,
|
|
@@ -245,8 +278,17 @@ export interface CalendarProps<Mode extends CalendarMode = 'single'>
|
|
|
245
278
|
startMonth?: Date;
|
|
246
279
|
/** Latest month the caption's pickers offer. Defaults to `maxDate`, else ten years on. */
|
|
247
280
|
endMonth?: Date;
|
|
248
|
-
/**
|
|
249
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Which day the week's first column is. `0` is Sunday.
|
|
283
|
+
*
|
|
284
|
+
* `auto` asks the locale, which is what most of the world wants — `en-GB` and
|
|
285
|
+
* `fr-FR` start on Monday, and a calendar that says otherwise is wrong in a
|
|
286
|
+
* way its reader notices immediately. It is not the default only because
|
|
287
|
+
* changing an existing calendar's columns out from under it is not something
|
|
288
|
+
* a version bump should do. Out-of-range numbers wrap rather than producing
|
|
289
|
+
* a blank column.
|
|
290
|
+
*/
|
|
291
|
+
weekStartsOn?: number | 'auto';
|
|
250
292
|
/** Draw the neighbouring months' days rather than leaving the cells blank. */
|
|
251
293
|
showOutsideDays?: boolean;
|
|
252
294
|
/**
|
|
@@ -291,7 +333,7 @@ function CalendarRoot<Mode extends CalendarMode = 'single'>({
|
|
|
291
333
|
maxDate,
|
|
292
334
|
startMonth,
|
|
293
335
|
endMonth,
|
|
294
|
-
weekStartsOn = 0,
|
|
336
|
+
weekStartsOn: weekStartsOnProp = 0,
|
|
295
337
|
showOutsideDays = true,
|
|
296
338
|
selectOutsideDays = false,
|
|
297
339
|
bordered = true,
|
|
@@ -302,6 +344,16 @@ function CalendarRoot<Mode extends CalendarMode = 'single'>({
|
|
|
302
344
|
// Settled once here rather than at each call site, so every part of the
|
|
303
345
|
// grid is certainly reading the same calendar.
|
|
304
346
|
const system = useMemo(() => resolveCalendar(calendar, locale), [calendar, locale]);
|
|
347
|
+
// Settled once too, and normalised: everything below indexes an array of
|
|
348
|
+
// seven with it, where a negative or out-of-range value is a blank column
|
|
349
|
+
// rather than an error anyone would trace back to here.
|
|
350
|
+
const weekStartsOn = useMemo(
|
|
351
|
+
() =>
|
|
352
|
+
weekStartsOnProp === 'auto'
|
|
353
|
+
? localeWeekStart(locale)
|
|
354
|
+
: normalizeWeekStart(weekStartsOnProp),
|
|
355
|
+
[weekStartsOnProp, locale]
|
|
356
|
+
);
|
|
305
357
|
const [internalSelected, setInternalSelected] = useState<CalendarSelection[Mode] | undefined>(
|
|
306
358
|
defaultSelected
|
|
307
359
|
);
|
|
@@ -328,15 +380,52 @@ function CalendarRoot<Mode extends CalendarMode = 'single'>({
|
|
|
328
380
|
}, []);
|
|
329
381
|
|
|
330
382
|
const [internalMonth, setInternalMonth] = useState(initialMonth);
|
|
331
|
-
|
|
383
|
+
/*
|
|
384
|
+
* Memoised on the *instant*, not on the prop. A controlled caller almost
|
|
385
|
+
* always passes a fresh `new Date(…)`, and deriving a new month object from
|
|
386
|
+
* it on every render invalidates the grid memo and the context memo below —
|
|
387
|
+
* so a calendar that has not moved rebuilds forty-two cells per keystroke
|
|
388
|
+
* somewhere else on the screen.
|
|
389
|
+
*/
|
|
390
|
+
const controlledMonth = useMemo(
|
|
391
|
+
() => (monthProp ? startOfCalendarMonth(monthProp, system, locale) : undefined),
|
|
392
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
393
|
+
[monthProp?.getTime(), system, locale]
|
|
394
|
+
);
|
|
395
|
+
const month = controlledMonth ?? internalMonth;
|
|
396
|
+
|
|
397
|
+
/*
|
|
398
|
+
* How far the calendar may be paged, as the first day of the outermost month
|
|
399
|
+
* either way. `startMonth`/`endMonth` win where they are given, because they
|
|
400
|
+
* are the props about *navigation*; `minDate`/`maxDate` are about which days
|
|
401
|
+
* can be picked, and stand in when the navigation bounds are not stated.
|
|
402
|
+
*/
|
|
403
|
+
const monthBounds = useMemo(() => {
|
|
404
|
+
const lower = startMonth ?? minDate;
|
|
405
|
+
const upper = endMonth ?? maxDate;
|
|
406
|
+
return {
|
|
407
|
+
from: lower ? startOfCalendarMonth(lower, system, locale) : undefined,
|
|
408
|
+
to: upper ? startOfCalendarMonth(upper, system, locale) : undefined,
|
|
409
|
+
};
|
|
410
|
+
}, [startMonth, minDate, endMonth, maxDate, system, locale]);
|
|
332
411
|
|
|
333
412
|
const setMonth = useCallback(
|
|
334
413
|
(next: Date) => {
|
|
335
|
-
|
|
414
|
+
/*
|
|
415
|
+
* Clamped here rather than only at the arrows. The arrows guard
|
|
416
|
+
* themselves, but the caption's dropdowns and a controlled caller both
|
|
417
|
+
* reach this by other routes — and a month list that offers a year is a
|
|
418
|
+
* poor place to discover that the year was out of bounds.
|
|
419
|
+
*/
|
|
420
|
+
const settled = clampDate(
|
|
421
|
+
startOfCalendarMonth(next, system, locale),
|
|
422
|
+
monthBounds.from,
|
|
423
|
+
monthBounds.to
|
|
424
|
+
);
|
|
336
425
|
if (!monthProp) setInternalMonth(settled);
|
|
337
426
|
onMonthChange?.(settled);
|
|
338
427
|
},
|
|
339
|
-
[monthProp, onMonthChange, system, locale]
|
|
428
|
+
[monthProp, onMonthChange, system, locale, monthBounds]
|
|
340
429
|
);
|
|
341
430
|
|
|
342
431
|
const commit = useCallback(
|
|
@@ -351,6 +440,14 @@ function CalendarRoot<Mode extends CalendarMode = 'single'>({
|
|
|
351
440
|
(date: Date) => {
|
|
352
441
|
const day = startOfDay(date);
|
|
353
442
|
|
|
443
|
+
/*
|
|
444
|
+
* A tap on a neighbouring month's cell — which only happens with
|
|
445
|
+
* `selectOutsideDays` — commits a date the grid is not showing. Page to
|
|
446
|
+
* it, or the selection lands in a greyed-out cell of the month you are
|
|
447
|
+
* still looking at and reads as having missed.
|
|
448
|
+
*/
|
|
449
|
+
if (!isSameCalendarMonth(day, month, system, locale)) setMonth(day);
|
|
450
|
+
|
|
354
451
|
if (mode === 'multiple') {
|
|
355
452
|
const current = (value as Date[] | undefined) ?? [];
|
|
356
453
|
const without = current.filter((picked) => !isSameDay(picked, day));
|
|
@@ -379,7 +476,7 @@ function CalendarRoot<Mode extends CalendarMode = 'single'>({
|
|
|
379
476
|
const current = value as Date | undefined;
|
|
380
477
|
commit((isSameDay(current, day) ? undefined : day) as CalendarSelection[Mode]);
|
|
381
478
|
},
|
|
382
|
-
[mode, value, commit]
|
|
479
|
+
[mode, value, commit, month, system, locale, setMonth]
|
|
383
480
|
);
|
|
384
481
|
|
|
385
482
|
const context = useMemo(
|
|
@@ -563,10 +660,30 @@ function CalendarDropdowns({ month, offset }: { month: Date; offset: number }) {
|
|
|
563
660
|
*/
|
|
564
661
|
const first = startMonth ?? minDate;
|
|
565
662
|
const last = endMonth ?? maxDate;
|
|
663
|
+
const firstParts = first ? calendarParts(first, system, locale) : null;
|
|
664
|
+
const lastParts = last ? calendarParts(last, system, locale) : null;
|
|
566
665
|
const bounds = {
|
|
567
|
-
from:
|
|
568
|
-
to:
|
|
666
|
+
from: firstParts ? firstParts.year : current.year - 100,
|
|
667
|
+
to: lastParts ? lastParts.year : current.year + 10,
|
|
569
668
|
};
|
|
669
|
+
/*
|
|
670
|
+
* The month list is bounded too, and only the year list used to be — so a
|
|
671
|
+
* calendar starting in March 1925 still offered January and February of 1925,
|
|
672
|
+
* and picking one navigated somewhere the calendar had said it would not go.
|
|
673
|
+
* Only the outermost year of each bound loses months; every year between them
|
|
674
|
+
* has all twelve.
|
|
675
|
+
*/
|
|
676
|
+
const monthOptions = months
|
|
677
|
+
.map((label, index) => ({ label, month: index + 1 }))
|
|
678
|
+
.filter(({ month }) => {
|
|
679
|
+
if (firstParts && current.year === firstParts.year && month < firstParts.month) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
if (lastParts && current.year === lastParts.year && month > lastParts.month) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return true;
|
|
686
|
+
});
|
|
570
687
|
/*
|
|
571
688
|
* Oldest first, the way a year runs. The list is long — a century by default
|
|
572
689
|
* — so it is opened scrolled to the year already in the caption rather than
|
|
@@ -594,10 +711,10 @@ function CalendarDropdowns({ month, offset }: { month: Date; offset: number }) {
|
|
|
594
711
|
<CaptionDropdown
|
|
595
712
|
label={months[current.month - 1] ?? String(current.month)}
|
|
596
713
|
accessibilityLabel="Month"
|
|
597
|
-
options={
|
|
598
|
-
active={
|
|
714
|
+
options={monthOptions.map((option) => option.label)}
|
|
715
|
+
active={monthOptions.findIndex((option) => option.month === current.month)}
|
|
599
716
|
width={MONTH_LIST_WIDTH}
|
|
600
|
-
onSelect={(index) => choose(index
|
|
717
|
+
onSelect={(index) => choose((monthOptions[index]?.month ?? current.month) - current.month)}
|
|
601
718
|
/>
|
|
602
719
|
<CaptionDropdown
|
|
603
720
|
label={String(current.year)}
|
|
@@ -787,16 +904,12 @@ function CalendarGrid({
|
|
|
787
904
|
* boundaries and the day numbers differ — so the walk itself is the same
|
|
788
905
|
* either way and only where it starts moves.
|
|
789
906
|
*/
|
|
790
|
-
const weeks = useMemo(
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
return Array.from({ length: 6 }, (_unusedWeek, week) =>
|
|
795
|
-
Array.from({ length: 7 }, (_unusedDay, day) => addDays(start, week * 7 + day))
|
|
796
|
-
);
|
|
797
|
-
}, [month, weekStartsOn, system, locale]);
|
|
907
|
+
const weeks = useMemo(
|
|
908
|
+
() => monthGrid(month, weekStartsOn, system, locale),
|
|
909
|
+
[month, weekStartsOn, system, locale]
|
|
910
|
+
);
|
|
798
911
|
const headings = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
|
|
799
|
-
const today =
|
|
912
|
+
const today = useToday();
|
|
800
913
|
|
|
801
914
|
const range = mode === 'range' ? (value as DateRange | undefined) : undefined;
|
|
802
915
|
const multiple = mode === 'multiple' ? ((value as Date[] | undefined) ?? []) : [];
|
|
@@ -60,6 +60,7 @@ import Svg, { ClipPath, Defs, G, Line as SvgLine, Rect } from 'react-native-svg'
|
|
|
60
60
|
import { useCSSVariable } from 'uniwind';
|
|
61
61
|
import { Text } from '../../primitives/text';
|
|
62
62
|
import { cn } from '../../utils/cn';
|
|
63
|
+
import { normalizeWeekStart, startOfDay } from '../../utils/date';
|
|
63
64
|
|
|
64
65
|
const AnimatedRect = Animated.createAnimatedComponent(Rect);
|
|
65
66
|
|
|
@@ -225,12 +226,15 @@ export function buildHeatmapCalendar(
|
|
|
225
226
|
entries: { date: Date; count: number }[],
|
|
226
227
|
options: { start?: Date; end?: Date; weekStartDay?: number } = {}
|
|
227
228
|
): HeatmapColumn[] {
|
|
228
|
-
const weekStartDay = options.weekStartDay ?? 0;
|
|
229
|
+
const weekStartDay = normalizeWeekStart(options.weekStartDay ?? 0);
|
|
229
230
|
if (!entries.length && !options.start) return [];
|
|
230
231
|
|
|
231
232
|
const times = entries.map((entry) => entry.date.getTime());
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
// Both bounds at local midnight, and both the same way. Comparing a
|
|
234
|
+
// normalised lower bound against a raw upper one drops the last day whenever
|
|
235
|
+
// the caller's `end` carries a time earlier than the cell being tested.
|
|
236
|
+
const start = startOfDay(options.start ?? new Date(Math.min(...times)));
|
|
237
|
+
const end = startOfDay(options.end ?? new Date(Math.max(...times)));
|
|
234
238
|
|
|
235
239
|
const byDay = new Map<string, number>();
|
|
236
240
|
for (const entry of entries) {
|
|
@@ -240,7 +244,7 @@ export function buildHeatmapCalendar(
|
|
|
240
244
|
|
|
241
245
|
// Back up to the first day of the week the range starts in, so column 0 is a
|
|
242
246
|
// whole week and every row lines up with a weekday for the rest of the chart.
|
|
243
|
-
const cursor =
|
|
247
|
+
const cursor = startOfDay(start);
|
|
244
248
|
cursor.setDate(cursor.getDate() - ((cursor.getDay() - weekStartDay + 7) % 7));
|
|
245
249
|
|
|
246
250
|
const columns: HeatmapColumn[] = [];
|
|
@@ -251,7 +255,7 @@ export function buildHeatmapCalendar(
|
|
|
251
255
|
const date = new Date(cursor);
|
|
252
256
|
// Before the range starts and after it ends the cell exists but has no
|
|
253
257
|
// reading — `count: 0` and no date, so the tooltip stays quiet on it.
|
|
254
|
-
const inRange = date >=
|
|
258
|
+
const inRange = date >= start && date <= end;
|
|
255
259
|
column.push({
|
|
256
260
|
bin: row,
|
|
257
261
|
count: inRange ? (byDay.get(dayKey(date)) ?? 0) : 0,
|
|
@@ -269,10 +273,6 @@ export function buildHeatmapCalendar(
|
|
|
269
273
|
return columns;
|
|
270
274
|
}
|
|
271
275
|
|
|
272
|
-
function startOfDay(date: Date) {
|
|
273
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
274
|
-
}
|
|
275
|
-
|
|
276
276
|
function dayKey(date: Date) {
|
|
277
277
|
return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
|
|
278
278
|
}
|
|
@@ -78,6 +78,11 @@ const mapVariants = tv({
|
|
|
78
78
|
control: 'h-9 w-9 items-center justify-center active:bg-muted',
|
|
79
79
|
popup: 'rounded-xl border border-border bg-popover px-3 py-2 shadow-lg',
|
|
80
80
|
pin: 'h-3.5 w-3.5 rounded-full border-2 border-background bg-primary shadow-md',
|
|
81
|
+
// Opaque enough to read over a landmass fill, with an edge and a shadow so
|
|
82
|
+
// it sits on the map rather than in it. At 85% the basemap's own roads and
|
|
83
|
+
// borders showed through the text.
|
|
84
|
+
label: 'rounded-md border border-border bg-background/95 shadow-sm',
|
|
85
|
+
labelText: 'text-foreground',
|
|
81
86
|
},
|
|
82
87
|
variants: {
|
|
83
88
|
position: {
|
|
@@ -90,6 +95,27 @@ const mapVariants = tv({
|
|
|
90
95
|
defaultVariants: { position: 'bottom-right' },
|
|
91
96
|
});
|
|
92
97
|
|
|
98
|
+
/**
|
|
99
|
+
* The two knobs on a label's pill.
|
|
100
|
+
*
|
|
101
|
+
* Lookups rather than `tv()` variants because they belong to `Map.Label`, not
|
|
102
|
+
* to `Map` — a variant here would be read off the root and documented as
|
|
103
|
+
* something you could pass to the map itself.
|
|
104
|
+
*/
|
|
105
|
+
const labelSize: Record<'sm' | 'md', string> = {
|
|
106
|
+
sm: 'px-1.5 py-px',
|
|
107
|
+
md: 'px-2 py-0.5',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const labelTone: Record<'default' | 'muted' | 'primary', { pill: string; text: string }> = {
|
|
111
|
+
default: { pill: '', text: '' },
|
|
112
|
+
muted: { pill: '', text: 'text-muted-foreground' },
|
|
113
|
+
primary: {
|
|
114
|
+
pill: 'border-primary/24 bg-primary',
|
|
115
|
+
text: 'text-primary-foreground',
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
93
119
|
export type MapControlsPosition =
|
|
94
120
|
| 'top-left'
|
|
95
121
|
| 'top-right'
|
|
@@ -445,6 +471,13 @@ export interface MapLabelProps {
|
|
|
445
471
|
className?: string;
|
|
446
472
|
/** Which side of the marker the label sits on. */
|
|
447
473
|
side?: 'top' | 'bottom';
|
|
474
|
+
/** `sm` for a map carrying a lot of them, where the pills start to collide. */
|
|
475
|
+
size?: 'sm' | 'md';
|
|
476
|
+
/**
|
|
477
|
+
* How loud the label is. `muted` for codes and counts that support the map
|
|
478
|
+
* without being its subject; `primary` for the one place being pointed at.
|
|
479
|
+
*/
|
|
480
|
+
tone?: 'default' | 'muted' | 'primary';
|
|
448
481
|
}
|
|
449
482
|
|
|
450
483
|
/**
|
|
@@ -460,19 +493,35 @@ export interface MapLabelProps {
|
|
|
460
493
|
* without widening the marker — a name is usually far wider than the dot it
|
|
461
494
|
* belongs to, and a box sized to the name would be anchored by the name.
|
|
462
495
|
*/
|
|
463
|
-
function MapLabel({
|
|
496
|
+
function MapLabel({
|
|
497
|
+
children,
|
|
498
|
+
className,
|
|
499
|
+
side = 'bottom',
|
|
500
|
+
size = 'md',
|
|
501
|
+
tone = 'default',
|
|
502
|
+
}: MapLabelProps) {
|
|
503
|
+
const { label, labelText } = mapVariants();
|
|
504
|
+
const shade = labelTone[tone];
|
|
505
|
+
|
|
464
506
|
return (
|
|
465
507
|
<View
|
|
466
508
|
pointerEvents="none"
|
|
467
509
|
className={cn(
|
|
468
510
|
'absolute items-center',
|
|
469
|
-
|
|
511
|
+
// 6pt of clearance rather than 4: the pill has a border now, and at 4
|
|
512
|
+
// its edge and the pin's ring read as one smudged shape.
|
|
513
|
+
side === 'top' ? 'bottom-full mb-1.5' : 'top-full mt-1.5'
|
|
470
514
|
)}
|
|
471
515
|
style={{ left: -120, right: -120 }}
|
|
472
516
|
>
|
|
473
|
-
<View className={
|
|
517
|
+
<View className={label({ className: cn(labelSize[size], shade.pill, className) })}>
|
|
474
518
|
{textChildren(children, (text) => (
|
|
475
|
-
<Text
|
|
519
|
+
<Text
|
|
520
|
+
size="xs"
|
|
521
|
+
weight="medium"
|
|
522
|
+
numberOfLines={1}
|
|
523
|
+
className={labelText({ className: shade.text })}
|
|
524
|
+
>
|
|
476
525
|
{text}
|
|
477
526
|
</Text>
|
|
478
527
|
))}
|