@terrafirmagreg-team/ui 0.0.0-stage → 1.2.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/package.json +93 -3
- package/src/components/blocks/button/icon-button.tsx +49 -0
- package/src/components/blocks/canvas/chevron-edge.tsx +242 -0
- package/src/components/blocks/canvas/flow-canvas.tsx +181 -0
- package/src/components/blocks/canvas/flow-toolbar.tsx +33 -0
- package/src/components/blocks/canvas/flow-viewport.tsx +244 -0
- package/src/components/blocks/fit-box.tsx +26 -0
- package/src/components/blocks/git/github-info.tsx +78 -0
- package/src/components/blocks/lang/content.tsx +52 -0
- package/src/components/blocks/lang/flag.tsx +25 -0
- package/src/components/blocks/lang/link.tsx +23 -0
- package/src/components/blocks/lang/redirect.tsx +48 -0
- package/src/components/blocks/lang/switcher.tsx +54 -0
- package/src/components/blocks/lang/untranslated-callout.tsx +29 -0
- package/src/components/blocks/lang/untranslated-chip.tsx +53 -0
- package/src/components/blocks/link/link-preview.tsx +257 -0
- package/src/components/blocks/list/row.tsx +92 -0
- package/src/components/blocks/media/glb/model.tsx +244 -0
- package/src/components/blocks/media/glb/scene.tsx +373 -0
- package/src/components/blocks/media/glb/viewer.tsx +52 -0
- package/src/components/blocks/media/image.tsx +136 -0
- package/src/components/blocks/media/slideshow.tsx +178 -0
- package/src/components/blocks/nav/breadcrumb.tsx +51 -0
- package/src/components/blocks/nav/copy-menu.tsx +37 -0
- package/src/components/blocks/nav/link.tsx +86 -0
- package/src/components/blocks/nav/site-menu.tsx +78 -0
- package/src/components/blocks/nav/site-title.tsx +61 -0
- package/src/components/blocks/nav/switcher.tsx +206 -0
- package/src/components/blocks/page/empty-state.tsx +32 -0
- package/src/components/blocks/page/heading.tsx +54 -0
- package/src/components/blocks/page/pager.tsx +220 -0
- package/src/components/blocks/readability/fields.tsx +250 -0
- package/src/components/blocks/readability/menu.tsx +247 -0
- package/src/components/blocks/readability/track.tsx +165 -0
- package/src/components/blocks/search/command.tsx +80 -0
- package/src/components/blocks/search/dialog.tsx +724 -0
- package/src/components/blocks/search/provider.tsx +118 -0
- package/src/components/blocks/sidebar.tsx +406 -0
- package/src/components/blocks/sprite.tsx +231 -0
- package/src/components/blocks/text/format-text.tsx +110 -0
- package/src/components/blocks/theme/toggle.tsx +75 -0
- package/src/components/blocks/tooltip/hint.tsx +28 -0
- package/src/components/blocks/tooltip/lazy.tsx +41 -0
- package/src/components/blocks/viewer/controls.tsx +284 -0
- package/src/components/blocks/viewer/float-bar.tsx +51 -0
- package/src/components/layouts/layout.tsx +94 -0
- package/src/components/layouts/left-sidebar/content.tsx +63 -0
- package/src/components/layouts/left-sidebar/context.tsx +55 -0
- package/src/components/layouts/left-sidebar/expand.tsx +23 -0
- package/src/components/layouts/left-sidebar/footer.tsx +111 -0
- package/src/components/layouts/left-sidebar/index.ts +29 -0
- package/src/components/layouts/left-sidebar/items.tsx +103 -0
- package/src/components/layouts/left-sidebar/search.tsx +75 -0
- package/src/components/layouts/left-sidebar/sidebar.tsx +189 -0
- package/src/components/layouts/left-sidebar/tree.tsx +157 -0
- package/src/components/layouts/right-sidebar.tsx +96 -0
- package/src/components/layouts/tree-context.tsx +42 -0
- package/src/components/mdx/accordion.tsx +185 -0
- package/src/components/mdx/banner.tsx +157 -0
- package/src/components/mdx/callout.tsx +97 -0
- package/src/components/mdx/card.tsx +56 -0
- package/src/components/mdx/codeblock.tsx +250 -0
- package/src/components/mdx/collapsible.tsx +33 -0
- package/src/components/mdx/files.tsx +63 -0
- package/src/components/mdx/glow-header.tsx +54 -0
- package/src/components/mdx/gradient-text.tsx +28 -0
- package/src/components/mdx/icon.tsx +35 -0
- package/src/components/mdx/image-zoom.tsx +315 -0
- package/src/components/mdx/math.tsx +30 -0
- package/src/components/mdx/mermaid.tsx +23 -0
- package/src/components/mdx/multiblock.tsx +19 -0
- package/src/components/mdx/preview.tsx +101 -0
- package/src/components/mdx/steps.tsx +10 -0
- package/src/components/mdx/table.tsx +14 -0
- package/src/components/mdx/tabs.tsx +354 -0
- package/src/components/mdx/type-table.tsx +171 -0
- package/src/components/provider.tsx +139 -0
- package/src/components/ui/accordion.tsx +71 -0
- package/src/components/ui/alert-dialog.tsx +187 -0
- package/src/components/ui/alert.tsx +75 -0
- package/src/components/ui/aspect-ratio.tsx +22 -0
- package/src/components/ui/attachment.tsx +207 -0
- package/src/components/ui/avatar.tsx +108 -0
- package/src/components/ui/badge.tsx +51 -0
- package/src/components/ui/breadcrumb.tsx +124 -0
- package/src/components/ui/bubble.tsx +127 -0
- package/src/components/ui/button-group.tsx +87 -0
- package/src/components/ui/button.tsx +57 -0
- package/src/components/ui/calendar.tsx +221 -0
- package/src/components/ui/card.tsx +102 -0
- package/src/components/ui/carousel.tsx +242 -0
- package/src/components/ui/chart.tsx +371 -0
- package/src/components/ui/checkbox.tsx +28 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/combobox.tsx +297 -0
- package/src/components/ui/command.tsx +198 -0
- package/src/components/ui/context-menu.tsx +270 -0
- package/src/components/ui/dialog.tsx +160 -0
- package/src/components/ui/direction.tsx +6 -0
- package/src/components/ui/drawer.tsx +227 -0
- package/src/components/ui/dropdown-menu.tsx +269 -0
- package/src/components/ui/empty.tsx +103 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/hover-card.tsx +50 -0
- package/src/components/ui/input-group.tsx +158 -0
- package/src/components/ui/input-otp.tsx +86 -0
- package/src/components/ui/input.tsx +19 -0
- package/src/components/ui/item.tsx +201 -0
- package/src/components/ui/kbd.tsx +26 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/marker.tsx +70 -0
- package/src/components/ui/menubar.tsx +280 -0
- package/src/components/ui/message-scroller.tsx +131 -0
- package/src/components/ui/message.tsx +91 -0
- package/src/components/ui/native-select.tsx +60 -0
- package/src/components/ui/navigation-menu.tsx +167 -0
- package/src/components/ui/pagination.tsx +130 -0
- package/src/components/ui/popover.tsx +91 -0
- package/src/components/ui/progress.tsx +82 -0
- package/src/components/ui/questionnaire.tsx +324 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/resizable.tsx +49 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +200 -0
- package/src/components/ui/separator.tsx +24 -0
- package/src/components/ui/sheet.tsx +138 -0
- package/src/components/ui/sidebar.tsx +730 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +51 -0
- package/src/components/ui/spinner.tsx +10 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +115 -0
- package/src/components/ui/tabs.tsx +81 -0
- package/src/components/ui/textarea.tsx +17 -0
- package/src/components/ui/toast.tsx +234 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle.tsx +44 -0
- package/src/components/ui/tooltip.tsx +65 -0
- package/src/hooks/canvas/use-flow-hover.ts +19 -0
- package/src/hooks/canvas/use-flow-viewport-persist.ts +34 -0
- package/src/hooks/page/use-fit-pages.ts +92 -0
- package/src/hooks/page/use-location-search.ts +31 -0
- package/src/hooks/page/use-page-preview.ts +41 -0
- package/src/hooks/page/use-paged-list.ts +21 -0
- package/src/hooks/readability/use-readability.ts +87 -0
- package/src/hooks/search/use-dialog.ts +42 -0
- package/src/hooks/search/use-highlight-query.ts +58 -0
- package/src/hooks/search/use-provider.ts +26 -0
- package/src/hooks/use-async-value.ts +60 -0
- package/src/hooks/use-close-on-scroll.ts +24 -0
- package/src/hooks/use-copy-button.ts +47 -0
- package/src/hooks/use-cycle-index.ts +76 -0
- package/src/hooks/use-document-title.ts +10 -0
- package/src/hooks/use-element-size.ts +36 -0
- package/src/hooks/use-external-meta.ts +133 -0
- package/src/hooks/use-hot-key.ts +34 -0
- package/src/hooks/use-hover-follow.ts +55 -0
- package/src/hooks/use-in-view.ts +31 -0
- package/src/hooks/use-lang.ts +9 -0
- package/src/hooks/use-last-value.ts +16 -0
- package/src/hooks/use-layout.ts +19 -0
- package/src/hooks/use-marquee.ts +52 -0
- package/src/hooks/use-media-query.ts +15 -0
- package/src/hooks/use-mobile.ts +20 -0
- package/src/hooks/use-modifier-keys.ts +32 -0
- package/src/hooks/use-on-change.ts +36 -0
- package/src/hooks/use-pane-keys.ts +29 -0
- package/src/hooks/use-sidebar.ts +64 -0
- package/src/hooks/use-swipe.ts +27 -0
- package/src/hooks/use-tree.ts +100 -0
- package/src/hooks/viewer/use-in-float-bar.ts +7 -0
- package/src/hooks/viewer/use-viewer-prefs.ts +78 -0
- package/src/lib/canvas/edge.ts +202 -0
- package/src/lib/canvas/geometry.ts +54 -0
- package/src/lib/canvas/state.ts +48 -0
- package/src/lib/canvas/viewport.ts +38 -0
- package/src/lib/canvas/xyflow.ts +2 -0
- package/src/lib/config.ts +106 -0
- package/src/lib/mdx/callout.ts +21 -0
- package/src/lib/mdx/gradient.ts +16 -0
- package/src/lib/mdx/mermaid.ts +15 -0
- package/src/lib/media/crop-opaque.ts +77 -0
- package/src/lib/media/nearby-indexes.ts +7 -0
- package/src/lib/media/trim-transparent.ts +118 -0
- package/src/lib/readability/config.ts +11 -0
- package/src/lib/readability/spotlight.ts +187 -0
- package/src/lib/readability/store.ts +147 -0
- package/src/lib/tree.ts +90 -0
- package/src/lib/utils/cn.ts +2 -0
- package/src/lib/utils/format.ts +22 -0
- package/src/lib/utils/highlight-style.ts +10 -0
- package/src/lib/utils/history.ts +10 -0
- package/src/lib/utils/merge-refs.ts +14 -0
- package/src/lib/utils/nav-active.ts +59 -0
- package/src/lib/utils/stored-value.ts +137 -0
- package/src/lib/utils/urls.ts +18 -0
- package/src/locales/en_us.json +110 -0
- package/src/locales/pl_pl.json +33 -0
- package/src/locales/ru_ru.json +110 -0
- package/src/locales/uk_ua.json +33 -0
- package/src/locales/zh_cn.json +15 -0
- package/src/locales/zh_tw.json +15 -0
- package/src/locales.ts +6 -0
- package/src/types/canvas.ts +54 -0
- package/src/types/glb.ts +26 -0
- package/src/types/mdx-directives.ts +106 -0
- package/src/types/nav.ts +96 -0
- package/src/types/preview.ts +15 -0
- package/src/types/readability.ts +29 -0
- package/src/types/search.ts +57 -0
- package/src/types/tree.ts +19 -0
- package/src/types/viewer.ts +54 -0
- package/styles/content/base.css +198 -0
- package/styles/content/default-colors.css +51 -0
- package/styles/content/shiki.css +107 -0
- package/styles/content/typography.mjs +3637 -0
- package/styles/content.css +5 -0
- package/styles/image-zoom.css +83 -0
- package/styles/layout.css +28 -0
- package/styles/left-sidebar.css +140 -0
- package/styles/readability.css +50 -0
- package/styles/right-sidebar.css +6 -0
- package/styles/workspace.css +26 -0
- package/styles.css +430 -0
- package/README.md +0 -3
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRef, type TouchEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
const MIN_DX = 48;
|
|
6
|
+
|
|
7
|
+
export function useSwipe(onSwipe: (dir: -1 | 1) => void) {
|
|
8
|
+
const start = useRef<{ x: number; y: number } | null>(null);
|
|
9
|
+
return {
|
|
10
|
+
onTouchStart: (event: TouchEvent) => {
|
|
11
|
+
const touch = event.touches[0];
|
|
12
|
+
if (!touch) return;
|
|
13
|
+
start.current = { x: touch.clientX, y: touch.clientY };
|
|
14
|
+
},
|
|
15
|
+
onTouchEnd: (event: TouchEvent) => {
|
|
16
|
+
const from = start.current;
|
|
17
|
+
start.current = null;
|
|
18
|
+
const touch = event.changedTouches[0];
|
|
19
|
+
if (!from || !touch) return;
|
|
20
|
+
const dx = touch.clientX - from.x;
|
|
21
|
+
const dy = touch.clientY - from.y;
|
|
22
|
+
if (Math.abs(dx) < MIN_DX || Math.abs(dx) < Math.abs(dy) * 1.5) return;
|
|
23
|
+
onSwipe(dx < 0 ? 1 : -1);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useMemo, createContext, use } from 'react';
|
|
3
|
+
import type * as PageTree from '@terrafirmagreg-team/translate/types/tree';
|
|
4
|
+
import type { TabOption, TreeContextValue } from '@terrafirmagreg-team/ui/types/tree';
|
|
5
|
+
import { findProjection } from '@terrafirmagreg-team/ui/lib/tree';
|
|
6
|
+
|
|
7
|
+
export const TreeContext = createContext<TreeContextValue | null>(null);
|
|
8
|
+
export const PathContext = createContext<PageTree.Node[]>([]);
|
|
9
|
+
|
|
10
|
+
export function useTreePath(): PageTree.Node[] {
|
|
11
|
+
return use(PathContext);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function useTreeContext(): TreeContextValue {
|
|
15
|
+
const ctx = use(TreeContext);
|
|
16
|
+
if (!ctx)
|
|
17
|
+
throw new Error('You must wrap this component under <DocsLayout />');
|
|
18
|
+
return ctx;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useTabsGroups(tabs: TabOption[]): {
|
|
22
|
+
active?: PageTree.Folder;
|
|
23
|
+
options: TabOption[];
|
|
24
|
+
}[] {
|
|
25
|
+
const { full: tree } = useTreeContext();
|
|
26
|
+
const path = use(PathContext);
|
|
27
|
+
|
|
28
|
+
return useMemo(() => {
|
|
29
|
+
const out: { active?: PageTree.Folder; options: TabOption[] }[] = [];
|
|
30
|
+
const last = path[path.length - 1];
|
|
31
|
+
const page = last?.type === 'page' ? last : undefined;
|
|
32
|
+
let scope = tree.fallback
|
|
33
|
+
? [...tree.children, ...tree.fallback.children]
|
|
34
|
+
: tree.children;
|
|
35
|
+
|
|
36
|
+
for (const node of path) {
|
|
37
|
+
if (node.type !== 'folder' || !node.root) continue;
|
|
38
|
+
const group = { active: node, options: [] as TabOption[] };
|
|
39
|
+
collectTabs(scope, node, page, tabs, group.options);
|
|
40
|
+
if (group.options.length > 0) out.push(group);
|
|
41
|
+
scope = node.children;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const custom = tabs.filter((tab) => !tab.$folder);
|
|
45
|
+
if (custom.length > 0) {
|
|
46
|
+
const group = out.findLast((group) => group.active?.root === true);
|
|
47
|
+
if (group) group.options.push(...custom);
|
|
48
|
+
else out.push({ options: custom });
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}, [tabs, tree, path]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function collectTabs(
|
|
55
|
+
scope: PageTree.Node[],
|
|
56
|
+
active: PageTree.Folder,
|
|
57
|
+
page: PageTree.Item | undefined,
|
|
58
|
+
tabs: TabOption[],
|
|
59
|
+
out: TabOption[],
|
|
60
|
+
): void {
|
|
61
|
+
for (const node of scope) {
|
|
62
|
+
if (node.type !== 'folder') continue;
|
|
63
|
+
if (node.root === active.root) {
|
|
64
|
+
const tab = tabs.find(
|
|
65
|
+
(tab) =>
|
|
66
|
+
tab.$folder &&
|
|
67
|
+
(tab.$folder === node || tab.$folder.$id === node.$id),
|
|
68
|
+
);
|
|
69
|
+
if (!tab) continue;
|
|
70
|
+
const projection =
|
|
71
|
+
page && findProjection(active, node, page);
|
|
72
|
+
out.push(projection ? { ...tab, url: projection.url } : tab);
|
|
73
|
+
} else if (!node.root) {
|
|
74
|
+
collectTabs(node.children, active, page, tabs, out);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const footerCache = new WeakMap<PageTree.Root | PageTree.Folder, PageTree.Item[]>();
|
|
80
|
+
|
|
81
|
+
export function useFooterItems(): PageTree.Item[] {
|
|
82
|
+
const { root } = useTreeContext();
|
|
83
|
+
const cached = footerCache.get(root);
|
|
84
|
+
if (cached) return cached;
|
|
85
|
+
|
|
86
|
+
const list: PageTree.Item[] = [];
|
|
87
|
+
function onNode(node: PageTree.Node): void {
|
|
88
|
+
if (node.type === 'folder') {
|
|
89
|
+
if (node.index) onNode(node.index);
|
|
90
|
+
for (const child of node.children) onNode(child);
|
|
91
|
+
} else if (node.type === 'page' && !node.external) {
|
|
92
|
+
list.push(node);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const child of root.children) onNode(child);
|
|
96
|
+
|
|
97
|
+
footerCache.set(root, list);
|
|
98
|
+
return list;
|
|
99
|
+
}
|
|
100
|
+
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
boolCodec,
|
|
5
|
+
createStoredValue,
|
|
6
|
+
numberCodec,
|
|
7
|
+
} from '@terrafirmagreg-team/ui/lib/utils/stored-value';
|
|
8
|
+
|
|
9
|
+
export const AUTOPLAY_SPEED_MIN = 1;
|
|
10
|
+
export const AUTOPLAY_SPEED_MAX = 10;
|
|
11
|
+
export const ROTATE_MIN = 0;
|
|
12
|
+
export const ROTATE_MAX = 4;
|
|
13
|
+
export const SCENE_WIDTH_MIN = 50;
|
|
14
|
+
export const SCENE_WIDTH_MAX = 100;
|
|
15
|
+
export const SCENE_HEIGHT_MIN = 160;
|
|
16
|
+
export const SCENE_HEIGHT_MAX = 640;
|
|
17
|
+
|
|
18
|
+
export const useAutoplay = createStoredValue(
|
|
19
|
+
'wiki.viewer.autoplay',
|
|
20
|
+
boolCodec(true),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export const useAutoplaySpeed = createStoredValue(
|
|
24
|
+
'wiki.viewer.autoplaySpeed',
|
|
25
|
+
numberCodec(4, AUTOPLAY_SPEED_MIN, AUTOPLAY_SPEED_MAX),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const useAutoRotate = createStoredValue(
|
|
29
|
+
'wiki.viewer.glb.autoRotate',
|
|
30
|
+
boolCodec(true),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const useRotateSpeed = createStoredValue(
|
|
34
|
+
'wiki.viewer.glb.rotateSpeed',
|
|
35
|
+
numberCodec(1, ROTATE_MIN, ROTATE_MAX),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const useDotGrid = createStoredValue(
|
|
39
|
+
'wiki.viewer.glb.dotGrid',
|
|
40
|
+
boolCodec(true),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const useSceneWidth = createStoredValue(
|
|
44
|
+
'wiki.viewer.glb.width',
|
|
45
|
+
numberCodec(SCENE_WIDTH_MAX, SCENE_WIDTH_MIN, SCENE_WIDTH_MAX),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const useSceneHeight = createStoredValue(
|
|
49
|
+
'wiki.viewer.glb.height',
|
|
50
|
+
numberCodec(288, SCENE_HEIGHT_MIN, SCENE_HEIGHT_MAX),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export function useViewerPrefs() {
|
|
54
|
+
const [autoplay, setAutoplay] = useAutoplay();
|
|
55
|
+
const [autoplaySpeed, setAutoplaySpeed] = useAutoplaySpeed();
|
|
56
|
+
const [autoRotate, setAutoRotate] = useAutoRotate();
|
|
57
|
+
const [rotateSpeed, setRotateSpeed] = useRotateSpeed();
|
|
58
|
+
const [dotGrid, setDotGrid] = useDotGrid();
|
|
59
|
+
const [sceneWidth, setSceneWidth] = useSceneWidth();
|
|
60
|
+
const [sceneHeight, setSceneHeight] = useSceneHeight();
|
|
61
|
+
return {
|
|
62
|
+
autoplay,
|
|
63
|
+
setAutoplay,
|
|
64
|
+
autoplaySpeed,
|
|
65
|
+
setAutoplaySpeed,
|
|
66
|
+
autoRotate,
|
|
67
|
+
setAutoRotate,
|
|
68
|
+
rotateSpeed,
|
|
69
|
+
setRotateSpeed,
|
|
70
|
+
dotGrid,
|
|
71
|
+
setDotGrid,
|
|
72
|
+
sceneWidth,
|
|
73
|
+
setSceneWidth,
|
|
74
|
+
sceneHeight,
|
|
75
|
+
setSceneHeight,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChevronEdgeHighlight,
|
|
3
|
+
FlowPoint,
|
|
4
|
+
} from '@terrafirmagreg-team/ui/types/canvas';
|
|
5
|
+
|
|
6
|
+
export const CHEVRON_SPACING = 10;
|
|
7
|
+
const CHEVRON_SIZE = 3.5;
|
|
8
|
+
|
|
9
|
+
export const CHEVRON_GLYPH = `M${-CHEVRON_SIZE * 0.45} ${-CHEVRON_SIZE * 0.9}L${CHEVRON_SIZE} 0L${-CHEVRON_SIZE * 0.45} ${CHEVRON_SIZE * 0.9}`;
|
|
10
|
+
|
|
11
|
+
export function chevronTicksAlongPath(points: readonly FlowPoint[]): {
|
|
12
|
+
offsets: number[];
|
|
13
|
+
total: number;
|
|
14
|
+
} {
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (let i = 0; i + 1 < points.length; i += 1) {
|
|
17
|
+
total += Math.hypot(
|
|
18
|
+
points[i + 1]!.x - points[i]!.x,
|
|
19
|
+
points[i + 1]!.y - points[i]!.y,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const offsets: number[] = [];
|
|
23
|
+
for (
|
|
24
|
+
let d = CHEVRON_SPACING;
|
|
25
|
+
d <= total - CHEVRON_SPACING / 2;
|
|
26
|
+
d += CHEVRON_SPACING
|
|
27
|
+
) {
|
|
28
|
+
offsets.push(d);
|
|
29
|
+
}
|
|
30
|
+
return { offsets, total };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function svgPathToPoints(d: string, samples = 12): FlowPoint[] {
|
|
34
|
+
const out: FlowPoint[] = [];
|
|
35
|
+
const re = /([MLCQ])([^MLCQ]*)/g;
|
|
36
|
+
let match: RegExpExecArray | null;
|
|
37
|
+
let cur: FlowPoint = { x: 0, y: 0 };
|
|
38
|
+
while ((match = re.exec(d))) {
|
|
39
|
+
const nums = match[2]!.trim().split(/[\s,]+/).map(Number);
|
|
40
|
+
if (match[1] === 'M' || match[1] === 'L') {
|
|
41
|
+
for (let i = 0; i + 1 < nums.length; i += 2) {
|
|
42
|
+
cur = { x: nums[i]!, y: nums[i + 1]! };
|
|
43
|
+
out.push(cur);
|
|
44
|
+
}
|
|
45
|
+
} else if (match[1] === 'C') {
|
|
46
|
+
for (let i = 0; i + 5 < nums.length; i += 6) {
|
|
47
|
+
const p0 = cur;
|
|
48
|
+
const c1 = { x: nums[i]!, y: nums[i + 1]! };
|
|
49
|
+
const c2 = { x: nums[i + 2]!, y: nums[i + 3]! };
|
|
50
|
+
const p1 = { x: nums[i + 4]!, y: nums[i + 5]! };
|
|
51
|
+
for (let s = 1; s <= samples; s += 1) {
|
|
52
|
+
const t = s / samples;
|
|
53
|
+
const mt = 1 - t;
|
|
54
|
+
out.push({
|
|
55
|
+
x:
|
|
56
|
+
mt * mt * mt * p0.x +
|
|
57
|
+
3 * mt * mt * t * c1.x +
|
|
58
|
+
3 * mt * t * t * c2.x +
|
|
59
|
+
t * t * t * p1.x,
|
|
60
|
+
y:
|
|
61
|
+
mt * mt * mt * p0.y +
|
|
62
|
+
3 * mt * mt * t * c1.y +
|
|
63
|
+
3 * mt * t * t * c2.y +
|
|
64
|
+
t * t * t * p1.y,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
cur = p1;
|
|
68
|
+
}
|
|
69
|
+
} else if (match[1] === 'Q') {
|
|
70
|
+
for (let i = 0; i + 3 < nums.length; i += 4) {
|
|
71
|
+
const p0 = cur;
|
|
72
|
+
const c = { x: nums[i]!, y: nums[i + 1]! };
|
|
73
|
+
const p1 = { x: nums[i + 2]!, y: nums[i + 3]! };
|
|
74
|
+
for (let s = 1; s <= samples; s += 1) {
|
|
75
|
+
const t = s / samples;
|
|
76
|
+
const mt = 1 - t;
|
|
77
|
+
out.push({
|
|
78
|
+
x: mt * mt * p0.x + 2 * mt * t * c.x + t * t * p1.x,
|
|
79
|
+
y: mt * mt * p0.y + 2 * mt * t * c.y + t * t * p1.y,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
cur = p1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function chamferedPolylinePath(
|
|
90
|
+
points: readonly FlowPoint[],
|
|
91
|
+
cut = 8,
|
|
92
|
+
): string {
|
|
93
|
+
if (!points.length) return '';
|
|
94
|
+
if (points.length < 3) {
|
|
95
|
+
return `M${points[0]!.x} ${points[0]!.y}${points
|
|
96
|
+
.slice(1)
|
|
97
|
+
.map((p) => `L${p.x} ${p.y}`)
|
|
98
|
+
.join('')}`;
|
|
99
|
+
}
|
|
100
|
+
let d = `M${points[0]!.x} ${points[0]!.y}`;
|
|
101
|
+
let cursor = points[0]!;
|
|
102
|
+
for (let i = 1; i + 1 < points.length; i += 1) {
|
|
103
|
+
const corner = points[i]!;
|
|
104
|
+
const next = points[i + 1]!;
|
|
105
|
+
const inLen = Math.hypot(corner.x - cursor.x, corner.y - cursor.y);
|
|
106
|
+
const outLen = Math.hypot(next.x - corner.x, next.y - corner.y);
|
|
107
|
+
const take = Math.min(cut, inLen / 2, outLen / 2);
|
|
108
|
+
if (take < 0.5) {
|
|
109
|
+
d += `L${corner.x} ${corner.y}`;
|
|
110
|
+
cursor = corner;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const ax = corner.x - ((corner.x - cursor.x) / inLen) * take;
|
|
114
|
+
const ay = corner.y - ((corner.y - cursor.y) / inLen) * take;
|
|
115
|
+
const bx = corner.x + ((next.x - corner.x) / outLen) * take;
|
|
116
|
+
const by = corner.y + ((next.y - corner.y) / outLen) * take;
|
|
117
|
+
d += `L${ax} ${ay}L${bx} ${by}`;
|
|
118
|
+
cursor = { x: bx, y: by };
|
|
119
|
+
}
|
|
120
|
+
const last = points[points.length - 1]!;
|
|
121
|
+
return `${d}L${last.x} ${last.y}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function roundedPolylinePath(
|
|
125
|
+
points: readonly FlowPoint[],
|
|
126
|
+
radius = 10,
|
|
127
|
+
): string {
|
|
128
|
+
if (!points.length) return '';
|
|
129
|
+
if (points.length < 3) {
|
|
130
|
+
return `M${points[0]!.x} ${points[0]!.y}${points
|
|
131
|
+
.slice(1)
|
|
132
|
+
.map((p) => `L${p.x} ${p.y}`)
|
|
133
|
+
.join('')}`;
|
|
134
|
+
}
|
|
135
|
+
let d = `M${points[0]!.x} ${points[0]!.y}`;
|
|
136
|
+
let cursor = points[0]!;
|
|
137
|
+
for (let i = 1; i + 1 < points.length; i += 1) {
|
|
138
|
+
const corner = points[i]!;
|
|
139
|
+
const next = points[i + 1]!;
|
|
140
|
+
const inLen = Math.hypot(corner.x - cursor.x, corner.y - cursor.y);
|
|
141
|
+
const outLen = Math.hypot(next.x - corner.x, next.y - corner.y);
|
|
142
|
+
const take = Math.min(radius, inLen / 2, outLen / 2);
|
|
143
|
+
if (take < 0.5) {
|
|
144
|
+
d += `L${corner.x} ${corner.y}`;
|
|
145
|
+
cursor = corner;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const ax = corner.x - ((corner.x - cursor.x) / inLen) * take;
|
|
149
|
+
const ay = corner.y - ((corner.y - cursor.y) / inLen) * take;
|
|
150
|
+
const bx = corner.x + ((next.x - corner.x) / outLen) * take;
|
|
151
|
+
const by = corner.y + ((next.y - corner.y) / outLen) * take;
|
|
152
|
+
d += `L${ax} ${ay}Q${corner.x} ${corner.y} ${bx} ${by}`;
|
|
153
|
+
cursor = { x: bx, y: by };
|
|
154
|
+
}
|
|
155
|
+
const last = points[points.length - 1]!;
|
|
156
|
+
return `${d}L${last.x} ${last.y}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function nearestPointOnPolyline(
|
|
160
|
+
points: readonly FlowPoint[],
|
|
161
|
+
p: FlowPoint,
|
|
162
|
+
): FlowPoint {
|
|
163
|
+
let best = points[0]!;
|
|
164
|
+
let bestD = Infinity;
|
|
165
|
+
for (let i = 1; i < points.length; i += 1) {
|
|
166
|
+
const a = points[i - 1]!;
|
|
167
|
+
const b = points[i]!;
|
|
168
|
+
const dx = b.x - a.x;
|
|
169
|
+
const dy = b.y - a.y;
|
|
170
|
+
const len2 = dx * dx + dy * dy;
|
|
171
|
+
const t = len2
|
|
172
|
+
? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2))
|
|
173
|
+
: 0;
|
|
174
|
+
const q = { x: a.x + dx * t, y: a.y + dy * t };
|
|
175
|
+
const d = (q.x - p.x) ** 2 + (q.y - p.y) ** 2;
|
|
176
|
+
if (d < bestD) {
|
|
177
|
+
bestD = d;
|
|
178
|
+
best = q;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return best;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function chevronHighlightClass(
|
|
185
|
+
highlight: ChevronEdgeHighlight,
|
|
186
|
+
): string {
|
|
187
|
+
switch (highlight) {
|
|
188
|
+
case 'incoming':
|
|
189
|
+
return 'chevron-edge--incoming';
|
|
190
|
+
case 'outgoing':
|
|
191
|
+
return 'chevron-edge--outgoing';
|
|
192
|
+
case 'dim':
|
|
193
|
+
return 'chevron-edge--dim';
|
|
194
|
+
case undefined:
|
|
195
|
+
return '';
|
|
196
|
+
default: {
|
|
197
|
+
const _never: never = highlight;
|
|
198
|
+
return _never;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FLOW_MAX_ZOOM, FLOW_MIN_ZOOM } from '@terrafirmagreg-team/ui/lib/canvas/viewport';
|
|
2
|
+
import type {
|
|
3
|
+
FlowContentBox,
|
|
4
|
+
FlowRect,
|
|
5
|
+
FlowSizedNode,
|
|
6
|
+
} from '@terrafirmagreg-team/ui/types/canvas';
|
|
7
|
+
|
|
8
|
+
const FIT_VIEW_PAD = 0.12;
|
|
9
|
+
|
|
10
|
+
export function flowContentBox(
|
|
11
|
+
nodes: FlowSizedNode[],
|
|
12
|
+
rects: FlowRect[] = [],
|
|
13
|
+
): FlowContentBox | null {
|
|
14
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
15
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
16
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
17
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
18
|
+
for (const node of nodes) {
|
|
19
|
+
const halfW = (node.width ?? 0) / 2;
|
|
20
|
+
const halfH = (node.height ?? 0) / 2;
|
|
21
|
+
minX = Math.min(minX, node.position.x - halfW);
|
|
22
|
+
minY = Math.min(minY, node.position.y - halfH);
|
|
23
|
+
maxX = Math.max(maxX, node.position.x + halfW);
|
|
24
|
+
maxY = Math.max(maxY, node.position.y + halfH);
|
|
25
|
+
}
|
|
26
|
+
for (const rect of rects) {
|
|
27
|
+
minX = Math.min(minX, rect.x - rect.width / 2);
|
|
28
|
+
minY = Math.min(minY, rect.y - rect.height / 2);
|
|
29
|
+
maxX = Math.max(maxX, rect.x + rect.width / 2);
|
|
30
|
+
maxY = Math.max(maxY, rect.y + rect.height / 2);
|
|
31
|
+
}
|
|
32
|
+
if (!Number.isFinite(minX)) return null;
|
|
33
|
+
return {
|
|
34
|
+
midX: (minX + maxX) / 2,
|
|
35
|
+
midY: (minY + maxY) / 2,
|
|
36
|
+
width: Math.max(1, maxX - minX),
|
|
37
|
+
height: Math.max(1, maxY - minY),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function zoomToFitBox(
|
|
42
|
+
box: { width: number; height: number },
|
|
43
|
+
viewW: number,
|
|
44
|
+
viewH: number,
|
|
45
|
+
minZoom = FLOW_MIN_ZOOM,
|
|
46
|
+
maxZoom = FLOW_MAX_ZOOM,
|
|
47
|
+
): number {
|
|
48
|
+
const zoom = Math.min(
|
|
49
|
+
(viewW * (1 - 2 * FIT_VIEW_PAD)) / box.width,
|
|
50
|
+
(viewH * (1 - 2 * FIT_VIEW_PAD)) / box.height,
|
|
51
|
+
);
|
|
52
|
+
return Math.min(maxZoom, Math.max(minZoom, zoom));
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Edge, Node } from '@terrafirmagreg-team/ui/lib/canvas/xyflow';
|
|
2
|
+
import type { ChevronEdgeHighlight } from '@terrafirmagreg-team/ui/types/canvas';
|
|
3
|
+
|
|
4
|
+
export function applyNodeSelection<N extends Node>(
|
|
5
|
+
nodes: N[],
|
|
6
|
+
selectedId: string | null,
|
|
7
|
+
type?: string,
|
|
8
|
+
): N[] {
|
|
9
|
+
let changed = false;
|
|
10
|
+
const next = nodes.map((node) => {
|
|
11
|
+
if (type && node.type !== type) return node;
|
|
12
|
+
const selected = selectedId != null && node.id === selectedId;
|
|
13
|
+
if (node.selected === selected) return node;
|
|
14
|
+
changed = true;
|
|
15
|
+
return { ...node, selected };
|
|
16
|
+
});
|
|
17
|
+
return changed ? next : nodes;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function decorateFlowEdges<E extends Edge>(
|
|
21
|
+
edges: E[],
|
|
22
|
+
hoverId: string | null,
|
|
23
|
+
selectedId: string | null,
|
|
24
|
+
): E[] {
|
|
25
|
+
return edges.map((edge) => {
|
|
26
|
+
const outgoing = hoverId != null && edge.source === hoverId;
|
|
27
|
+
const incoming = hoverId != null && edge.target === hoverId;
|
|
28
|
+
const selected =
|
|
29
|
+
selectedId != null &&
|
|
30
|
+
(edge.source === selectedId || edge.target === selectedId);
|
|
31
|
+
const sourceEngaged = outgoing || selectedId === edge.source;
|
|
32
|
+
const targetEngaged = incoming || selectedId === edge.target;
|
|
33
|
+
const flags = edge.data as
|
|
34
|
+
| { hideTarget?: boolean; hideSource?: boolean }
|
|
35
|
+
| undefined;
|
|
36
|
+
const hidden =
|
|
37
|
+
(flags?.hideTarget === true && !targetEngaged) ||
|
|
38
|
+
(flags?.hideSource === true && !sourceEngaged);
|
|
39
|
+
const highlight: ChevronEdgeHighlight =
|
|
40
|
+
outgoing || (selected && edge.source === selectedId)
|
|
41
|
+
? 'outgoing'
|
|
42
|
+
: incoming || (selected && edge.target === selectedId)
|
|
43
|
+
? 'incoming'
|
|
44
|
+
: undefined;
|
|
45
|
+
return { ...edge, data: { hidden, highlight } };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { StoredViewport } from '@terrafirmagreg-team/ui/types/canvas';
|
|
2
|
+
|
|
3
|
+
export const VIEWPORT_SAVE_MS = 300;
|
|
4
|
+
export const FLOW_MIN_ZOOM = 0.25;
|
|
5
|
+
export const FLOW_MAX_ZOOM = 2;
|
|
6
|
+
|
|
7
|
+
export function readStoredViewport(key: string): StoredViewport | null {
|
|
8
|
+
try {
|
|
9
|
+
const raw = localStorage.getItem(key);
|
|
10
|
+
if (!raw) return null;
|
|
11
|
+
const parsed = JSON.parse(raw) as Partial<StoredViewport>;
|
|
12
|
+
if (
|
|
13
|
+
typeof parsed.x !== 'number' ||
|
|
14
|
+
typeof parsed.y !== 'number' ||
|
|
15
|
+
typeof parsed.zoom !== 'number' ||
|
|
16
|
+
!Number.isFinite(parsed.x) ||
|
|
17
|
+
!Number.isFinite(parsed.y) ||
|
|
18
|
+
!Number.isFinite(parsed.zoom)
|
|
19
|
+
) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return { x: parsed.x, y: parsed.y, zoom: parsed.zoom };
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function writeStoredViewport(
|
|
29
|
+
key: string,
|
|
30
|
+
viewport: StoredViewport,
|
|
31
|
+
): void {
|
|
32
|
+
try {
|
|
33
|
+
localStorage.setItem(key, JSON.stringify(viewport));
|
|
34
|
+
} catch {
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { Lang } from '@terrafirmagreg-team/translate/types/locale';
|
|
3
|
+
import type { PagePreviewData, PreviewIcon } from '@terrafirmagreg-team/ui/types/preview';
|
|
4
|
+
|
|
5
|
+
export type ExternalMetaProvider = {
|
|
6
|
+
host: RegExp;
|
|
7
|
+
path?: RegExp;
|
|
8
|
+
url: string;
|
|
9
|
+
kind?: 'json' | 'html';
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
replace?: [RegExp, string][];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_EXTERNAL_META_PROVIDERS: ExternalMetaProvider[] = [
|
|
16
|
+
{
|
|
17
|
+
host: /(^|\.)wikipedia\.org$/i,
|
|
18
|
+
path: /^\/wiki\/(.+)$/,
|
|
19
|
+
url: 'https://{host}/api/rest_v1/page/summary/{1}',
|
|
20
|
+
kind: 'json',
|
|
21
|
+
title: 'titles.normalized',
|
|
22
|
+
description: 'description',
|
|
23
|
+
replace: [[/_/g, ' ']],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
host: /^github\.com$/i,
|
|
27
|
+
path: /^\/([^/]+)\/([^/?#]+)\/releases\/tag\/([^/?#]+)\/?$/,
|
|
28
|
+
url: 'https://api.github.com/repos/{1}/{2}/releases/tags/{3}',
|
|
29
|
+
kind: 'json',
|
|
30
|
+
title: 'name',
|
|
31
|
+
description: 'body',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
host: /^github\.com$/i,
|
|
35
|
+
path: /^\/([^/]+)\/([^/?#]+)\/(?:issues|pull)\/([^/?#]+)\/?$/,
|
|
36
|
+
url: 'https://api.github.com/repos/{1}/{2}/issues/{3}',
|
|
37
|
+
kind: 'json',
|
|
38
|
+
title: 'title',
|
|
39
|
+
description: 'body',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
host: /^github\.com$/i,
|
|
43
|
+
path: /^\/([^/]+)\/([^/?#]+)\/?$/,
|
|
44
|
+
url: 'https://api.github.com/repos/{1}/{2}',
|
|
45
|
+
kind: 'json',
|
|
46
|
+
title: 'full_name',
|
|
47
|
+
description: 'description',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
host: /^github\.com$/i,
|
|
51
|
+
path: /^\/([^/?#]+)\/?$/,
|
|
52
|
+
url: 'https://api.github.com/users/{1}',
|
|
53
|
+
kind: 'json',
|
|
54
|
+
title: 'name',
|
|
55
|
+
description: 'bio',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
host: /.*/,
|
|
59
|
+
url: 'https://api.allorigins.win/raw?url={href}',
|
|
60
|
+
kind: 'html',
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export const DEFAULT_EXTERNAL_FAVICON = 'https://{host}/favicon.ico';
|
|
65
|
+
|
|
66
|
+
export type UiConfig = {
|
|
67
|
+
basePath?: string;
|
|
68
|
+
|
|
69
|
+
brand?: { name: string; icon?: string };
|
|
70
|
+
|
|
71
|
+
components?: Record<string, ComponentType<any>>;
|
|
72
|
+
externalMetaProviders?: ExternalMetaProvider[];
|
|
73
|
+
externalFavicon?: string;
|
|
74
|
+
|
|
75
|
+
loadPagePreview?: (
|
|
76
|
+
href: string,
|
|
77
|
+
ctx?: { lang?: string },
|
|
78
|
+
) => Promise<PagePreviewData | undefined>;
|
|
79
|
+
|
|
80
|
+
loadPagePreviews?: (
|
|
81
|
+
lang: Lang,
|
|
82
|
+
langMap: Record<string, string>,
|
|
83
|
+
hrefs: Iterable<string>,
|
|
84
|
+
) => Promise<Record<string, PagePreviewData>>;
|
|
85
|
+
pagePreviewIcon?: ComponentType<{ icon: PreviewIcon }>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function createConfigRegistry<I extends object, R = I>(
|
|
89
|
+
resolve: (overrides: I) => R = (overrides) => overrides as unknown as R,
|
|
90
|
+
) {
|
|
91
|
+
let overrides = {} as I;
|
|
92
|
+
return {
|
|
93
|
+
register(input: I): void {
|
|
94
|
+
overrides = { ...overrides, ...input };
|
|
95
|
+
},
|
|
96
|
+
get(): R {
|
|
97
|
+
return resolve(overrides);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const { register: registerUiConfig, get: uiConfig } =
|
|
103
|
+
createConfigRegistry<UiConfig>();
|
|
104
|
+
|
|
105
|
+
export { registerUiConfig, uiConfig };
|
|
106
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
export const COLORS: Record<string, string> = {
|
|
4
|
+
info: 'var(--color-info)',
|
|
5
|
+
note: 'var(--color-note)',
|
|
6
|
+
tip: 'var(--color-tip)',
|
|
7
|
+
warn: 'var(--color-warning)',
|
|
8
|
+
warning: 'var(--color-warning)',
|
|
9
|
+
error: 'var(--color-error)',
|
|
10
|
+
danger: 'var(--color-error)',
|
|
11
|
+
success: 'var(--color-success)',
|
|
12
|
+
idea: 'var(--color-idea)',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function calloutStyle(
|
|
16
|
+
type: string,
|
|
17
|
+
style: CSSProperties | undefined,
|
|
18
|
+
): CSSProperties {
|
|
19
|
+
return { '--callout-color': COLORS[type] ?? COLORS.info, ...style } as CSSProperties;
|
|
20
|
+
}
|
|
21
|
+
|