@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,354 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
type ComponentProps,
|
|
6
|
+
createContext,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
use,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useEffectEvent,
|
|
12
|
+
useId,
|
|
13
|
+
useLayoutEffect,
|
|
14
|
+
useMemo,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
} from 'react';
|
|
18
|
+
import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
|
|
19
|
+
import { mergeRefs } from '@terrafirmagreg-team/ui/lib/utils/merge-refs';
|
|
20
|
+
import { Tabs as Primitive } from '@base-ui/react/tabs';
|
|
21
|
+
|
|
22
|
+
type ChangeListener = (v: string) => void;
|
|
23
|
+
const listeners = new Map<string, Set<ChangeListener>>();
|
|
24
|
+
|
|
25
|
+
export interface BaseTabsProps extends ComponentProps<typeof Primitive.Root> {
|
|
26
|
+
|
|
27
|
+
groupId?: string;
|
|
28
|
+
|
|
29
|
+
persist?: boolean;
|
|
30
|
+
|
|
31
|
+
updateAnchor?: boolean;
|
|
32
|
+
|
|
33
|
+
onValueChange?: (value: string) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const BaseTabsContext = createContext<{
|
|
37
|
+
valueToIdMap: Map<string, string>;
|
|
38
|
+
|
|
39
|
+
panels: Map<string, HTMLElement>;
|
|
40
|
+
|
|
41
|
+
triggers: Set<string>;
|
|
42
|
+
} | null>(null);
|
|
43
|
+
|
|
44
|
+
function useBaseTabContext() {
|
|
45
|
+
const ctx = use(BaseTabsContext);
|
|
46
|
+
if (!ctx) throw new Error('You must wrap your component in <BaseTabs>');
|
|
47
|
+
return ctx;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const BaseTabsList = Primitive.List;
|
|
51
|
+
|
|
52
|
+
export function BaseTabsTrigger({ value, ref, ...props }: ComponentProps<typeof Primitive.Tab>) {
|
|
53
|
+
const { triggers } = useBaseTabContext();
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Primitive.Tab
|
|
57
|
+
ref={mergeRefs(ref, (element) => {
|
|
58
|
+
if (element) triggers.add(value);
|
|
59
|
+
else triggers.delete(value);
|
|
60
|
+
})}
|
|
61
|
+
value={value}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function BaseTabs({
|
|
68
|
+
ref,
|
|
69
|
+
groupId,
|
|
70
|
+
persist = false,
|
|
71
|
+
updateAnchor = false,
|
|
72
|
+
defaultValue,
|
|
73
|
+
value: _value,
|
|
74
|
+
onValueChange: _onValueChange,
|
|
75
|
+
...props
|
|
76
|
+
}: BaseTabsProps) {
|
|
77
|
+
const tabsRef = useRef<HTMLDivElement>(null);
|
|
78
|
+
const valueToIdMap = useMemo(() => new Map<string, string>(), []);
|
|
79
|
+
const panels = useMemo(() => new Map<string, HTMLElement>(), []);
|
|
80
|
+
const triggers = useMemo(() => new Set<string>(), []);
|
|
81
|
+
const [value, setValue] =
|
|
82
|
+
_value === undefined
|
|
83
|
+
? // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
|
|
84
|
+
useState(defaultValue)
|
|
85
|
+
: // eslint-disable-next-line react-hooks/rules-of-hooks -- not supposed to change controlled/uncontrolled
|
|
86
|
+
[_value, (v: string) => _onValueChange?.(v)];
|
|
87
|
+
|
|
88
|
+
const onChange = useEffectEvent((v: string) => {
|
|
89
|
+
if (triggers.has(v)) setValue(v);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const openFromHash = useEffectEvent(() => {
|
|
93
|
+
const hash = window.location.hash.slice(1);
|
|
94
|
+
if (!hash) return;
|
|
95
|
+
|
|
96
|
+
for (const [value, id] of valueToIdMap.entries()) {
|
|
97
|
+
if (id === hash) {
|
|
98
|
+
setValue(value);
|
|
99
|
+
tabsRef.current?.scrollIntoView();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const target = document.getElementById(hash);
|
|
105
|
+
if (!target) return;
|
|
106
|
+
|
|
107
|
+
for (const [value, panel] of panels.entries()) {
|
|
108
|
+
if (!panel.contains(target)) continue;
|
|
109
|
+
|
|
110
|
+
setValue(value);
|
|
111
|
+
requestAnimationFrame(() => target.scrollIntoView());
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
if (!groupId) return;
|
|
118
|
+
let previous = sessionStorage.getItem(groupId);
|
|
119
|
+
if (persist) previous ??= localStorage.getItem(groupId);
|
|
120
|
+
if (previous) onChange(previous);
|
|
121
|
+
|
|
122
|
+
const groupListeners = listeners.get(groupId) ?? new Set();
|
|
123
|
+
groupListeners.add(onChange);
|
|
124
|
+
listeners.set(groupId, groupListeners);
|
|
125
|
+
return () => {
|
|
126
|
+
groupListeners.delete(onChange);
|
|
127
|
+
};
|
|
128
|
+
}, [groupId, persist]);
|
|
129
|
+
|
|
130
|
+
useLayoutEffect(() => {
|
|
131
|
+
openFromHash();
|
|
132
|
+
window.addEventListener('hashchange', openFromHash);
|
|
133
|
+
return () => window.removeEventListener('hashchange', openFromHash);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Primitive.Root
|
|
138
|
+
ref={mergeRefs(ref, tabsRef)}
|
|
139
|
+
value={value}
|
|
140
|
+
onValueChange={(v: string) => {
|
|
141
|
+
if (updateAnchor) {
|
|
142
|
+
const id = valueToIdMap.get(v);
|
|
143
|
+
|
|
144
|
+
if (id) {
|
|
145
|
+
window.history.replaceState(null, '', `#${id}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (groupId) {
|
|
150
|
+
const groupListeners = listeners.get(groupId);
|
|
151
|
+
if (groupListeners) {
|
|
152
|
+
for (const listener of groupListeners) listener(v);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
sessionStorage.setItem(groupId, v);
|
|
156
|
+
if (persist) localStorage.setItem(groupId, v);
|
|
157
|
+
} else {
|
|
158
|
+
setValue(v);
|
|
159
|
+
}
|
|
160
|
+
}}
|
|
161
|
+
{...props}
|
|
162
|
+
>
|
|
163
|
+
<BaseTabsContext
|
|
164
|
+
value={useMemo(
|
|
165
|
+
() => ({ valueToIdMap, panels, triggers }),
|
|
166
|
+
[valueToIdMap, panels, triggers],
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
{props.children}
|
|
170
|
+
</BaseTabsContext>
|
|
171
|
+
</Primitive.Root>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function BaseTabsContent({ value, ref, ...props }: ComponentProps<typeof Primitive.Panel>) {
|
|
176
|
+
const { valueToIdMap, panels } = useBaseTabContext();
|
|
177
|
+
|
|
178
|
+
if (props.id) {
|
|
179
|
+
valueToIdMap.set(value, props.id);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Primitive.Panel
|
|
184
|
+
ref={mergeRefs(ref, (element) => {
|
|
185
|
+
if (element) panels.set(value, element);
|
|
186
|
+
else panels.delete(value);
|
|
187
|
+
})}
|
|
188
|
+
value={value}
|
|
189
|
+
{...props}
|
|
190
|
+
>
|
|
191
|
+
{props.children}
|
|
192
|
+
</Primitive.Panel>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
type CollectionKey = string | symbol;
|
|
197
|
+
|
|
198
|
+
export interface TabsProps extends Omit<
|
|
199
|
+
ComponentProps<typeof BaseTabs>,
|
|
200
|
+
'value' | 'onValueChange'
|
|
201
|
+
> {
|
|
202
|
+
|
|
203
|
+
items?: string[];
|
|
204
|
+
|
|
205
|
+
defaultIndex?: number;
|
|
206
|
+
|
|
207
|
+
label?: ReactNode;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const TabsContext = createContext<{
|
|
211
|
+
items?: string[];
|
|
212
|
+
collection: CollectionKey[];
|
|
213
|
+
} | null>(null);
|
|
214
|
+
|
|
215
|
+
function useTabContext() {
|
|
216
|
+
const ctx = useContext(TabsContext);
|
|
217
|
+
if (!ctx) throw new Error('You must wrap your component in <Tabs>');
|
|
218
|
+
return ctx;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function TabsList({ className, ...props }: React.ComponentProps<typeof BaseTabsList>) {
|
|
222
|
+
return (
|
|
223
|
+
<BaseTabsList
|
|
224
|
+
{...props}
|
|
225
|
+
className={(s) =>
|
|
226
|
+
cn(
|
|
227
|
+
'flex gap-3.5 text-secondary-foreground overflow-x-auto px-4 not-prose border-b border-border',
|
|
228
|
+
typeof className === 'function' ? className(s) : className,
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
/>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function TabsTrigger({
|
|
236
|
+
className,
|
|
237
|
+
...props
|
|
238
|
+
}: React.ComponentProps<typeof BaseTabsTrigger>) {
|
|
239
|
+
return (
|
|
240
|
+
<BaseTabsTrigger
|
|
241
|
+
{...props}
|
|
242
|
+
className={(s) =>
|
|
243
|
+
cn(
|
|
244
|
+
'inline-flex items-center gap-2 whitespace-nowrap text-muted-foreground border-b border-transparent py-2 text-sm font-medium transition-colors [&_svg]:size-4 hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[active]:border-primary data-[active]:text-primary',
|
|
245
|
+
typeof className === 'function' ? className(s) : className,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
/>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function Tabs({
|
|
253
|
+
ref,
|
|
254
|
+
className,
|
|
255
|
+
items,
|
|
256
|
+
label,
|
|
257
|
+
defaultIndex = 0,
|
|
258
|
+
defaultValue = items ? escapeValue(items[defaultIndex]) : undefined,
|
|
259
|
+
...props
|
|
260
|
+
}: TabsProps) {
|
|
261
|
+
const collection = useMemo<CollectionKey[]>(() => [], []);
|
|
262
|
+
|
|
263
|
+
return (
|
|
264
|
+
<BaseTabs
|
|
265
|
+
ref={ref}
|
|
266
|
+
className={(s) =>
|
|
267
|
+
cn(
|
|
268
|
+
'flex flex-col overflow-hidden rounded-xl border bg-secondary my-4',
|
|
269
|
+
typeof className === 'function' ? className(s) : className,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
defaultValue={defaultValue}
|
|
273
|
+
{...props}
|
|
274
|
+
>
|
|
275
|
+
{items && (
|
|
276
|
+
<TabsList>
|
|
277
|
+
{label && <span className="text-sm font-medium my-auto me-auto">{label}</span>}
|
|
278
|
+
{items.map((item) => (
|
|
279
|
+
<TabsTrigger key={item} value={escapeValue(item)}>
|
|
280
|
+
{item}
|
|
281
|
+
</TabsTrigger>
|
|
282
|
+
))}
|
|
283
|
+
</TabsList>
|
|
284
|
+
)}
|
|
285
|
+
<TabsContext.Provider value={useMemo(() => ({ items, collection }), [collection, items])}>
|
|
286
|
+
{props.children}
|
|
287
|
+
</TabsContext.Provider>
|
|
288
|
+
</BaseTabs>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface TabProps extends Omit<ComponentProps<typeof BaseTabsContent>, 'value'> {
|
|
293
|
+
|
|
294
|
+
value?: string;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function Tab({ value, ...props }: TabProps) {
|
|
298
|
+
const { items } = useTabContext();
|
|
299
|
+
const resolved =
|
|
300
|
+
value ??
|
|
301
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks -- `value` is not supposed to change
|
|
302
|
+
items?.at(useCollectionIndex());
|
|
303
|
+
if (!resolved)
|
|
304
|
+
throw new Error(
|
|
305
|
+
'Failed to resolve tab `value`, please pass a `value` prop to the Tab component.',
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<TabsContent value={escapeValue(resolved)} {...props}>
|
|
310
|
+
{props.children}
|
|
311
|
+
</TabsContent>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function TabsContent({
|
|
316
|
+
value,
|
|
317
|
+
className,
|
|
318
|
+
...props
|
|
319
|
+
}: ComponentProps<typeof BaseTabsContent>) {
|
|
320
|
+
return (
|
|
321
|
+
<BaseTabsContent
|
|
322
|
+
value={value}
|
|
323
|
+
className={(s) =>
|
|
324
|
+
cn(
|
|
325
|
+
'p-4 text-[0.9375rem] bg-background rounded-xl outline-none prose-no-margin data-[inactive]:hidden [&>figure:only-child]:-m-4 [&>figure:only-child]:border-none',
|
|
326
|
+
typeof className === 'function' ? className(s) : className,
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
{...props}
|
|
330
|
+
>
|
|
331
|
+
{props.children}
|
|
332
|
+
</BaseTabsContent>
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function useCollectionIndex() {
|
|
337
|
+
const key = useId();
|
|
338
|
+
const { collection } = useTabContext();
|
|
339
|
+
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
return () => {
|
|
342
|
+
const idx = collection.indexOf(key);
|
|
343
|
+
if (idx !== -1) collection.splice(idx, 1);
|
|
344
|
+
};
|
|
345
|
+
}, [key, collection]);
|
|
346
|
+
|
|
347
|
+
if (!collection.includes(key)) collection.push(key);
|
|
348
|
+
return collection.indexOf(key);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function escapeValue(v: string): string {
|
|
352
|
+
return v.toLowerCase().replace(/\s/, '-');
|
|
353
|
+
}
|
|
354
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ChevronDown } from 'lucide-react';
|
|
4
|
+
import { useUiComponents } from '@terrafirmagreg-team/ui/provider';
|
|
5
|
+
import { cva } from 'class-variance-authority';
|
|
6
|
+
import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
|
|
7
|
+
import { type ComponentProps, type ReactNode, useEffect, useState } from 'react';
|
|
8
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './collapsible';
|
|
9
|
+
import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
|
|
10
|
+
|
|
11
|
+
export interface ParameterNode {
|
|
12
|
+
name: string;
|
|
13
|
+
description: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TypeNode {
|
|
17
|
+
|
|
18
|
+
description?: ReactNode;
|
|
19
|
+
|
|
20
|
+
type: ReactNode;
|
|
21
|
+
|
|
22
|
+
typeDescription?: ReactNode;
|
|
23
|
+
|
|
24
|
+
typeDescriptionLink?: string;
|
|
25
|
+
|
|
26
|
+
default?: ReactNode;
|
|
27
|
+
|
|
28
|
+
required?: boolean;
|
|
29
|
+
deprecated?: boolean;
|
|
30
|
+
|
|
31
|
+
parameters?: ParameterNode[];
|
|
32
|
+
|
|
33
|
+
returns?: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const fieldVariants = cva('text-muted-foreground not-prose pe-2');
|
|
37
|
+
|
|
38
|
+
export function TypeTable({
|
|
39
|
+
id,
|
|
40
|
+
type,
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: { type: Record<string, TypeNode> } & ComponentProps<'div'>) {
|
|
44
|
+
const t = useTranslations();
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
id={id}
|
|
49
|
+
className={cn(
|
|
50
|
+
'@container flex flex-col p-1 bg-card text-card-foreground rounded-2xl border my-6 text-sm overflow-hidden',
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
<div className="flex font-medium items-center px-3 py-1 not-prose text-muted-foreground">
|
|
56
|
+
<p className="w-1/4">{t('Prop', { note: 'ui.prop' })}</p>
|
|
57
|
+
<p className="@max-xl:hidden">{t('Type', { note: 'ui.type' })}</p>
|
|
58
|
+
</div>
|
|
59
|
+
{Object.entries(type).map(([key, value]) => (
|
|
60
|
+
<Item key={key} parentId={id} name={key} item={value} />
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Item({
|
|
67
|
+
parentId,
|
|
68
|
+
name,
|
|
69
|
+
item: {
|
|
70
|
+
parameters = [],
|
|
71
|
+
description,
|
|
72
|
+
required = false,
|
|
73
|
+
deprecated,
|
|
74
|
+
typeDescription,
|
|
75
|
+
default: defaultValue,
|
|
76
|
+
type,
|
|
77
|
+
typeDescriptionLink,
|
|
78
|
+
returns,
|
|
79
|
+
},
|
|
80
|
+
}: {
|
|
81
|
+
parentId?: string;
|
|
82
|
+
name: string;
|
|
83
|
+
item: TypeNode;
|
|
84
|
+
}) {
|
|
85
|
+
const t = useTranslations();
|
|
86
|
+
const { Link } = useUiComponents();
|
|
87
|
+
const [open, setOpen] = useState(false);
|
|
88
|
+
const id = parentId ? `${parentId}-${name}` : undefined;
|
|
89
|
+
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
const hash = window.location.hash;
|
|
92
|
+
if (!id || !hash) return;
|
|
93
|
+
if (`#${id}` === hash) setOpen(true);
|
|
94
|
+
}, [id]);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Collapsible
|
|
98
|
+
id={id}
|
|
99
|
+
open={open}
|
|
100
|
+
onOpenChange={(v) => {
|
|
101
|
+
if (v && id) {
|
|
102
|
+
window.history.replaceState(null, '', `#${id}`);
|
|
103
|
+
}
|
|
104
|
+
setOpen(v);
|
|
105
|
+
}}
|
|
106
|
+
className={cn(
|
|
107
|
+
'rounded-xl border overflow-hidden scroll-m-20 transition-all',
|
|
108
|
+
open ? 'shadow-sm bg-background not-last:mb-2' : 'border-transparent',
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
<CollapsibleTrigger className="relative flex flex-row items-center w-full group text-start px-3 py-2 not-prose hover:bg-accent">
|
|
112
|
+
<code
|
|
113
|
+
className={cn(
|
|
114
|
+
'text-primary min-w-fit w-1/4 font-mono font-medium pe-2',
|
|
115
|
+
deprecated && 'line-through text-primary/50',
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
{name}
|
|
119
|
+
{!required && '?'}
|
|
120
|
+
</code>
|
|
121
|
+
{typeDescriptionLink ? (
|
|
122
|
+
<Link href={typeDescriptionLink} className="underline @max-xl:hidden">
|
|
123
|
+
{type}
|
|
124
|
+
</Link>
|
|
125
|
+
) : (
|
|
126
|
+
<span className="@max-xl:hidden">{type}</span>
|
|
127
|
+
)}
|
|
128
|
+
<ChevronDown className="absolute inset-e-2 size-4 text-muted-foreground transition-transform group-data-[open]:rotate-180" />
|
|
129
|
+
</CollapsibleTrigger>
|
|
130
|
+
<CollapsibleContent>
|
|
131
|
+
<div className="grid grid-cols-[1fr_3fr] gap-y-4 text-sm p-3 overflow-auto scroll-container border-t">
|
|
132
|
+
<div className="prose prose-sm col-span-full prose-no-margin empty:hidden">
|
|
133
|
+
{description}
|
|
134
|
+
</div>
|
|
135
|
+
{typeDescription && (
|
|
136
|
+
<>
|
|
137
|
+
<p className={cn(fieldVariants())}>{t('Type', { note: 'ui.type' })}</p>
|
|
138
|
+
<p className="my-auto not-prose">{typeDescription}</p>
|
|
139
|
+
</>
|
|
140
|
+
)}
|
|
141
|
+
{defaultValue && (
|
|
142
|
+
<>
|
|
143
|
+
<p className={cn(fieldVariants())}>{t('Default', { note: 'ui.default' })}</p>
|
|
144
|
+
<p className="my-auto not-prose">{defaultValue}</p>
|
|
145
|
+
</>
|
|
146
|
+
)}
|
|
147
|
+
{parameters.length > 0 && (
|
|
148
|
+
<>
|
|
149
|
+
<p className={cn(fieldVariants())}>{t('Parameters', { note: 'ui.parameters' })}</p>
|
|
150
|
+
<div className="flex flex-col gap-2">
|
|
151
|
+
{parameters.map((param) => (
|
|
152
|
+
<div key={param.name} className="inline-flex items-center flex-wrap gap-1">
|
|
153
|
+
<p className="font-medium not-prose text-nowrap">{param.name} -</p>
|
|
154
|
+
<div className="prose prose-sm prose-no-margin">{param.description}</div>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
</>
|
|
159
|
+
)}
|
|
160
|
+
{returns && (
|
|
161
|
+
<>
|
|
162
|
+
<p className={cn(fieldVariants())}>{t('Returns', { note: 'ui.returns' })}</p>
|
|
163
|
+
<div className="my-auto prose prose-sm prose-no-margin">{returns}</div>
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</CollapsibleContent>
|
|
168
|
+
</Collapsible>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import Link from 'next/link';
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useMemo,
|
|
7
|
+
type ComponentProps,
|
|
8
|
+
type ComponentType,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { flushSync } from 'react-dom';
|
|
12
|
+
import { DirectionProvider } from '@base-ui/react/direction-provider';
|
|
13
|
+
import {
|
|
14
|
+
ThemeProvider,
|
|
15
|
+
useTheme,
|
|
16
|
+
type ThemeProviderProps,
|
|
17
|
+
} from 'next-themes';
|
|
18
|
+
import { TranslationProvider } from '@terrafirmagreg-team/translate';
|
|
19
|
+
import { useHotKey } from '@terrafirmagreg-team/ui/hooks/use-hot-key';
|
|
20
|
+
|
|
21
|
+
export type UiLinkProps = { href: string; prefetch?: boolean } & Omit<
|
|
22
|
+
ComponentProps<'a'>,
|
|
23
|
+
'href'
|
|
24
|
+
>;
|
|
25
|
+
|
|
26
|
+
export type UiComponents = {
|
|
27
|
+
Link?: ComponentType<UiLinkProps>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const defaults: Required<UiComponents> = {
|
|
31
|
+
Link: Link as ComponentType<UiLinkProps>,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const UiComponentsContext = createContext<UiComponents | null>(null);
|
|
35
|
+
|
|
36
|
+
export function UiProvider({
|
|
37
|
+
components,
|
|
38
|
+
translations,
|
|
39
|
+
children,
|
|
40
|
+
}: {
|
|
41
|
+
components?: UiComponents;
|
|
42
|
+
translations?: Record<string, string>;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}) {
|
|
45
|
+
const parent = useContext(UiComponentsContext);
|
|
46
|
+
const merged = useMemo(
|
|
47
|
+
() => ({ ...parent, ...components }),
|
|
48
|
+
[parent, components],
|
|
49
|
+
);
|
|
50
|
+
const inner = (
|
|
51
|
+
<UiComponentsContext.Provider value={merged}>
|
|
52
|
+
{children}
|
|
53
|
+
</UiComponentsContext.Provider>
|
|
54
|
+
);
|
|
55
|
+
if (!translations) return inner;
|
|
56
|
+
return (
|
|
57
|
+
<TranslationProvider translations={translations}>
|
|
58
|
+
{inner}
|
|
59
|
+
</TranslationProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function useUiComponents(): Required<UiComponents> {
|
|
64
|
+
return { ...defaults, ...useContext(UiComponentsContext) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ThemeOptions extends ThemeProviderProps {
|
|
68
|
+
|
|
69
|
+
enabled?: boolean;
|
|
70
|
+
|
|
71
|
+
hotKey?: string | ((e: KeyboardEvent) => boolean) | false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ThemeHotKey({
|
|
75
|
+
hotKey,
|
|
76
|
+
}: {
|
|
77
|
+
hotKey: Exclude<ThemeOptions['hotKey'], false | undefined>;
|
|
78
|
+
}) {
|
|
79
|
+
const { setTheme, resolvedTheme } = useTheme();
|
|
80
|
+
|
|
81
|
+
useHotKey(
|
|
82
|
+
(e) => {
|
|
83
|
+
|
|
84
|
+
const matched =
|
|
85
|
+
typeof hotKey === 'string'
|
|
86
|
+
? !e.metaKey &&
|
|
87
|
+
!e.ctrlKey &&
|
|
88
|
+
!e.altKey &&
|
|
89
|
+
e.key.toLowerCase() === hotKey.toLowerCase()
|
|
90
|
+
: hotKey(e);
|
|
91
|
+
if (!matched) return;
|
|
92
|
+
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
const next = resolvedTheme === 'dark' ? 'light' : 'dark';
|
|
95
|
+
if (document?.startViewTransition) {
|
|
96
|
+
document.startViewTransition(() => flushSync(() => setTheme(next)));
|
|
97
|
+
} else {
|
|
98
|
+
setTheme(next);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{ ignoreTyping: true },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function UiRootProvider({
|
|
108
|
+
children,
|
|
109
|
+
dir = 'ltr',
|
|
110
|
+
theme = {},
|
|
111
|
+
}: {
|
|
112
|
+
children?: ReactNode;
|
|
113
|
+
|
|
114
|
+
dir?: 'rtl' | 'ltr';
|
|
115
|
+
|
|
116
|
+
theme?: ThemeOptions;
|
|
117
|
+
}) {
|
|
118
|
+
let body = children;
|
|
119
|
+
|
|
120
|
+
if (theme?.enabled !== false) {
|
|
121
|
+
const { enabled: _, hotKey = 'd', ...themeProps } = theme;
|
|
122
|
+
|
|
123
|
+
body = (
|
|
124
|
+
<ThemeProvider
|
|
125
|
+
attribute="class"
|
|
126
|
+
defaultTheme="system"
|
|
127
|
+
enableSystem
|
|
128
|
+
disableTransitionOnChange
|
|
129
|
+
{...themeProps}
|
|
130
|
+
>
|
|
131
|
+
{hotKey !== false && <ThemeHotKey hotKey={hotKey} />}
|
|
132
|
+
{body}
|
|
133
|
+
</ThemeProvider>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return <DirectionProvider direction={dir}>{body}</DirectionProvider>;
|
|
138
|
+
}
|
|
139
|
+
|