@robr0/design-system 0.10.0 → 0.11.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 +7 -6
- package/components/AgentPlan/AgentPlan.css +14 -3
- package/components/AiButton/AiButton.css +1 -1
- package/components/Alert/Alert.css +9 -0
- package/components/AlertDialog/AlertDialog.css +3 -1
- package/components/AlertDialog/AlertDialog.js +2 -3
- package/components/AppSidebar/AppSidebar.css +9 -0
- package/components/Card/Card.css +7 -2
- package/components/CardStack/CardStack.css +106 -0
- package/components/CardStack/CardStack.d.ts +44 -0
- package/components/CardStack/CardStack.js +140 -0
- package/components/Carousel/Carousel.css +9 -0
- package/components/Carousel/Carousel.js +37 -4
- package/components/Chart/AreaChart.js +6 -5
- package/components/Chart/LineChart.js +4 -3
- package/components/Chart/PieChart.js +2 -15
- package/components/Chart/RadarChart.js +5 -4
- package/components/Chart/RadialChart.js +2 -15
- package/components/Chart/ScatterChart.js +3 -13
- package/components/Chart/StackedBarChart.js +2 -15
- package/components/Chart/Treemap.js +3 -17
- package/components/Chart/palette.d.ts +1 -0
- package/components/Chart/palette.js +19 -0
- package/components/ChatMarker/ChatMarker.css +9 -0
- package/components/ChatMessage/ChatMessage.css +16 -0
- package/components/ChatThread/ChatThread.js +2 -2
- package/components/Chip/Chip.css +9 -0
- package/components/CodeBlock/CodeBlock.css +10 -1
- package/components/CodeBlock/CodeBlock.js +2 -1
- package/components/ColorPicker/ColorPicker.js +10 -0
- package/components/Combobox/Combobox.css +9 -0
- package/components/CommandPalette/CommandPalette.css +9 -0
- package/components/CommandPalette/CommandPalette.js +31 -5
- package/components/Composer/Composer.css +118 -0
- package/components/Composer/Composer.d.ts +7 -0
- package/components/Composer/Composer.js +7 -1
- package/components/ContactCard/ContactCard.css +9 -0
- package/components/ContextMenu/ContextMenu.js +39 -3
- package/components/DatePicker/DatePicker.js +16 -0
- package/components/Dialog/Dialog.css +3 -1
- package/components/Dialog/Dialog.js +5 -4
- package/components/DocumentChip/DocumentChip.css +9 -0
- package/components/Dropdown/Dropdown.js +22 -3
- package/components/DropdownMenu/DropdownMenu.js +43 -6
- package/components/EmptyState/EmptyState.css +9 -0
- package/components/FileInput/FileInput.css +9 -0
- package/components/Globe/Globe.css +155 -0
- package/components/Globe/Globe.d.ts +96 -0
- package/components/Globe/Globe.js +415 -0
- package/components/InterruptCard/InterruptCard.css +27 -17
- package/components/InterruptCard/InterruptCard.d.ts +1 -1
- package/components/InterruptCard/InterruptCard.js +1 -1
- package/components/MapCallout/MapCallout.css +46 -0
- package/components/MapCallout/MapCallout.d.ts +29 -0
- package/components/MapCallout/MapCallout.js +17 -0
- package/components/MapLegend/MapLegend.css +88 -0
- package/components/MapLegend/MapLegend.d.ts +39 -0
- package/components/MapLegend/MapLegend.js +68 -0
- package/components/MessageActions/MessageActions.css +9 -0
- package/components/MessageCard/MessageCard.css +68 -16
- package/components/MessageCard/MessageCard.d.ts +1 -1
- package/components/MessageCard/MessageCard.js +6 -4
- package/components/ModelPicker/ModelPicker.css +4 -1
- package/components/ModelPicker/ModelPicker.js +43 -1
- package/components/NotificationCenter/NotificationCenter.css +9 -0
- package/components/NotificationCenter/NotificationCenter.js +26 -10
- package/components/NumberInput/NumberInput.css +9 -0
- package/components/Popover/Popover.css +3 -1
- package/components/Popover/Popover.js +2 -1
- package/components/SourceChip/SourceChip.css +9 -0
- package/components/Stat/Stat.css +9 -0
- package/components/Stepper/Stepper.css +9 -0
- package/components/TagInput/TagInput.css +9 -0
- package/components/TimePicker/TimePicker.js +4 -1
- package/components/Toast/Toast.css +32 -11
- package/components/Toast/Toast.js +5 -4
- package/components/ToggleGroup/ToggleGroup.css +9 -0
- package/components/ToolCall/ToolCall.css +18 -6
- package/components/Tooltip/Tooltip.js +20 -16
- package/components/registry.json +37 -0
- package/components/registry.json.d.ts +37 -0
- package/components/registry.json.js +2 -2
- package/index.d.ts +4 -0
- package/index.js +8 -0
- package/package.json +5 -1
- package/tokens/motion.d.ts +23 -0
- package/tokens/motion.js +14 -0
- package/tokens/registry.json +17 -0
- package/tokens/registry.json.d.ts +17 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +11 -0
- package/tokens/tokens-light.css +11 -0
- package/tokens/tokens-typography.css +20 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** A place on the globe. */
|
|
3
|
+
export interface GlobePoint {
|
|
4
|
+
/** Stable identifier, referenced by arcs and by `activePointId`. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Latitude in degrees, north positive. */
|
|
7
|
+
lat: number;
|
|
8
|
+
/** Longitude in degrees, east positive. */
|
|
9
|
+
lng: number;
|
|
10
|
+
/** Short label drawn beside the marker when `showLabels` is on. */
|
|
11
|
+
label?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Marker glyph. `point` is a cross (something that moves or listens),
|
|
14
|
+
* `anchor` is a square (something fixed). The glyph is the only difference.
|
|
15
|
+
*/
|
|
16
|
+
kind?: 'point' | 'anchor';
|
|
17
|
+
}
|
|
18
|
+
/** A great-circle arc between two points. */
|
|
19
|
+
export interface GlobeArc {
|
|
20
|
+
/** `id` of the point the arc leaves. */
|
|
21
|
+
from: string;
|
|
22
|
+
/** `id` of the point the arc reaches. */
|
|
23
|
+
to: string;
|
|
24
|
+
/**
|
|
25
|
+
* Stroke colour, any CSS colour (`var(--color-chart-series-3)` works).
|
|
26
|
+
* Omit for the default cobalt-to-violet gradient along the arc.
|
|
27
|
+
*/
|
|
28
|
+
color?: string;
|
|
29
|
+
/** How far the arc lifts off the surface at its midpoint, as a fraction of the radius. */
|
|
30
|
+
altitude?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Where the globe is looking: `[longitude, latitude]` of the centre, in degrees. */
|
|
33
|
+
export type GlobeRotation = [number, number];
|
|
34
|
+
/** Props owned by Globe itself — everything else falls through to the root `<div>`. */
|
|
35
|
+
type GlobeOwnProps = {
|
|
36
|
+
/** The places to mark. */
|
|
37
|
+
points?: GlobePoint[];
|
|
38
|
+
/** The arcs to draw between them. An arc whose endpoint is not in `points` is skipped. */
|
|
39
|
+
arcs?: GlobeArc[];
|
|
40
|
+
/**
|
|
41
|
+
* Controlled view: `[longitude, latitude]` of the centre, in degrees.
|
|
42
|
+
* Pair with `onRotationChange`. Omit to let the globe own its rotation.
|
|
43
|
+
*/
|
|
44
|
+
rotation?: GlobeRotation;
|
|
45
|
+
/** Initial view when uncontrolled. */
|
|
46
|
+
defaultRotation?: GlobeRotation;
|
|
47
|
+
/** Fires whenever the view changes — drag, keys, or the auto-rotation. */
|
|
48
|
+
onRotationChange?: (rotation: GlobeRotation) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Spin slowly on its own, in degrees per second. Pauses while the pointer
|
|
51
|
+
* is over the globe or it has focus, and never runs under
|
|
52
|
+
* `prefers-reduced-motion`. `0` switches it off.
|
|
53
|
+
*/
|
|
54
|
+
autoRotate?: number;
|
|
55
|
+
/** Drag to rotate, and rotate with the arrow keys (or W, A, S, D) when focused. */
|
|
56
|
+
interactive?: boolean;
|
|
57
|
+
/** Degrees between graticule lines. `0` removes the graticule. */
|
|
58
|
+
graticuleStep?: number;
|
|
59
|
+
/** Draw each point's `label` beside its marker. */
|
|
60
|
+
showLabels?: boolean;
|
|
61
|
+
/** The point to single out: its marker enlarges and its callout renders. */
|
|
62
|
+
activePointId?: string;
|
|
63
|
+
/** Fires as the pointer enters a marker, and with `null` as it leaves. */
|
|
64
|
+
onPointHover?: (point: GlobePoint | null) => void;
|
|
65
|
+
/** Fires when a marker is clicked. */
|
|
66
|
+
onPointClick?: (point: GlobePoint) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Renders the annotation for the active (or hovered) point. It is placed
|
|
69
|
+
* beside the marker in an HTML overlay, so any markup works; MapCallout is
|
|
70
|
+
* the intended filling. The overlay carries `data-side="left"|"right"` for
|
|
71
|
+
* which side of the marker it sits on.
|
|
72
|
+
*/
|
|
73
|
+
renderCallout?: (point: GlobePoint) => React.ReactNode;
|
|
74
|
+
/**
|
|
75
|
+
* Accessible name for the globe, e.g. "Listening points across the
|
|
76
|
+
* network". The point and arc counts are appended for screen readers.
|
|
77
|
+
*/
|
|
78
|
+
label?: string;
|
|
79
|
+
/** Additional CSS classes */
|
|
80
|
+
className?: string;
|
|
81
|
+
};
|
|
82
|
+
export interface GlobeProps extends GlobeOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof GlobeOwnProps | 'children'> {
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Globe — an orthographic globe drawn in SVG, for showing where things are
|
|
86
|
+
* and what connects them: a graticule, markers at lat/lng, and great-circle
|
|
87
|
+
* arcs between them. No map library, no tile server, no land data; the
|
|
88
|
+
* graticule alone gives the sphere its shape, which is the point — this is
|
|
89
|
+
* for the geometry of a network, not for navigation.
|
|
90
|
+
*
|
|
91
|
+
* Colour comes from the tokens: the graticule in the divider colour, markers
|
|
92
|
+
* in the text colours, arcs along the cobalt-to-violet chart series. Rotate
|
|
93
|
+
* it by dragging, with the arrow keys when focused, or let it turn on its own.
|
|
94
|
+
*/
|
|
95
|
+
export declare const Globe: React.ForwardRefExoticComponent<GlobeProps & React.RefAttributes<HTMLDivElement>>;
|
|
96
|
+
export {};
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import React, { useId, useState, useRef, useCallback, useSyncExternalStore, useEffect } from "react";
|
|
4
|
+
import "./Globe.css";
|
|
5
|
+
const VIEW = 480;
|
|
6
|
+
const CENTER = VIEW / 2;
|
|
7
|
+
const RADIUS = 180;
|
|
8
|
+
const GRATICULE_SAMPLES = 60;
|
|
9
|
+
const ARC_SAMPLES = 48;
|
|
10
|
+
const DRAG_DEGREES_PER_RADIUS = 90;
|
|
11
|
+
const KEY_STEP = 10;
|
|
12
|
+
const MAX_LAT = 80;
|
|
13
|
+
const MARKER = 5;
|
|
14
|
+
const DEG = Math.PI / 180;
|
|
15
|
+
const toVec = (lat, lng) => {
|
|
16
|
+
const φ = lat * DEG;
|
|
17
|
+
const λ = lng * DEG;
|
|
18
|
+
return [Math.cos(φ) * Math.cos(λ), Math.cos(φ) * Math.sin(λ), Math.sin(φ)];
|
|
19
|
+
};
|
|
20
|
+
const makeProjector = ([lng0, lat0]) => {
|
|
21
|
+
const λ0 = lng0 * DEG;
|
|
22
|
+
const φ0 = lat0 * DEG;
|
|
23
|
+
const cosλ = Math.cos(λ0);
|
|
24
|
+
const sinλ = Math.sin(λ0);
|
|
25
|
+
const cosφ = Math.cos(φ0);
|
|
26
|
+
const sinφ = Math.sin(φ0);
|
|
27
|
+
return ([x, y, z], altitude = 0) => {
|
|
28
|
+
const x1 = x * cosλ + y * sinλ;
|
|
29
|
+
const y1 = -x * sinλ + y * cosλ;
|
|
30
|
+
const depth = x1 * cosφ + z * sinφ;
|
|
31
|
+
const up = -x1 * sinφ + z * cosφ;
|
|
32
|
+
const s = 1 + altitude;
|
|
33
|
+
const sx = y1 * s;
|
|
34
|
+
const sy = up * s;
|
|
35
|
+
return {
|
|
36
|
+
x: CENTER + RADIUS * sx,
|
|
37
|
+
y: CENTER - RADIUS * sy,
|
|
38
|
+
visible: depth >= 0 || sx * sx + sy * sy > 1
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const round = (n) => Math.round(n * 10) / 10;
|
|
43
|
+
const splitRuns = (pts) => {
|
|
44
|
+
let front = "";
|
|
45
|
+
let back = "";
|
|
46
|
+
let prev;
|
|
47
|
+
for (const p of pts) {
|
|
48
|
+
const cmd = `${prev && prev.visible === p.visible ? "L" : "M"}${round(p.x)} ${round(p.y)}`;
|
|
49
|
+
if (p.visible) front += cmd;
|
|
50
|
+
else back += cmd;
|
|
51
|
+
if (prev && prev.visible !== p.visible) {
|
|
52
|
+
const start = `M${round(prev.x)} ${round(prev.y)}L${round(p.x)} ${round(p.y)}`;
|
|
53
|
+
if (p.visible) front += start;
|
|
54
|
+
else back += start;
|
|
55
|
+
}
|
|
56
|
+
prev = p;
|
|
57
|
+
}
|
|
58
|
+
return { front, back };
|
|
59
|
+
};
|
|
60
|
+
const arcVectors = (a, b, altitude) => {
|
|
61
|
+
const dot = Math.max(-1, Math.min(1, a[0] * b[0] + a[1] * b[1] + a[2] * b[2]));
|
|
62
|
+
const ω = Math.acos(dot);
|
|
63
|
+
const sinω = Math.sin(ω);
|
|
64
|
+
const out = [];
|
|
65
|
+
for (let i = 0; i <= ARC_SAMPLES; i++) {
|
|
66
|
+
const t = i / ARC_SAMPLES;
|
|
67
|
+
let v;
|
|
68
|
+
if (sinω < 1e-6) {
|
|
69
|
+
v = a;
|
|
70
|
+
} else {
|
|
71
|
+
const wa = Math.sin((1 - t) * ω) / sinω;
|
|
72
|
+
const wb = Math.sin(t * ω) / sinω;
|
|
73
|
+
v = [wa * a[0] + wb * b[0], wa * a[1] + wb * b[1], wa * a[2] + wb * b[2]];
|
|
74
|
+
}
|
|
75
|
+
out.push([v, altitude * Math.sin(t * Math.PI)]);
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
};
|
|
79
|
+
const normalizeLng = (lng) => ((lng + 180) % 360 + 360) % 360 - 180;
|
|
80
|
+
const clampLat = (lat) => Math.max(-MAX_LAT, Math.min(MAX_LAT, lat));
|
|
81
|
+
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
82
|
+
const subscribeReducedMotion = (callback) => {
|
|
83
|
+
const query = window.matchMedia(REDUCED_MOTION_QUERY);
|
|
84
|
+
query.addEventListener("change", callback);
|
|
85
|
+
return () => query.removeEventListener("change", callback);
|
|
86
|
+
};
|
|
87
|
+
const getReducedMotion = () => window.matchMedia(REDUCED_MOTION_QUERY).matches;
|
|
88
|
+
const getServerReducedMotion = () => false;
|
|
89
|
+
const KEY_DELTAS = {
|
|
90
|
+
ArrowLeft: [KEY_STEP, 0],
|
|
91
|
+
ArrowRight: [-KEY_STEP, 0],
|
|
92
|
+
ArrowUp: [0, -KEY_STEP],
|
|
93
|
+
ArrowDown: [0, KEY_STEP],
|
|
94
|
+
a: [KEY_STEP, 0],
|
|
95
|
+
d: [-KEY_STEP, 0],
|
|
96
|
+
w: [0, -KEY_STEP],
|
|
97
|
+
s: [0, KEY_STEP]
|
|
98
|
+
};
|
|
99
|
+
const Globe = React.forwardRef(
|
|
100
|
+
({
|
|
101
|
+
points = [],
|
|
102
|
+
arcs = [],
|
|
103
|
+
rotation: rotationProp,
|
|
104
|
+
defaultRotation = [-20, 20],
|
|
105
|
+
onRotationChange,
|
|
106
|
+
autoRotate = 3,
|
|
107
|
+
interactive = true,
|
|
108
|
+
graticuleStep = 30,
|
|
109
|
+
showLabels = true,
|
|
110
|
+
activePointId,
|
|
111
|
+
onPointHover,
|
|
112
|
+
onPointClick,
|
|
113
|
+
renderCallout,
|
|
114
|
+
label = "Globe",
|
|
115
|
+
className = "",
|
|
116
|
+
onPointerDown,
|
|
117
|
+
onPointerMove,
|
|
118
|
+
onPointerUp,
|
|
119
|
+
onPointerCancel,
|
|
120
|
+
onPointerEnter,
|
|
121
|
+
onPointerLeave,
|
|
122
|
+
onKeyDown,
|
|
123
|
+
onFocus,
|
|
124
|
+
onBlur,
|
|
125
|
+
...rest
|
|
126
|
+
}, ref) => {
|
|
127
|
+
const baseClass = "ds-globe";
|
|
128
|
+
const gradientId = useId();
|
|
129
|
+
const isControlled = rotationProp !== void 0;
|
|
130
|
+
const [innerRotation, setInnerRotation] = useState(defaultRotation);
|
|
131
|
+
const rotation = isControlled ? rotationProp : innerRotation;
|
|
132
|
+
const rotationRef = useRef(rotation);
|
|
133
|
+
rotationRef.current = rotation;
|
|
134
|
+
const onRotationChangeRef = useRef(onRotationChange);
|
|
135
|
+
onRotationChangeRef.current = onRotationChange;
|
|
136
|
+
const commit = useCallback(
|
|
137
|
+
(next) => {
|
|
138
|
+
const clean = [normalizeLng(next[0]), clampLat(next[1])];
|
|
139
|
+
if (!isControlled) setInnerRotation(clean);
|
|
140
|
+
onRotationChangeRef.current?.(clean);
|
|
141
|
+
},
|
|
142
|
+
[isControlled]
|
|
143
|
+
);
|
|
144
|
+
const [hoveredId, setHoveredId] = useState(null);
|
|
145
|
+
const [paused, setPaused] = useState(false);
|
|
146
|
+
const reducedMotion = useSyncExternalStore(
|
|
147
|
+
subscribeReducedMotion,
|
|
148
|
+
getReducedMotion,
|
|
149
|
+
getServerReducedMotion
|
|
150
|
+
);
|
|
151
|
+
const spinning = autoRotate !== 0 && !paused && !reducedMotion;
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!spinning) return;
|
|
154
|
+
let frame = 0;
|
|
155
|
+
let last = performance.now();
|
|
156
|
+
const tick = (now) => {
|
|
157
|
+
const dt = (now - last) / 1e3;
|
|
158
|
+
last = now;
|
|
159
|
+
const [lng, lat] = rotationRef.current;
|
|
160
|
+
commit([lng - autoRotate * dt, lat]);
|
|
161
|
+
frame = requestAnimationFrame(tick);
|
|
162
|
+
};
|
|
163
|
+
frame = requestAnimationFrame(tick);
|
|
164
|
+
return () => cancelAnimationFrame(frame);
|
|
165
|
+
}, [spinning, autoRotate, commit]);
|
|
166
|
+
const rootRef = useRef(null);
|
|
167
|
+
const drag = useRef(null);
|
|
168
|
+
const [dragging, setDragging] = useState(false);
|
|
169
|
+
const setRef = (node) => {
|
|
170
|
+
rootRef.current = node;
|
|
171
|
+
if (typeof ref === "function") ref(node);
|
|
172
|
+
else if (ref) ref.current = node;
|
|
173
|
+
};
|
|
174
|
+
const handlePointerDown = (e) => {
|
|
175
|
+
onPointerDown?.(e);
|
|
176
|
+
if (!interactive || e.button !== 0) return;
|
|
177
|
+
drag.current = { x: e.clientX, y: e.clientY, rotation: rotationRef.current };
|
|
178
|
+
setDragging(true);
|
|
179
|
+
setPaused(true);
|
|
180
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
181
|
+
};
|
|
182
|
+
const handlePointerMove = (e) => {
|
|
183
|
+
onPointerMove?.(e);
|
|
184
|
+
if (!drag.current || !rootRef.current) return;
|
|
185
|
+
const radiusPx = rootRef.current.clientWidth * (RADIUS / VIEW) || 1;
|
|
186
|
+
const perPx = DRAG_DEGREES_PER_RADIUS / radiusPx;
|
|
187
|
+
const [lng, lat] = drag.current.rotation;
|
|
188
|
+
commit([
|
|
189
|
+
lng - (e.clientX - drag.current.x) * perPx,
|
|
190
|
+
lat + (e.clientY - drag.current.y) * perPx
|
|
191
|
+
]);
|
|
192
|
+
};
|
|
193
|
+
const endDrag = (e) => {
|
|
194
|
+
if (!drag.current) return;
|
|
195
|
+
drag.current = null;
|
|
196
|
+
setDragging(false);
|
|
197
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) {
|
|
198
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const handlePointerUp = (e) => {
|
|
202
|
+
onPointerUp?.(e);
|
|
203
|
+
endDrag(e);
|
|
204
|
+
};
|
|
205
|
+
const handlePointerCancel = (e) => {
|
|
206
|
+
onPointerCancel?.(e);
|
|
207
|
+
endDrag(e);
|
|
208
|
+
};
|
|
209
|
+
const handlePointerEnter = (e) => {
|
|
210
|
+
onPointerEnter?.(e);
|
|
211
|
+
setPaused(true);
|
|
212
|
+
};
|
|
213
|
+
const handlePointerLeave = (e) => {
|
|
214
|
+
onPointerLeave?.(e);
|
|
215
|
+
if (document.activeElement !== e.currentTarget) setPaused(false);
|
|
216
|
+
};
|
|
217
|
+
const handleKeyDown = (e) => {
|
|
218
|
+
onKeyDown?.(e);
|
|
219
|
+
if (!interactive || e.defaultPrevented) return;
|
|
220
|
+
const delta = KEY_DELTAS[e.key] ?? KEY_DELTAS[e.key.toLowerCase()];
|
|
221
|
+
if (!delta) return;
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
const [lng, lat] = rotationRef.current;
|
|
224
|
+
commit([lng + delta[0], lat + delta[1]]);
|
|
225
|
+
};
|
|
226
|
+
const handleFocus = (e) => {
|
|
227
|
+
onFocus?.(e);
|
|
228
|
+
setPaused(true);
|
|
229
|
+
};
|
|
230
|
+
const handleBlur = (e) => {
|
|
231
|
+
onBlur?.(e);
|
|
232
|
+
if (!e.currentTarget.matches(":hover")) setPaused(false);
|
|
233
|
+
};
|
|
234
|
+
const project = makeProjector(rotation);
|
|
235
|
+
let graticuleFront = "";
|
|
236
|
+
let graticuleBack = "";
|
|
237
|
+
if (graticuleStep > 0) {
|
|
238
|
+
for (let lng = -180; lng < 180; lng += graticuleStep) {
|
|
239
|
+
const pts = [];
|
|
240
|
+
for (let i = 0; i <= GRATICULE_SAMPLES; i++) {
|
|
241
|
+
pts.push(project(toVec(-90 + 180 * i / GRATICULE_SAMPLES, lng)));
|
|
242
|
+
}
|
|
243
|
+
const runs = splitRuns(pts);
|
|
244
|
+
graticuleFront += runs.front;
|
|
245
|
+
graticuleBack += runs.back;
|
|
246
|
+
}
|
|
247
|
+
for (let lat = -90 + graticuleStep; lat < 90; lat += graticuleStep) {
|
|
248
|
+
const pts = [];
|
|
249
|
+
for (let i = 0; i <= GRATICULE_SAMPLES; i++) {
|
|
250
|
+
pts.push(project(toVec(lat, -180 + 360 * i / GRATICULE_SAMPLES)));
|
|
251
|
+
}
|
|
252
|
+
const runs = splitRuns(pts);
|
|
253
|
+
graticuleFront += runs.front;
|
|
254
|
+
graticuleBack += runs.back;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const byId = new Map(points.map((p) => [p.id, p]));
|
|
258
|
+
const projectedPoints = points.map((p) => ({ point: p, at: project(toVec(p.lat, p.lng)) }));
|
|
259
|
+
const drawnArcs = arcs.flatMap((arc, i) => {
|
|
260
|
+
const from = byId.get(arc.from);
|
|
261
|
+
const to = byId.get(arc.to);
|
|
262
|
+
if (!from || !to) return [];
|
|
263
|
+
const altitude = arc.altitude ?? 0.25;
|
|
264
|
+
const samples = arcVectors(toVec(from.lat, from.lng), toVec(to.lat, to.lng), altitude);
|
|
265
|
+
const pts = samples.map(([v, h]) => project(v, h));
|
|
266
|
+
const runs = splitRuns(pts);
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
key: `${arc.from}-${arc.to}-${i}`,
|
|
270
|
+
color: arc.color,
|
|
271
|
+
start: pts[0],
|
|
272
|
+
end: pts[pts.length - 1],
|
|
273
|
+
...runs
|
|
274
|
+
}
|
|
275
|
+
];
|
|
276
|
+
});
|
|
277
|
+
const calloutId = activePointId ?? hoveredId;
|
|
278
|
+
const callout = calloutId ? projectedPoints.find((p) => p.point.id === calloutId) : void 0;
|
|
279
|
+
const calloutSide = callout && callout.at.x < CENTER ? "left" : "right";
|
|
280
|
+
const classes = [
|
|
281
|
+
baseClass,
|
|
282
|
+
interactive ? `${baseClass}--interactive` : "",
|
|
283
|
+
dragging ? `${baseClass}--dragging` : "",
|
|
284
|
+
className
|
|
285
|
+
].filter(Boolean).join(" ");
|
|
286
|
+
const description = `${points.length} ${points.length === 1 ? "point" : "points"}, ${arcs.length} ${arcs.length === 1 ? "arc" : "arcs"}.${interactive ? " Drag to rotate, or use the arrow keys when focused." : ""}`;
|
|
287
|
+
return /* @__PURE__ */ jsxs(
|
|
288
|
+
"div",
|
|
289
|
+
{
|
|
290
|
+
...rest,
|
|
291
|
+
ref: setRef,
|
|
292
|
+
className: classes,
|
|
293
|
+
role: interactive ? "application" : "img",
|
|
294
|
+
"aria-roledescription": "globe",
|
|
295
|
+
"aria-label": `${label}. ${description}`,
|
|
296
|
+
tabIndex: interactive ? 0 : void 0,
|
|
297
|
+
onPointerDown: handlePointerDown,
|
|
298
|
+
onPointerMove: handlePointerMove,
|
|
299
|
+
onPointerUp: handlePointerUp,
|
|
300
|
+
onPointerCancel: handlePointerCancel,
|
|
301
|
+
onPointerEnter: handlePointerEnter,
|
|
302
|
+
onPointerLeave: handlePointerLeave,
|
|
303
|
+
onKeyDown: handleKeyDown,
|
|
304
|
+
onFocus: handleFocus,
|
|
305
|
+
onBlur: handleBlur,
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ jsxs(
|
|
308
|
+
"svg",
|
|
309
|
+
{
|
|
310
|
+
className: `${baseClass}__svg`,
|
|
311
|
+
viewBox: `0 0 ${VIEW} ${VIEW}`,
|
|
312
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
313
|
+
"aria-hidden": "true",
|
|
314
|
+
focusable: "false",
|
|
315
|
+
children: [
|
|
316
|
+
/* @__PURE__ */ jsx("defs", { children: drawnArcs.map(
|
|
317
|
+
(arc) => arc.color ? null : /* @__PURE__ */ jsxs(
|
|
318
|
+
"linearGradient",
|
|
319
|
+
{
|
|
320
|
+
id: `${gradientId}-${arc.key}`,
|
|
321
|
+
gradientUnits: "userSpaceOnUse",
|
|
322
|
+
x1: round(arc.start.x),
|
|
323
|
+
y1: round(arc.start.y),
|
|
324
|
+
x2: round(arc.end.x),
|
|
325
|
+
y2: round(arc.end.y),
|
|
326
|
+
children: [
|
|
327
|
+
/* @__PURE__ */ jsx("stop", { offset: "0", className: `${baseClass}__arc-stop-start` }),
|
|
328
|
+
/* @__PURE__ */ jsx("stop", { offset: "1", className: `${baseClass}__arc-stop-end` })
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
arc.key
|
|
332
|
+
)
|
|
333
|
+
) }),
|
|
334
|
+
/* @__PURE__ */ jsx("circle", { className: `${baseClass}__limb`, cx: CENTER, cy: CENTER, r: RADIUS }),
|
|
335
|
+
graticuleBack && /* @__PURE__ */ jsx("path", { className: `${baseClass}__graticule-back`, d: graticuleBack }),
|
|
336
|
+
graticuleFront && /* @__PURE__ */ jsx("path", { className: `${baseClass}__graticule`, d: graticuleFront }),
|
|
337
|
+
/* @__PURE__ */ jsx("g", { className: `${baseClass}__arcs`, children: drawnArcs.map((arc) => {
|
|
338
|
+
const stroke = arc.color ?? `url(#${gradientId}-${arc.key})`;
|
|
339
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
340
|
+
arc.back && /* @__PURE__ */ jsx("path", { className: `${baseClass}__arc-back`, d: arc.back, stroke }),
|
|
341
|
+
arc.front && /* @__PURE__ */ jsx("path", { className: `${baseClass}__arc`, d: arc.front, stroke })
|
|
342
|
+
] }, arc.key);
|
|
343
|
+
}) }),
|
|
344
|
+
/* @__PURE__ */ jsx("g", { className: `${baseClass}__points`, children: projectedPoints.map(({ point, at }) => {
|
|
345
|
+
const x = round(at.x);
|
|
346
|
+
const y = round(at.y);
|
|
347
|
+
const active = point.id === calloutId;
|
|
348
|
+
const markerClasses = [
|
|
349
|
+
`${baseClass}__point`,
|
|
350
|
+
`${baseClass}__point--${point.kind ?? "point"}`,
|
|
351
|
+
at.visible ? "" : `${baseClass}__point--back`,
|
|
352
|
+
active ? `${baseClass}__point--active` : ""
|
|
353
|
+
].filter(Boolean).join(" ");
|
|
354
|
+
return /* @__PURE__ */ jsxs(
|
|
355
|
+
"g",
|
|
356
|
+
{
|
|
357
|
+
className: markerClasses,
|
|
358
|
+
transform: `translate(${x} ${y})`,
|
|
359
|
+
onPointerEnter: () => {
|
|
360
|
+
setHoveredId(point.id);
|
|
361
|
+
onPointHover?.(point);
|
|
362
|
+
},
|
|
363
|
+
onPointerLeave: () => {
|
|
364
|
+
setHoveredId((id) => id === point.id ? null : id);
|
|
365
|
+
onPointHover?.(null);
|
|
366
|
+
},
|
|
367
|
+
onClick: onPointClick ? () => onPointClick(point) : void 0,
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ jsx("circle", { className: `${baseClass}__hit`, r: MARKER * 2.4 }),
|
|
370
|
+
point.kind === "anchor" ? /* @__PURE__ */ jsx(
|
|
371
|
+
"rect",
|
|
372
|
+
{
|
|
373
|
+
className: `${baseClass}__glyph`,
|
|
374
|
+
x: -MARKER,
|
|
375
|
+
y: -MARKER,
|
|
376
|
+
width: MARKER * 2,
|
|
377
|
+
height: MARKER * 2
|
|
378
|
+
}
|
|
379
|
+
) : /* @__PURE__ */ jsx(
|
|
380
|
+
"path",
|
|
381
|
+
{
|
|
382
|
+
className: `${baseClass}__glyph`,
|
|
383
|
+
d: `M${-MARKER} 0H${MARKER}M0 ${-MARKER}V${MARKER}`
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
showLabels && point.label && /* @__PURE__ */ jsx("text", { className: `${baseClass}__label`, x: MARKER * 1.8, y: -MARKER * 1.2, children: point.label })
|
|
387
|
+
]
|
|
388
|
+
},
|
|
389
|
+
point.id
|
|
390
|
+
);
|
|
391
|
+
}) })
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
callout && renderCallout && /* @__PURE__ */ jsx(
|
|
396
|
+
"div",
|
|
397
|
+
{
|
|
398
|
+
className: `${baseClass}__callout`,
|
|
399
|
+
"data-side": calloutSide,
|
|
400
|
+
style: {
|
|
401
|
+
left: `${callout.at.x / VIEW * 100}%`,
|
|
402
|
+
top: `${callout.at.y / VIEW * 100}%`
|
|
403
|
+
},
|
|
404
|
+
children: renderCallout(callout.point)
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
Globe.displayName = "Globe";
|
|
413
|
+
export {
|
|
414
|
+
Globe
|
|
415
|
+
};
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
width: 100%;
|
|
20
20
|
background-color: var(--color-bg-container-primary);
|
|
21
21
|
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
22
|
-
|
|
22
|
+
/* The chat furniture's shared 24px shell — see ToolCall. */
|
|
23
|
+
border-radius: var(--radius-xl);
|
|
23
24
|
overflow: hidden;
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -31,13 +32,15 @@
|
|
|
31
32
|
display: flex;
|
|
32
33
|
align-items: flex-start;
|
|
33
34
|
gap: var(--gap-sm);
|
|
34
|
-
padding: var(--padding-
|
|
35
|
+
padding: var(--padding-md) var(--padding-lg);
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
.ds-interrupt-card__icon {
|
|
38
39
|
display: inline-flex;
|
|
39
40
|
align-items: center;
|
|
40
41
|
flex: none;
|
|
42
|
+
/* Centre the icon on the title's first line. */
|
|
43
|
+
margin-top: calc((var(--font-heading-3-line-height) - var(--icon-size-sm)) / 2);
|
|
41
44
|
color: var(--ds-interrupt-card-color);
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -48,18 +51,18 @@
|
|
|
48
51
|
.ds-interrupt-card__heading {
|
|
49
52
|
display: flex;
|
|
50
53
|
flex-direction: column;
|
|
51
|
-
gap: var(--gap-
|
|
54
|
+
gap: var(--gap-xs);
|
|
52
55
|
min-width: 0;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
/* The question is the card's one heading, so it takes the
|
|
56
|
-
|
|
58
|
+
/* The question is the card's one heading, so it takes the same heading
|
|
59
|
+
tier as MessageCard's title — the chat cards share one title scale. */
|
|
57
60
|
.ds-interrupt-card__title {
|
|
58
|
-
font-family: var(--font-
|
|
59
|
-
font-size: var(--font-
|
|
60
|
-
font-weight: var(--font-
|
|
61
|
-
line-height: var(--font-
|
|
62
|
-
letter-spacing: var(--font-
|
|
61
|
+
font-family: var(--font-heading-3-family);
|
|
62
|
+
font-size: var(--font-heading-3-size);
|
|
63
|
+
font-weight: var(--font-heading-3-weight);
|
|
64
|
+
line-height: var(--font-heading-3-line-height);
|
|
65
|
+
letter-spacing: var(--font-heading-3-letter-spacing);
|
|
63
66
|
color: var(--color-text-primary);
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -80,7 +83,7 @@
|
|
|
80
83
|
============================================ */
|
|
81
84
|
|
|
82
85
|
.ds-interrupt-card__detail {
|
|
83
|
-
padding: 0 var(--padding-
|
|
86
|
+
padding: 0 var(--padding-lg) var(--padding-md);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
.ds-interrupt-card__detail > :first-child {
|
|
@@ -101,10 +104,9 @@
|
|
|
101
104
|
.ds-interrupt-card__options {
|
|
102
105
|
display: flex;
|
|
103
106
|
align-items: center;
|
|
104
|
-
justify-content: flex-
|
|
107
|
+
justify-content: flex-start;
|
|
105
108
|
gap: var(--gap-sm);
|
|
106
|
-
padding: var(--padding-sm-
|
|
107
|
-
border-top: var(--border-xs) solid var(--color-divider);
|
|
109
|
+
padding: var(--padding-sm) var(--padding-lg) var(--padding-md);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
/* ============================================
|
|
@@ -116,10 +118,9 @@
|
|
|
116
118
|
.ds-interrupt-card__answer {
|
|
117
119
|
display: flex;
|
|
118
120
|
align-items: center;
|
|
119
|
-
justify-content: flex-
|
|
121
|
+
justify-content: flex-start;
|
|
120
122
|
gap: var(--gap-xs);
|
|
121
|
-
padding: var(--padding-sm-
|
|
122
|
-
border-top: var(--border-xs) solid var(--color-divider);
|
|
123
|
+
padding: var(--padding-sm) var(--padding-lg) var(--padding-md);
|
|
123
124
|
font-family: var(--font-paragraph-sm-family);
|
|
124
125
|
font-size: var(--font-paragraph-sm-size);
|
|
125
126
|
font-weight: var(--font-paragraph-sm-weight);
|
|
@@ -152,3 +153,12 @@
|
|
|
152
153
|
white-space: nowrap;
|
|
153
154
|
border: 0;
|
|
154
155
|
}
|
|
156
|
+
|
|
157
|
+
/* Icons take the colour of the text around them. Explicit, not just
|
|
158
|
+
inherited by default: a consumer stylesheet that sets a colour on
|
|
159
|
+
.material-symbols-rounded itself (a common global default) would
|
|
160
|
+
otherwise override inheritance and detach the icon from the state
|
|
161
|
+
colours this component sets on its parents. */
|
|
162
|
+
.ds-interrupt-card .material-symbols-rounded {
|
|
163
|
+
color: inherit;
|
|
164
|
+
}
|
|
@@ -14,7 +14,7 @@ type InterruptCardOwnProps = {
|
|
|
14
14
|
title: string;
|
|
15
15
|
/** One line of context under the title. */
|
|
16
16
|
description?: string;
|
|
17
|
-
/** Leading Material Symbol name, or any custom element. */
|
|
17
|
+
/** Leading Material Symbol name, or any custom element. No icon by default. */
|
|
18
18
|
icon?: string | React.ReactNode;
|
|
19
19
|
/** The choices, rendered as buttons left to right. */
|
|
20
20
|
options?: InterruptCardOption[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
MAP CALLOUT COMPONENT
|
|
3
|
+
The annotation beside a marker: a name in
|
|
4
|
+
capitals over readout lines in the code face.
|
|
5
|
+
============================================ */
|
|
6
|
+
|
|
7
|
+
/* Base */
|
|
8
|
+
|
|
9
|
+
.ds-map-callout {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--gap-xxs);
|
|
13
|
+
font-family: var(--font-family-code);
|
|
14
|
+
color: var(--color-text-primary);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ds-map-callout--start {
|
|
18
|
+
align-items: flex-start;
|
|
19
|
+
text-align: left;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ds-map-callout--end {
|
|
23
|
+
align-items: flex-end;
|
|
24
|
+
text-align: right;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/* Parts */
|
|
28
|
+
|
|
29
|
+
/* The overline face on the code family: the metrics and tracking are the
|
|
30
|
+
uppercase-label tokens, the family stays monospace with the readouts. */
|
|
31
|
+
.ds-map-callout__title {
|
|
32
|
+
font-size: var(--font-overline-size);
|
|
33
|
+
font-weight: var(--font-overline-weight);
|
|
34
|
+
line-height: var(--font-overline-line-height);
|
|
35
|
+
letter-spacing: var(--font-overline-letter-spacing);
|
|
36
|
+
text-transform: uppercase;
|
|
37
|
+
color: var(--color-text-primary);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ds-map-callout__line {
|
|
41
|
+
font-size: var(--font-caption-size);
|
|
42
|
+
font-weight: var(--font-caption-weight);
|
|
43
|
+
line-height: var(--font-caption-line-height);
|
|
44
|
+
color: var(--color-text-tertiary);
|
|
45
|
+
font-variant-numeric: tabular-nums;
|
|
46
|
+
}
|