@djangocfg/ui-tools 2.1.431 → 2.1.432
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/package.json +28 -14
- package/src/common/CodePanelHeader/CodePanelHeader.tsx +121 -0
- package/src/common/CodePanelHeader/index.ts +1 -0
- package/src/common/blocks/BlockError.tsx +46 -0
- package/src/common/blocks/index.ts +34 -0
- package/src/common/blocks/schemas.ts +143 -0
- package/src/common/blocks/types.ts +76 -0
- package/src/common/chips/README.md +67 -0
- package/src/common/chips/UrlChip.tsx +105 -0
- package/src/common/chips/index.ts +20 -0
- package/src/common/link-preview/LinkPreviewCard.tsx +178 -0
- package/src/common/link-preview/index.ts +12 -0
- package/src/common/link-preview/types.ts +44 -0
- package/src/common/tiptap/index.ts +10 -0
- package/src/common/tiptap/useNodeAttrs.ts +128 -0
- package/src/lib/page-snapshot/react/PageSnapshotChip.tsx +1 -1
- package/src/tools/chat/README.md +88 -2
- package/src/tools/chat/composer/Composer.tsx +45 -12
- package/src/tools/chat/composer/ComposerRichTextarea.tsx +16 -4
- package/src/tools/chat/constants.ts +9 -2
- package/src/tools/chat/core/extractFirstUrl.ts +45 -0
- package/src/tools/chat/core/reducer.ts +32 -8
- package/src/tools/chat/core/transport/mappers/pydantic-ai.ts +1 -1
- package/src/tools/chat/core/transport/mock.ts +4 -1
- package/src/tools/chat/hooks/useChat.ts +20 -20
- package/src/tools/chat/lazy.tsx +10 -0
- package/src/tools/chat/messages/Attachments.tsx +267 -42
- package/src/tools/chat/messages/BubbleContextMenu.tsx +203 -0
- package/src/tools/chat/messages/MessageBubble.tsx +175 -31
- package/src/tools/chat/messages/MessageList.tsx +107 -21
- package/src/tools/chat/messages/ToolCalls.tsx +28 -13
- package/src/tools/chat/messages/blocks/MessageBlocks.tsx +70 -17
- package/src/tools/chat/messages/blocks/builtin.tsx +6 -3
- package/src/tools/chat/messages/blocks/renderers/AudioBlock.tsx +6 -0
- package/src/tools/chat/messages/blocks/renderers/ChatImageAlbum.tsx +154 -0
- package/src/tools/chat/messages/blocks/renderers/CodeBlock.tsx +94 -6
- package/src/tools/chat/messages/blocks/renderers/DiffBlock.tsx +29 -0
- package/src/tools/chat/messages/blocks/renderers/GalleryBlock.tsx +17 -10
- package/src/tools/chat/messages/blocks/renderers/ImageBlock.tsx +37 -16
- package/src/tools/chat/messages/blocks/renderers/JsonBlock.tsx +11 -6
- package/src/tools/chat/messages/blocks/renderers/LinkBlock.tsx +32 -0
- package/src/tools/chat/messages/blocks/renderers/MapBlock.tsx +181 -19
- package/src/tools/chat/messages/blocks/renderers/VideoBlock.tsx +2 -5
- package/src/tools/chat/messages/index.ts +13 -0
- package/src/tools/chat/public.ts +5 -1
- package/src/tools/chat/shell/ChatRoot.tsx +29 -0
- package/src/tools/chat/styles/bubbleTokens.ts +18 -0
- package/src/tools/chat/styles/index.ts +1 -0
- package/src/tools/chat/types/attachment.ts +26 -2
- package/src/tools/chat/types/block.ts +56 -9
- package/src/tools/chat/types/blockSchemas.ts +197 -0
- package/src/tools/chat/types/config.ts +33 -0
- package/src/tools/chat/types/events.ts +14 -7
- package/src/tools/chat/types/index.ts +4 -2
- package/src/tools/chat/types/message.ts +7 -0
- package/src/tools/data/JsonTree/JsonViewer.tsx +7 -7
- package/src/tools/dev/Map/README.md +30 -345
- package/src/tools/dev/Map/cards/index.ts +6 -0
- package/src/tools/dev/Map/cards/types.ts +67 -0
- package/src/tools/dev/Map/components/CompassChip.tsx +67 -0
- package/src/tools/dev/Map/components/DraggableMarkers.tsx +79 -0
- package/src/tools/dev/Map/components/GeocoderControl.tsx +148 -63
- package/src/tools/dev/Map/components/GeocoderInput.tsx +181 -0
- package/src/tools/dev/Map/components/MapContainer/FullscreenDialog.tsx +140 -0
- package/src/tools/dev/Map/components/MapContainer/GeolocateHandle.tsx +70 -0
- package/src/tools/dev/Map/components/MapContainer/MapInner.tsx +326 -0
- package/src/tools/dev/Map/components/MapContainer/MapToolbar.tsx +286 -0
- package/src/tools/dev/Map/components/MapContainer/RotateHint.tsx +25 -0
- package/src/tools/dev/Map/components/MapContainer/ScrollLockOverlay.tsx +52 -0
- package/src/tools/dev/Map/components/MapContainer/constants.ts +15 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useAutoReset.ts +51 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useBasemapState.ts +48 -0
- package/src/tools/dev/Map/components/MapContainer/hooks/useExternalMapsUrl.ts +29 -0
- package/src/tools/dev/Map/components/MapContainer/index.tsx +29 -0
- package/src/tools/dev/Map/components/MapContainer/types.ts +198 -0
- package/src/tools/dev/Map/components/MapControls.tsx +27 -0
- package/src/tools/dev/Map/components/MapMarkers.tsx +94 -0
- package/src/tools/dev/Map/components/MarkerCard.tsx +102 -0
- package/src/tools/dev/Map/components/MeasureControl.tsx +219 -0
- package/src/tools/dev/Map/components/index.ts +35 -1
- package/src/tools/dev/Map/geocode/index.ts +2 -0
- package/src/tools/dev/Map/geocode/photon.ts +265 -0
- package/src/tools/dev/Map/hooks/index.ts +37 -0
- package/src/tools/dev/Map/hooks/useCompass.ts +44 -0
- package/src/tools/dev/Map/hooks/useDraggableMarkers.ts +112 -0
- package/src/tools/dev/Map/hooks/useGeocoder.ts +155 -0
- package/src/tools/dev/Map/hooks/useGeolocation.ts +174 -0
- package/src/tools/dev/Map/hooks/useMapControl.ts +5 -1
- package/src/tools/dev/Map/hooks/useMapImages.ts +112 -0
- package/src/tools/dev/Map/hooks/useMapScrollProtection.ts +155 -0
- package/src/tools/dev/Map/hooks/useMapTouchMode.ts +21 -0
- package/src/tools/dev/Map/hooks/useMarkerCard.ts +39 -0
- package/src/tools/dev/Map/hooks/useMarkers.ts +9 -3
- package/src/tools/dev/Map/hooks/useMeasure.ts +169 -0
- package/src/tools/dev/Map/hooks/useTerrain.ts +123 -0
- package/src/tools/dev/Map/index.ts +59 -2
- package/src/tools/dev/Map/layers/index.ts +1 -0
- package/src/tools/dev/Map/layers/symbol.ts +93 -0
- package/src/tools/dev/Map/lazy.tsx +120 -0
- package/src/tools/dev/Map/protocols/pmtiles.ts +73 -0
- package/src/tools/dev/Map/styles/index.ts +62 -0
- package/src/tools/dev/Map/styles/terrain.ts +29 -0
- package/src/tools/dev/Map/types.ts +31 -3
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +5 -12
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/SendButton.tsx +1 -1
- package/src/tools/dev/api/OpenapiViewer/components/shared/http-colors.ts +61 -0
- package/src/tools/dev/api/OpenapiViewer/components/shared/ui.tsx +4 -21
- package/src/tools/dev/code/MarkdownMessage/MarkdownMessage.tsx +150 -58
- package/src/tools/dev/code/MarkdownMessage/README.md +38 -0
- package/src/tools/dev/code/MarkdownMessage/builtinRules.tsx +92 -0
- package/src/tools/dev/code/MarkdownMessage/index.ts +3 -0
- package/src/tools/dev/code/MarkdownMessage/streamingBlocks.ts +164 -0
- package/src/tools/dev/code/MarkdownMessage/types.ts +20 -0
- package/src/tools/dev/code/PrettyCode/PrettyCode.client.tsx +27 -30
- package/src/tools/dev/code/PrettyCode/index.tsx +4 -1
- package/src/tools/dev/code/PrettyCode/lazy.tsx +3 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +168 -3
- package/src/tools/forms/MarkdownEditor/README.md +84 -0
- package/src/tools/forms/MarkdownEditor/chip/ChipNode.ts +454 -0
- package/src/tools/forms/MarkdownEditor/chip/syncChips.ts +80 -0
- package/src/tools/forms/MarkdownEditor/filePath/FilePathChip.tsx +68 -0
- package/src/tools/forms/MarkdownEditor/filePath/__tests__/detect.test.ts +161 -0
- package/src/tools/forms/MarkdownEditor/filePath/detect.ts +289 -0
- package/src/tools/forms/MarkdownEditor/filePath/index.ts +15 -0
- package/src/tools/forms/MarkdownEditor/index.ts +19 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +10 -0
- package/src/tools/forms/MarkdownEditor/styles.css +134 -2
- package/src/tools/forms/MarkdownEditor/url/__tests__/detect.test.ts +121 -0
- package/src/tools/forms/MarkdownEditor/url/detect.ts +227 -0
- package/src/tools/forms/NotionEditor/BookmarkNode.ts +158 -0
- package/src/tools/forms/NotionEditor/BookmarkView.tsx +45 -0
- package/src/tools/forms/NotionEditor/MapEditLayer.tsx +199 -0
- package/src/tools/forms/NotionEditor/MapNode.ts +218 -0
- package/src/tools/forms/NotionEditor/MapView.tsx +103 -0
- package/src/tools/forms/NotionEditor/NotionEditor.tsx +7 -2
- package/src/tools/forms/NotionEditor/README.md +109 -188
- package/src/tools/forms/NotionEditor/extensions.ts +15 -0
- package/src/tools/forms/NotionEditor/slashItems.ts +19 -0
- package/src/tools/forms/NotionEditor/styles.css +25 -0
- package/src/tools/forms/NotionEditor/types.ts +9 -0
- package/src/tools/input/SpeechRecognition/core/engine/external.ts +5 -0
- package/src/tools/input/SpeechRecognition/types.ts +10 -0
- package/src/tools/media/Gallery/components/lightbox/GalleryLightbox.tsx +39 -22
- package/src/tools/media/ImageViewer/README.md +9 -0
- package/src/tools/media/ImageViewer/components/ImageViewer.tsx +165 -79
- package/src/tools/media/ImageViewer/components/LightboxChrome.tsx +137 -0
- package/src/tools/media/ImageViewer/components/MiddleEllipsis.tsx +46 -0
- package/src/tools/media/ImageViewer/components/index.ts +2 -0
- package/src/tools/media/ImageViewer/hooks/index.ts +3 -0
- package/src/tools/media/ImageViewer/hooks/useIdleChrome.ts +73 -0
- package/src/tools/media/ImageViewer/types.ts +10 -0
- package/src/tools/media/ImageViewer/utils/constants.ts +28 -0
- package/src/tools/media/ImageViewer/utils/index.ts +4 -0
- package/src/tools/media/VideoPlayer/VideoPlayer.tsx +34 -40
- package/src/tools/media/VideoPlayer/canvas/youtube-canvas.tsx +13 -13
- package/src/tools/media/VideoPlayer/parts/center-play.tsx +26 -0
- package/src/tools/media/VideoPlayer/parts/index.ts +2 -0
- package/src/tools/media/VideoPlayer/styles/video-player.css +102 -53
- package/src/tools/visual/charts/SmoothLine/README.md +97 -0
- package/src/tools/visual/charts/SmoothLine/SmoothLine.tsx +177 -0
- package/src/tools/visual/charts/SmoothLine/index.ts +21 -0
- package/src/tools/visual/charts/SmoothLine/lazy.tsx +23 -0
- package/src/tools/visual/charts/SmoothLine/smoothPath.ts +209 -0
- package/src/tools/visual/charts/SmoothLine/types.ts +98 -0
- package/src/tools/visual/charts/SmoothLine/useSmoothSeries.ts +429 -0
- package/src/tools/visual/indicators/Fps/types.ts +1 -1
- package/src/tools/chat/messages/blocks/renderers/LottieBlock.tsx +0 -11
- package/src/tools/dev/Map/components/MapContainer.tsx +0 -198
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { LayerProps, SymbolLayerSpecification } from 'react-map-gl/maplibre'
|
|
2
|
+
import type { SymbolLayerOptions } from '../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `layout` / `paint` on `SymbolLayerSpecification` are declared optional
|
|
6
|
+
* (`layout?: {…}`), so `SymbolLayerSpecification['layout']` widens to
|
|
7
|
+
* `{…} | undefined`. Indexing that union per-key (e.g.
|
|
8
|
+
* `SymbolLayerSpecification['layout']['icon-image']`) is what leaks
|
|
9
|
+
* TS2339 into stricter consumer typechecks (cmdop's `tsc --noEmit`).
|
|
10
|
+
* Strip the `| undefined` once here so the rest of the file builds a
|
|
11
|
+
* single, fully-typed object literal instead of per-key indexed casts.
|
|
12
|
+
*/
|
|
13
|
+
type SymbolLayout = NonNullable<SymbolLayerSpecification['layout']>
|
|
14
|
+
type SymbolPaint = NonNullable<SymbolLayerSpecification['paint']>
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Create a `symbol` layer that draws a registered icon (and optional text
|
|
18
|
+
* label) per feature — the data-driven path for many icons at scale.
|
|
19
|
+
*
|
|
20
|
+
* Register the icon image first with the `useMapImages` hook, then pass its
|
|
21
|
+
* id (or a data-driven expression like `['get', 'icon']`) as `iconImage`.
|
|
22
|
+
*
|
|
23
|
+
* Two ways to do custom markers:
|
|
24
|
+
* - **For a few rich markers**, use a DOM `MapMarker` with `children` (or its
|
|
25
|
+
* `color`/`size` props) — full React content per marker.
|
|
26
|
+
* - **For many data-driven icons at scale**, use this symbol layer over a
|
|
27
|
+
* GeoJSON source: MapLibre renders the icons on the GPU from the sprite.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* useMapImages([{ id: 'pin', url: '/icons/pin.png' }])
|
|
32
|
+
* const layer = createSymbolLayer({
|
|
33
|
+
* id: 'places',
|
|
34
|
+
* sourceId: 'places-src',
|
|
35
|
+
* iconImage: 'pin',
|
|
36
|
+
* iconSize: 0.5,
|
|
37
|
+
* textField: ['get', 'name'],
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function createSymbolLayer({
|
|
42
|
+
id,
|
|
43
|
+
sourceId,
|
|
44
|
+
iconImage,
|
|
45
|
+
iconSize,
|
|
46
|
+
iconAllowOverlap = true,
|
|
47
|
+
textField,
|
|
48
|
+
textSize = 12,
|
|
49
|
+
textColor = '#1f2937',
|
|
50
|
+
textOffset = [0, 1.2],
|
|
51
|
+
minZoom,
|
|
52
|
+
maxZoom,
|
|
53
|
+
}: SymbolLayerOptions): LayerProps {
|
|
54
|
+
// Build one fully-typed `layout` literal up front (all keys present-or-
|
|
55
|
+
// omitted), so TS checks it as a unit against `SymbolLayout` rather than
|
|
56
|
+
// assigning per-key into a `… | undefined` indexed type. The option
|
|
57
|
+
// values are the author's loosened `DataDrivenValue<…>` (static value OR
|
|
58
|
+
// expression array); each is asserted to its concrete layout-property
|
|
59
|
+
// type via the non-nullable `SymbolLayout[…]` indexed access.
|
|
60
|
+
const labelKeys: Partial<SymbolLayout> =
|
|
61
|
+
textField !== undefined
|
|
62
|
+
? {
|
|
63
|
+
'text-field': textField as SymbolLayout['text-field'],
|
|
64
|
+
'text-size': textSize as SymbolLayout['text-size'],
|
|
65
|
+
'text-offset': textOffset,
|
|
66
|
+
'text-anchor': 'top',
|
|
67
|
+
}
|
|
68
|
+
: {}
|
|
69
|
+
|
|
70
|
+
const layout: SymbolLayout = {
|
|
71
|
+
'icon-image': iconImage as SymbolLayout['icon-image'],
|
|
72
|
+
'icon-allow-overlap': iconAllowOverlap,
|
|
73
|
+
...(iconSize !== undefined
|
|
74
|
+
? { 'icon-size': iconSize as SymbolLayout['icon-size'] }
|
|
75
|
+
: {}),
|
|
76
|
+
...labelKeys,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const paint: SymbolPaint = textField !== undefined ? { 'text-color': textColor } : {}
|
|
80
|
+
|
|
81
|
+
const layer: LayerProps = {
|
|
82
|
+
id,
|
|
83
|
+
type: 'symbol',
|
|
84
|
+
source: sourceId,
|
|
85
|
+
layout,
|
|
86
|
+
paint,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (minZoom !== undefined) layer.minzoom = minZoom
|
|
90
|
+
if (maxZoom !== undefined) layer.maxzoom = maxZoom
|
|
91
|
+
|
|
92
|
+
return layer
|
|
93
|
+
}
|
|
@@ -78,19 +78,50 @@ export {
|
|
|
78
78
|
CustomOverlay,
|
|
79
79
|
MapLegend,
|
|
80
80
|
LayerSwitcher,
|
|
81
|
+
MeasureControl,
|
|
82
|
+
DraggableMarkers,
|
|
83
|
+
GeocoderInput,
|
|
84
|
+
GeocoderControl,
|
|
85
|
+
CompassChip,
|
|
86
|
+
MarkerCard,
|
|
87
|
+
MapMarkers,
|
|
81
88
|
} from './components';
|
|
82
89
|
|
|
90
|
+
export type { DraggableMarkersProps, DraggablePoint } from './components';
|
|
91
|
+
export type { GeocoderInputProps, GeocoderControlProps } from './components';
|
|
92
|
+
export type { CompassChipProps } from './components';
|
|
93
|
+
// Marker info-cards — light wrappers (no maplibre at module scope), safe on
|
|
94
|
+
// the synchronous surface. `MarkerCard` data type lives in `./cards`.
|
|
95
|
+
export type {
|
|
96
|
+
MarkerCardProps,
|
|
97
|
+
MapMarkersProps,
|
|
98
|
+
MarkerWithCard,
|
|
99
|
+
MarkerCardAction,
|
|
100
|
+
MarkerCardData,
|
|
101
|
+
MarkerCardActionData,
|
|
102
|
+
} from './components';
|
|
103
|
+
// Card-data shape under `MarkerCardInfo` (the `MarkerCard` value is the
|
|
104
|
+
// component re-exported above).
|
|
105
|
+
export type { MarkerCard as MarkerCardInfo } from './cards';
|
|
106
|
+
|
|
107
|
+
// Geocoder resolver — pure, key-free Photon (light; no maplibre runtime).
|
|
108
|
+
export { photonResolve, cameraForResult } from './geocode';
|
|
109
|
+
export type { GeocodeResult, GeocodeKind, ResolveGeocode } from './geocode';
|
|
110
|
+
|
|
83
111
|
export { MapProvider, useMapContext, MapContext } from './context';
|
|
84
112
|
export type { MapProviderProps } from './context';
|
|
85
113
|
|
|
86
114
|
export type {
|
|
87
115
|
MapContainerProps,
|
|
116
|
+
GeolocateOptions,
|
|
117
|
+
GeolocatePosition,
|
|
88
118
|
MapMarkerProps,
|
|
89
119
|
MapPopupProps,
|
|
90
120
|
MapClusterProps,
|
|
91
121
|
MapSourceProps,
|
|
92
122
|
MapLayerProps,
|
|
93
123
|
MapControlsProps,
|
|
124
|
+
MeasureControlProps,
|
|
94
125
|
} from './components';
|
|
95
126
|
|
|
96
127
|
export type {
|
|
@@ -98,3 +129,92 @@ export type {
|
|
|
98
129
|
MapStyleKey,
|
|
99
130
|
MarkerData,
|
|
100
131
|
} from './types';
|
|
132
|
+
|
|
133
|
+
// Basemap styles (incl. free OpenFreeMap keys) + PMTiles protocol helper —
|
|
134
|
+
// light, safe to load synchronously alongside the lazy container.
|
|
135
|
+
export {
|
|
136
|
+
MAP_STYLES,
|
|
137
|
+
getMapStyle,
|
|
138
|
+
MAP_STYLE_LABELS,
|
|
139
|
+
KEY_FREE_STYLES,
|
|
140
|
+
resolveBasemapOptions,
|
|
141
|
+
TERRAIN_DEM,
|
|
142
|
+
TERRAIN_DEM_URL,
|
|
143
|
+
} from './styles';
|
|
144
|
+
export { addPMTilesProtocol, pmtilesSourceUrl } from './protocols/pmtiles';
|
|
145
|
+
|
|
146
|
+
// Hooks — pure React glue (maplibre is a TYPE-only import here, so these
|
|
147
|
+
// add no runtime weight and are safe on the synchronous lazy surface).
|
|
148
|
+
export {
|
|
149
|
+
useMap,
|
|
150
|
+
useMapControl,
|
|
151
|
+
useMarkers,
|
|
152
|
+
useMapEvents,
|
|
153
|
+
useMapViewport,
|
|
154
|
+
useMapLayers,
|
|
155
|
+
useSpiderfy,
|
|
156
|
+
useMapTouchMode,
|
|
157
|
+
useMapScrollProtection,
|
|
158
|
+
useMapImages,
|
|
159
|
+
useMeasure,
|
|
160
|
+
useTerrain,
|
|
161
|
+
useCompass,
|
|
162
|
+
useGeolocation,
|
|
163
|
+
useControl,
|
|
164
|
+
useDraggableMarkers,
|
|
165
|
+
useGeocoder,
|
|
166
|
+
useMarkerCard,
|
|
167
|
+
type UseMarkerCardResult,
|
|
168
|
+
type UseCompassResult,
|
|
169
|
+
type UseGeocoderOptions,
|
|
170
|
+
type UseGeocoderResult,
|
|
171
|
+
type UseSpiderfyOptions,
|
|
172
|
+
type UseSpiderfyResult,
|
|
173
|
+
type MapImage,
|
|
174
|
+
type UseMapImagesResult,
|
|
175
|
+
type UseMeasureResult,
|
|
176
|
+
type MeasureMode,
|
|
177
|
+
type UseTerrainOptions,
|
|
178
|
+
type GeolocationFix,
|
|
179
|
+
type GeolocationStatus,
|
|
180
|
+
type UseGeolocationOptions,
|
|
181
|
+
type UseGeolocationResult,
|
|
182
|
+
type UseDraggableMarkersOptions,
|
|
183
|
+
type UseDraggableMarkersResult,
|
|
184
|
+
type DraggableMarkerHandlers,
|
|
185
|
+
} from './hooks';
|
|
186
|
+
|
|
187
|
+
// Layer factories — pure functions returning `LayerProps` (type-only deps).
|
|
188
|
+
export {
|
|
189
|
+
createClusterLayers,
|
|
190
|
+
createPointLayer,
|
|
191
|
+
createHeatmapLayer,
|
|
192
|
+
createPolygonLayer,
|
|
193
|
+
createPolygonOutlineLayer,
|
|
194
|
+
createHighlightLayer,
|
|
195
|
+
createLineLayer,
|
|
196
|
+
createRouteLayers,
|
|
197
|
+
createDashedLineLayer,
|
|
198
|
+
createAnimatedLineLayer,
|
|
199
|
+
createSymbolLayer,
|
|
200
|
+
} from './layers';
|
|
201
|
+
|
|
202
|
+
// Geometry + spiderfy utils — pure, dependency-free helpers.
|
|
203
|
+
export {
|
|
204
|
+
calculateBounds,
|
|
205
|
+
calculateCenter,
|
|
206
|
+
calculateDistance,
|
|
207
|
+
isPointInBounds,
|
|
208
|
+
expandBounds,
|
|
209
|
+
toGeoJSON,
|
|
210
|
+
fromGeoJSON,
|
|
211
|
+
createPoint,
|
|
212
|
+
createPolygon,
|
|
213
|
+
createLineString,
|
|
214
|
+
createFeatureCollection,
|
|
215
|
+
offsetOverlappingMarkers,
|
|
216
|
+
getSpiderfyPositions,
|
|
217
|
+
groupOverlappingMarkers,
|
|
218
|
+
hasOverlappingMarkers,
|
|
219
|
+
getOverlapStats,
|
|
220
|
+
} from './utils';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PMTiles protocol registration for MapLibre.
|
|
5
|
+
*
|
|
6
|
+
* PMTiles is a single-file basemap format (Protomaps, BSD + ODbL) that
|
|
7
|
+
* MapLibre reads DIRECTLY in the browser via HTTP range requests — no tile
|
|
8
|
+
* server, no API key. Host one `.pmtiles` file on any static CDN (S3/R2/
|
|
9
|
+
* GitHub Pages) for a self-hosted / offline basemap.
|
|
10
|
+
*
|
|
11
|
+
* Usage (register ONCE per app lifecycle, e.g. in a top-level effect):
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import maplibregl from 'maplibre-gl';
|
|
15
|
+
* import { addPMTilesProtocol } from '@djangocfg/ui-tools/map';
|
|
16
|
+
*
|
|
17
|
+
* useEffect(() => addPMTilesProtocol(maplibregl), []); // returns a cleanup fn
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Then reference the file from a style/source with a `pmtiles://` URL:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // source url: pmtiles://https://your-cdn.example/basemap.pmtiles
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The `pmtiles` package is dynamically imported so it costs nothing until a
|
|
27
|
+
* consumer actually opts into PMTiles.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Minimal shape of the maplibre-gl module we touch (avoids a hard dep). */
|
|
31
|
+
interface MapLibreLike {
|
|
32
|
+
addProtocol: (
|
|
33
|
+
name: string,
|
|
34
|
+
handler: (...args: unknown[]) => unknown,
|
|
35
|
+
) => void
|
|
36
|
+
removeProtocol: (name: string) => void
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const PROTOCOL = 'pmtiles'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register the `pmtiles://` protocol on the given maplibre-gl module.
|
|
43
|
+
* Returns a cleanup function that removes it — wire it as an effect's
|
|
44
|
+
* teardown. Safe to call once; calling again re-registers (idempotent at
|
|
45
|
+
* the maplibre level, last handler wins).
|
|
46
|
+
*/
|
|
47
|
+
export function addPMTilesProtocol(maplibregl: MapLibreLike): () => void {
|
|
48
|
+
let cancelled = false
|
|
49
|
+
|
|
50
|
+
// Dynamic import keeps `pmtiles` out of the bundle until used.
|
|
51
|
+
void import('pmtiles').then(({ Protocol }) => {
|
|
52
|
+
if (cancelled) return
|
|
53
|
+
const protocol = new Protocol()
|
|
54
|
+
maplibregl.addProtocol(
|
|
55
|
+
PROTOCOL,
|
|
56
|
+
protocol.tile as (...args: unknown[]) => unknown,
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true
|
|
62
|
+
try {
|
|
63
|
+
maplibregl.removeProtocol(PROTOCOL)
|
|
64
|
+
} catch {
|
|
65
|
+
// protocol may not have registered yet (import still in flight)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Build a `pmtiles://` source URL from a plain https URL to a `.pmtiles` file. */
|
|
71
|
+
export function pmtilesSourceUrl(fileUrl: string): string {
|
|
72
|
+
return `${PROTOCOL}://${fileUrl}`
|
|
73
|
+
}
|
|
@@ -1,8 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in basemap styles.
|
|
3
|
+
*
|
|
4
|
+
* `light` / `dark` / `streets` — CARTO, free, no API key.
|
|
5
|
+
* `satellite` — MapTiler, **needs an API key** in the URL (not injected
|
|
6
|
+
* here; pass your own keyed style URL to `mapStyle` to use it).
|
|
7
|
+
* `liberty` / `bright` / `positron` — **OpenFreeMap** public instance:
|
|
8
|
+
* genuinely free, no key, no limits, commercial use allowed (donation-
|
|
9
|
+
* funded, no SLA). See `@plans/plan14-map-capabilities`.
|
|
10
|
+
*
|
|
11
|
+
* For a self-hosted / offline single-file basemap, use the PMTiles helper
|
|
12
|
+
* (`addPMTilesProtocol`) and pass a `pmtiles://…`-backed style instead.
|
|
13
|
+
*/
|
|
14
|
+
// Free DEM source for 3D terrain + hillshade (AWS Terrarium, key-free).
|
|
15
|
+
export { TERRAIN_DEM, TERRAIN_DEM_URL } from './terrain'
|
|
16
|
+
|
|
1
17
|
export const MAP_STYLES = {
|
|
2
18
|
light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
|
|
3
19
|
dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
|
|
4
20
|
streets: 'https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json',
|
|
5
21
|
satellite: 'https://api.maptiler.com/maps/satellite/style.json',
|
|
22
|
+
// OpenFreeMap — key-free, no limits, commercial OK.
|
|
23
|
+
liberty: 'https://tiles.openfreemap.org/styles/liberty',
|
|
24
|
+
bright: 'https://tiles.openfreemap.org/styles/bright',
|
|
25
|
+
positron: 'https://tiles.openfreemap.org/styles/positron',
|
|
6
26
|
} as const
|
|
7
27
|
|
|
8
28
|
export type MapStyleKey = keyof typeof MAP_STYLES
|
|
@@ -13,3 +33,45 @@ export function getMapStyle(key: MapStyleKey | string): string {
|
|
|
13
33
|
}
|
|
14
34
|
return key
|
|
15
35
|
}
|
|
36
|
+
|
|
37
|
+
/** Human label for the basemap switcher menu. */
|
|
38
|
+
export const MAP_STYLE_LABELS: Record<MapStyleKey, string> = {
|
|
39
|
+
light: 'Light',
|
|
40
|
+
dark: 'Dark',
|
|
41
|
+
streets: 'Streets',
|
|
42
|
+
satellite: 'Satellite',
|
|
43
|
+
liberty: 'Liberty',
|
|
44
|
+
bright: 'Bright',
|
|
45
|
+
positron: 'Positron',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Basemap keys that work with NO API key (safe to always offer). */
|
|
49
|
+
export const KEY_FREE_STYLES: readonly MapStyleKey[] = [
|
|
50
|
+
'light',
|
|
51
|
+
'dark',
|
|
52
|
+
'streets',
|
|
53
|
+
'liberty',
|
|
54
|
+
'bright',
|
|
55
|
+
'positron',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Pick the basemap options to show in the switcher, smartly:
|
|
60
|
+
* - all key-free built-ins, PLUS
|
|
61
|
+
* - `satellite` ONLY when usable — i.e. the host passed its own keyed
|
|
62
|
+
* satellite style (a full URL via `mapStyle`/`current`), since the
|
|
63
|
+
* built-in MapTiler satellite needs a key we don't ship.
|
|
64
|
+
*
|
|
65
|
+
* A host can override entirely by passing an explicit `requested` list.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveBasemapOptions(
|
|
68
|
+
requested: readonly MapStyleKey[] | undefined,
|
|
69
|
+
current: MapStyleKey | string | undefined,
|
|
70
|
+
): MapStyleKey[] {
|
|
71
|
+
if (requested && requested.length) return [...requested]
|
|
72
|
+
const opts: MapStyleKey[] = [...KEY_FREE_STYLES]
|
|
73
|
+
// Offer satellite only if the active style is a host-supplied URL (keyed)
|
|
74
|
+
// OR the current key is already 'satellite' (host wired a key elsewhere).
|
|
75
|
+
if (current === 'satellite') opts.push('satellite')
|
|
76
|
+
return opts
|
|
77
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Free DEM (digital-elevation-model) source for 3D terrain + hillshade.
|
|
5
|
+
*
|
|
6
|
+
* Uses **AWS Terrain Tiles** — Terrarium-encoded elevation PNGs served as
|
|
7
|
+
* free, open data from the public `elevation-tiles-prod` S3 bucket. No API
|
|
8
|
+
* key, no account, no usage limit. MapLibre renders 3D terrain and hillshade
|
|
9
|
+
* natively from a `raster-dem` source — no external tile service needed.
|
|
10
|
+
*
|
|
11
|
+
* See `@plans/plan14-map-capabilities` / `reports/research.md` §2.
|
|
12
|
+
*/
|
|
13
|
+
import type { RasterDEMSourceSpecification } from 'maplibre-gl'
|
|
14
|
+
|
|
15
|
+
/** Terrarium-encoded DEM tile URL template (AWS open data, key-free). */
|
|
16
|
+
export const TERRAIN_DEM_URL =
|
|
17
|
+
'https://elevation-tiles-prod.s3.amazonaws.com/terrarium/{z}/{x}/{y}.png'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Ready-to-use `raster-dem` source spec for the free AWS Terrarium DEM.
|
|
21
|
+
* Pass straight to `map.addSource(id, TERRAIN_DEM)`.
|
|
22
|
+
*/
|
|
23
|
+
export const TERRAIN_DEM: RasterDEMSourceSpecification = {
|
|
24
|
+
type: 'raster-dem',
|
|
25
|
+
tiles: [TERRAIN_DEM_URL],
|
|
26
|
+
encoding: 'terrarium',
|
|
27
|
+
tileSize: 256,
|
|
28
|
+
maxzoom: 15,
|
|
29
|
+
}
|
|
@@ -65,7 +65,7 @@ export interface MapContextValue {
|
|
|
65
65
|
export interface MapControlActions {
|
|
66
66
|
flyTo: (center: [number, number], zoom?: number, options?: { duration?: number }) => void
|
|
67
67
|
easeTo: (center: [number, number], zoom?: number, options?: { duration?: number }) => void
|
|
68
|
-
fitBounds: (bounds: LngLatBoundsLike, options?: { padding?: number; duration?: number }) => void
|
|
68
|
+
fitBounds: (bounds: LngLatBoundsLike, options?: { padding?: number; duration?: number; maxZoom?: number }) => void
|
|
69
69
|
zoomIn: () => void
|
|
70
70
|
zoomOut: () => void
|
|
71
71
|
resetView: (initialViewport: MapViewport) => void
|
|
@@ -80,7 +80,7 @@ export interface MarkerActions {
|
|
|
80
80
|
removeMarker: (id: string) => void
|
|
81
81
|
updateMarker: (id: string, updates: Partial<Omit<MarkerData, 'id'>>) => void
|
|
82
82
|
clearMarkers: () => void
|
|
83
|
-
fitToMarkers: (padding?: number) => void
|
|
83
|
+
fitToMarkers: (paddingOrOpts?: number | { padding?: number; maxZoom?: number }) => void
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// ============================================================================
|
|
@@ -108,7 +108,9 @@ export interface MapMouseEvent {
|
|
|
108
108
|
// Style Types
|
|
109
109
|
// ============================================================================
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
// Single source of truth — derived from `MAP_STYLES` so new basemap keys
|
|
112
|
+
// (e.g. OpenFreeMap `liberty`/`bright`/`positron`) flow through automatically.
|
|
113
|
+
export type { MapStyleKey } from './styles'
|
|
112
114
|
|
|
113
115
|
// ============================================================================
|
|
114
116
|
// Layer Types (from react-map-gl & maplibre-gl)
|
|
@@ -167,6 +169,32 @@ export interface RouteLayerOptions extends Omit<LineLayerOptions, 'id'> {
|
|
|
167
169
|
sourceId: string
|
|
168
170
|
}
|
|
169
171
|
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Symbol Layer Types
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/** A data-driven expression (e.g. `['get', 'icon']`) or a static value. */
|
|
177
|
+
export type DataDrivenValue<T> = T | unknown[]
|
|
178
|
+
|
|
179
|
+
export interface SymbolLayerOptions {
|
|
180
|
+
id: string
|
|
181
|
+
sourceId: string
|
|
182
|
+
/**
|
|
183
|
+
* Registered image id (see `useMapImages`) OR a data-driven expression
|
|
184
|
+
* selecting one per feature, e.g. `['get', 'icon']`.
|
|
185
|
+
*/
|
|
186
|
+
iconImage: DataDrivenValue<string>
|
|
187
|
+
iconSize?: DataDrivenValue<number>
|
|
188
|
+
iconAllowOverlap?: boolean
|
|
189
|
+
/** Property name or expression for an optional text label. */
|
|
190
|
+
textField?: DataDrivenValue<string>
|
|
191
|
+
textSize?: DataDrivenValue<number>
|
|
192
|
+
textColor?: string
|
|
193
|
+
textOffset?: [number, number]
|
|
194
|
+
minZoom?: number
|
|
195
|
+
maxZoom?: number
|
|
196
|
+
}
|
|
197
|
+
|
|
170
198
|
// ============================================================================
|
|
171
199
|
// Control Types
|
|
172
200
|
// ============================================================================
|
package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx
CHANGED
|
@@ -47,7 +47,7 @@ function IconButton({ label, onClick, children, active }: IconButtonProps) {
|
|
|
47
47
|
className={cn(
|
|
48
48
|
'shrink-0 h-6 w-6 inline-flex items-center justify-center rounded',
|
|
49
49
|
'text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors',
|
|
50
|
-
active && 'text-
|
|
50
|
+
active && 'text-success hover:text-success',
|
|
51
51
|
)}
|
|
52
52
|
>
|
|
53
53
|
{children}
|
package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx
CHANGED
|
@@ -2,25 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
4
4
|
|
|
5
|
+
import { getStatusCodeStyle } from '../../../shared/http-colors';
|
|
6
|
+
|
|
5
7
|
interface StatusTagProps {
|
|
6
8
|
code: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
/** HTTP status pill. Colours map to the usual conventions: 2xx
|
|
10
|
-
* 3xx
|
|
11
|
+
/** HTTP status pill. Colours map to the usual conventions: 2xx success,
|
|
12
|
+
* 3xx info, 4xx warning, 5xx destructive, non-numeric (``default``) neutral.
|
|
11
13
|
* Codes that don't parse as integers render in the neutral style so we
|
|
12
14
|
* never flash a wrong-severity colour at the user. */
|
|
13
15
|
export function StatusTag({ code }: StatusTagProps) {
|
|
14
|
-
const
|
|
15
|
-
const cls = !Number.isFinite(numeric)
|
|
16
|
-
? 'bg-muted text-muted-foreground border-border'
|
|
17
|
-
: numeric >= 500
|
|
18
|
-
? 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25'
|
|
19
|
-
: numeric >= 400
|
|
20
|
-
? 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25'
|
|
21
|
-
: numeric >= 300
|
|
22
|
-
? 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25'
|
|
23
|
-
: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25';
|
|
16
|
+
const cls = getStatusCodeStyle(code);
|
|
24
17
|
return (
|
|
25
18
|
<span className={cn(
|
|
26
19
|
'inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums',
|
package/src/tools/dev/api/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx
CHANGED
|
@@ -77,7 +77,7 @@ export function SectionHeader({ sectionId, title, badge, open, onToggle }: Secti
|
|
|
77
77
|
className={cn(
|
|
78
78
|
'ml-auto shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all',
|
|
79
79
|
'opacity-0 group-hover/section:opacity-100 focus-visible:opacity-100',
|
|
80
|
-
copied && 'opacity-100 text-
|
|
80
|
+
copied && 'opacity-100 text-success',
|
|
81
81
|
)}
|
|
82
82
|
>
|
|
83
83
|
{copied ? <Check className="h-3 w-3" /> : <Link2 className="h-3 w-3" />}
|
|
@@ -167,7 +167,7 @@ export function SchemaCopyMenu({ schema, endpoints, baseUrl, variant = 'button',
|
|
|
167
167
|
<div className="flex w-full items-center gap-2">
|
|
168
168
|
<span className="text-xs font-medium flex-1">{label.title}</span>
|
|
169
169
|
{isDone ? (
|
|
170
|
-
<span className="inline-flex items-center gap-1 text-[10px] text-
|
|
170
|
+
<span className="inline-flex items-center gap-1 text-[10px] text-success">
|
|
171
171
|
<Check className="h-3 w-3" /> Copied
|
|
172
172
|
</span>
|
|
173
173
|
) : size ? (
|
|
@@ -62,7 +62,7 @@ export function SendButton({ className }: SendButtonProps) {
|
|
|
62
62
|
return (
|
|
63
63
|
<div className={cn('space-y-2', className)}>
|
|
64
64
|
{blockers.length > 0 && (
|
|
65
|
-
<div className="flex items-start gap-2 rounded-md border border-
|
|
65
|
+
<div className="flex items-start gap-2 rounded-md border border-warning-border bg-warning-background px-3 py-2 text-[11px] text-warning-foreground">
|
|
66
66
|
<AlertCircle className="h-3.5 w-3.5 shrink-0 mt-px" />
|
|
67
67
|
<span className="leading-snug">{blockers[0]}</span>
|
|
68
68
|
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared HTTP method + status → semantic status-token mappings.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the badge colours used across the OpenAPI
|
|
5
|
+
* viewer (method pills in the request list, status pills in responses).
|
|
6
|
+
* Uses ui-core's semantic status surface tokens (the tinted banner set:
|
|
7
|
+
* `bg-{status}-background` + `text-{status}-foreground` + `border-{status}-border`)
|
|
8
|
+
* so badges adapt to theme + preset instead of hardcoded colour scales.
|
|
9
|
+
*
|
|
10
|
+
* HTTP → status mapping:
|
|
11
|
+
* 2xx / GET → success · 3xx → info · 4xx / PUT / PATCH → warning ·
|
|
12
|
+
* 5xx / DELETE → destructive · POST → info.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type StatusKind = 'success' | 'info' | 'warning' | 'destructive';
|
|
16
|
+
|
|
17
|
+
const BADGE_CLASS: Record<StatusKind, string> = {
|
|
18
|
+
success: 'bg-success-background text-success-foreground border-success-border',
|
|
19
|
+
info: 'bg-info-background text-info-foreground border-info-border',
|
|
20
|
+
warning: 'bg-warning-background text-warning-foreground border-warning-border',
|
|
21
|
+
destructive: 'bg-destructive-background text-destructive-foreground border-destructive-border',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const NEUTRAL_BADGE = 'bg-muted text-muted-foreground border-border';
|
|
25
|
+
|
|
26
|
+
const METHOD_KIND: Record<string, StatusKind> = {
|
|
27
|
+
GET: 'success',
|
|
28
|
+
POST: 'info',
|
|
29
|
+
PUT: 'warning',
|
|
30
|
+
PATCH: 'warning',
|
|
31
|
+
DELETE: 'destructive',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Badge classes for an HTTP method. Unknown methods fall back to neutral. */
|
|
35
|
+
export function getMethodStyle(method: string): string {
|
|
36
|
+
const kind = METHOD_KIND[method.toUpperCase()];
|
|
37
|
+
return kind ? BADGE_CLASS[kind] : NEUTRAL_BADGE;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function statusKind(status: number): StatusKind {
|
|
41
|
+
if (status >= 500) return 'destructive';
|
|
42
|
+
if (status >= 400) return 'warning';
|
|
43
|
+
if (status >= 300) return 'info';
|
|
44
|
+
return 'success';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Badge classes for a numeric HTTP status code. */
|
|
48
|
+
export function getStatusStyle(status: number): string {
|
|
49
|
+
return BADGE_CLASS[statusKind(status)];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Badge classes for an HTTP status *code string* (e.g. ``"200"``,
|
|
54
|
+
* ``"default"``). Non-numeric codes render neutral so we never flash a
|
|
55
|
+
* wrong-severity colour at the user.
|
|
56
|
+
*/
|
|
57
|
+
export function getStatusCodeStyle(code: string): string {
|
|
58
|
+
const numeric = Number.parseInt(code, 10);
|
|
59
|
+
if (!Number.isFinite(numeric)) return NEUTRAL_BADGE;
|
|
60
|
+
return getStatusStyle(numeric);
|
|
61
|
+
}
|
|
@@ -12,27 +12,10 @@ import React from 'react';
|
|
|
12
12
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
13
13
|
|
|
14
14
|
// ─── Style helpers ────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
PUT: 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25',
|
|
20
|
-
PATCH: 'bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25',
|
|
21
|
-
DELETE: 'bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25',
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
const METHOD_FALLBACK = 'bg-muted text-muted-foreground border-border';
|
|
25
|
-
|
|
26
|
-
export function getMethodStyle(method: string): string {
|
|
27
|
-
return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getStatusStyle(status: number): string {
|
|
31
|
-
if (status >= 500) return 'bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25';
|
|
32
|
-
if (status >= 400) return 'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25';
|
|
33
|
-
if (status >= 300) return 'bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25';
|
|
34
|
-
return 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25';
|
|
35
|
-
}
|
|
15
|
+
// Method/status → semantic status-token mappings live in ./http-colors and are
|
|
16
|
+
// re-exported here so existing imports of `./ui` keep working.
|
|
17
|
+
export { getMethodStyle, getStatusStyle } from './http-colors';
|
|
18
|
+
import { getMethodStyle, getStatusStyle } from './http-colors';
|
|
36
19
|
|
|
37
20
|
// ``relativePath`` lives in utils/url.ts now — re-exported here so every
|
|
38
21
|
// component that used to import it from ``./ui`` keeps working.
|