@windrun-huaiin/third-ui 14.4.0 → 14.4.2
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.
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References most of its code and SVG animation design from
|
|
3
|
+
* https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/src/components/toc/clerk.tsx
|
|
4
|
+
*/
|
|
1
5
|
import * as Primitive from 'fumadocs-core/toc';
|
|
2
6
|
import { type ComponentProps, type ReactNode } from 'react';
|
|
3
7
|
type TOCItemType = Primitive.TOCItemType;
|
|
@@ -37,6 +37,18 @@ const CLERK_TURN_CURVE_HEIGHT = 12;
|
|
|
37
37
|
const CLERK_TURN_CONTROL_FACTOR = 0.68;
|
|
38
38
|
// Safety margin that keeps turns away from the heading rows themselves.
|
|
39
39
|
const CLERK_TURN_GAP_MARGIN = 7;
|
|
40
|
+
// Shared duration for active rail fade transitions and endpoint dot movement.
|
|
41
|
+
const CLERK_ACTIVE_ANIMATION_DURATION_MS = 300;
|
|
42
|
+
// Easing curve for the active rail and dot; tuned for a slightly delayed, softer motion.
|
|
43
|
+
const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
44
|
+
// Horizontal gap between the path centerline and the heading text start.
|
|
45
|
+
const CLERK_TEXT_GAP_FROM_PATH = 12;
|
|
46
|
+
// Radius of numbered step badges rendered on top of the path centerline.
|
|
47
|
+
const CLERK_STEP_BADGE_RADIUS = 7;
|
|
48
|
+
// Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
|
|
49
|
+
const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
|
|
50
|
+
// Max number of characters rendered for a TOC label before trimming with ellipsis.
|
|
51
|
+
const CLERK_MAX_LABEL_LENGTH = 44;
|
|
40
52
|
function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
|
|
41
53
|
return (jsxRuntime.jsxs(page.PageTOC, { className: className, children: [header, title !== null && title !== void 0 ? title : jsxRuntime.jsx(page.PageTOCTitle, {}), jsxRuntime.jsx(PortableClerkTOCScrollArea, { children: jsxRuntime.jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
|
|
42
54
|
}
|
|
@@ -116,49 +128,85 @@ function PortableClerkTOCItems(_a) {
|
|
|
116
128
|
if (toc.length === 0) {
|
|
117
129
|
return (jsxRuntime.jsx("div", { className: "rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground", children: emptyLabel }));
|
|
118
130
|
}
|
|
119
|
-
return (jsxRuntime.jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsxRuntime.jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsxRuntime.jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, itemPadding: meta.itemPadding, contentRef: (node) => {
|
|
131
|
+
return (jsxRuntime.jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsxRuntime.jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsxRuntime.jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, fullTitle: meta.fullTitle, itemPadding: meta.itemPadding, contentRef: (node) => {
|
|
120
132
|
contentRefs.current[i] = node;
|
|
121
133
|
}, ref: (node) => {
|
|
122
134
|
itemRefs.current[i] = node;
|
|
123
135
|
} }, meta.item.url)))] })));
|
|
124
136
|
}
|
|
125
|
-
function PortableClerkTOCItem({ item, isActive, resolvedContent, itemPadding, contentRef, ref, }) {
|
|
126
|
-
return (jsxRuntime.jsx(Primitive__namespace.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", style: {
|
|
137
|
+
function PortableClerkTOCItem({ item, isActive, resolvedContent, fullTitle, itemPadding, contentRef, ref, }) {
|
|
138
|
+
return (jsxRuntime.jsx(Primitive__namespace.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", title: fullTitle !== null && fullTitle !== void 0 ? fullTitle : undefined, style: {
|
|
127
139
|
paddingInlineStart: itemPadding,
|
|
128
|
-
}, className: cn('prose group relative py-1.5 text-sm transition-colors
|
|
140
|
+
}, className: cn('prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? lib.themeIconColor : 'text-fd-muted-foreground'), children: jsxRuntime.jsx("span", { ref: contentRef, className: "relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap", children: resolvedContent }) }));
|
|
129
141
|
}
|
|
130
142
|
function ClerkOutline({ path, items, activePath, activeAnchors, activeEndpoint, }) {
|
|
143
|
+
const activeSet = new Set(activeAnchors);
|
|
144
|
+
const [displayPath, setDisplayPath] = React.useState(activePath);
|
|
145
|
+
const [fadingPath, setFadingPath] = React.useState(null);
|
|
146
|
+
React.useEffect(() => {
|
|
147
|
+
if (activePath === displayPath)
|
|
148
|
+
return;
|
|
149
|
+
if (!displayPath) {
|
|
150
|
+
setDisplayPath(activePath);
|
|
151
|
+
setFadingPath(null);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
setFadingPath(displayPath);
|
|
155
|
+
setDisplayPath(activePath);
|
|
156
|
+
const timeout = window.setTimeout(() => {
|
|
157
|
+
setFadingPath(null);
|
|
158
|
+
}, CLERK_ACTIVE_ANIMATION_DURATION_MS);
|
|
159
|
+
return () => window.clearTimeout(timeout);
|
|
160
|
+
}, [activePath, displayPath]);
|
|
161
|
+
const dotTranslate = activeEndpoint
|
|
162
|
+
? `translate(${activeEndpoint.x - CLERK_ACTIVE_DOT_RADIUS}px, ${activeEndpoint.y - CLERK_ACTIVE_DOT_RADIUS}px)`
|
|
163
|
+
: undefined;
|
|
164
|
+
const transitionStyle = {
|
|
165
|
+
transitionDuration: `${CLERK_ACTIVE_ANIMATION_DURATION_MS}ms`,
|
|
166
|
+
transitionTimingFunction: CLERK_ACTIVE_ANIMATION_EASING,
|
|
167
|
+
};
|
|
131
168
|
if (!path)
|
|
132
169
|
return null;
|
|
133
|
-
|
|
134
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: jsxRuntime.jsx("path", { d: path, className: "stroke-fd-foreground/15", fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round" }) }), jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: activePath ? (jsxRuntime.jsx("path", { d: activePath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: lib.themeSvgIconColor })) : null }), jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: activeEndpoint ? (jsxRuntime.jsx("circle", { cx: activeEndpoint.x, cy: activeEndpoint.y, r: CLERK_ACTIVE_DOT_RADIUS, fill: lib.themeSvgIconColor })) : null }), jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-1 overflow-visible", width: "100%", height: "100%", children: items.map((item) => {
|
|
170
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: jsxRuntime.jsx("path", { d: path, className: "stroke-fd-foreground/15", fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round" }) }), jsxRuntime.jsxs("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: [fadingPath ? (jsxRuntime.jsx("path", { d: fadingPath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: lib.themeSvgIconColor, className: "transition-opacity", style: Object.assign({ opacity: 0 }, transitionStyle) })) : null, displayPath ? (jsxRuntime.jsx("path", { d: displayPath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: lib.themeSvgIconColor, className: "transition-opacity", style: Object.assign({ opacity: 1 }, transitionStyle) })) : null] }), jsxRuntime.jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0", children: jsxRuntime.jsx("div", { className: "absolute rounded-full transition-transform", style: Object.assign({ width: CLERK_ACTIVE_DOT_RADIUS * 2, height: CLERK_ACTIVE_DOT_RADIUS * 2, backgroundColor: lib.themeSvgIconColor, transform: dotTranslate, opacity: activeEndpoint ? 1 : 0 }, transitionStyle) }) }), jsxRuntime.jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-1 overflow-visible", width: "100%", height: "100%", children: items.map((item) => {
|
|
135
171
|
if (!item.stepNumber)
|
|
136
172
|
return null;
|
|
137
173
|
const isActive = activeSet.has(item.url.slice(1));
|
|
138
|
-
return (jsxRuntime.jsxs("g", { transform: `translate(${item.x}, ${item.y})`, children: [jsxRuntime.jsx("circle", { r:
|
|
174
|
+
return (jsxRuntime.jsxs("g", { transform: `translate(${item.x}, ${item.y})`, children: [jsxRuntime.jsx("circle", { r: CLERK_STEP_BADGE_RADIUS, fill: isActive ? lib.themeSvgIconColor : undefined, className: cn(!isActive && 'fill-black dark:fill-white') }), jsxRuntime.jsx("text", { y: "0.5", textAnchor: "middle", dominantBaseline: "middle", className: "fill-white text-[9px] font-medium dark:fill-black", children: item.stepNumber })] }, item.url));
|
|
139
175
|
}) })] }));
|
|
140
176
|
}
|
|
141
177
|
function getItemOffset(depth) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return 26;
|
|
146
|
-
return 36;
|
|
178
|
+
const lineOffset = getLineOffset(depth);
|
|
179
|
+
const badgeRadius = depth === 3 ? CLERK_STEP_BADGE_RADIUS : 0;
|
|
180
|
+
return lineOffset + badgeRadius + CLERK_TEXT_GAP_FROM_PATH;
|
|
147
181
|
}
|
|
148
182
|
function getLineOffset(depth) {
|
|
149
|
-
|
|
183
|
+
const group = getDepthGroup(depth);
|
|
184
|
+
return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
|
|
150
185
|
}
|
|
151
186
|
function getVisualLinePosition(depth) {
|
|
152
187
|
return getLineOffset(depth);
|
|
153
188
|
}
|
|
189
|
+
function getDepthGroup(depth) {
|
|
190
|
+
if (depth <= 2)
|
|
191
|
+
return 0;
|
|
192
|
+
if (depth === 3)
|
|
193
|
+
return 1;
|
|
194
|
+
if (depth === 4)
|
|
195
|
+
return 2;
|
|
196
|
+
return 3;
|
|
197
|
+
}
|
|
154
198
|
function resolveClerkItem(item) {
|
|
155
199
|
const isH3 = item.depth === 3;
|
|
156
200
|
const rawTitle = typeof item.title === 'string' ? item.title : '';
|
|
157
201
|
const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
|
|
158
202
|
let stepNumber = isH3 && isStep ? String(displayStep) : null;
|
|
159
203
|
let resolvedContent = item.title;
|
|
204
|
+
let fullTitle = rawTitle || null;
|
|
160
205
|
if (isH3 && isStep) {
|
|
161
206
|
resolvedContent = content !== null && content !== void 0 ? content : item.title;
|
|
207
|
+
if (typeof content === 'string') {
|
|
208
|
+
fullTitle = content;
|
|
209
|
+
}
|
|
162
210
|
}
|
|
163
211
|
if (isH3 && !stepNumber) {
|
|
164
212
|
const urlNum = getDigitsFromUrl(item.url);
|
|
@@ -169,18 +217,30 @@ function resolveClerkItem(item) {
|
|
|
169
217
|
const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
|
|
170
218
|
if (match === null || match === void 0 ? void 0 : match[2]) {
|
|
171
219
|
resolvedContent = match[2];
|
|
220
|
+
fullTitle = match[2];
|
|
172
221
|
}
|
|
173
222
|
}
|
|
174
223
|
}
|
|
175
224
|
}
|
|
225
|
+
if (typeof resolvedContent === 'string') {
|
|
226
|
+
fullTitle = resolvedContent;
|
|
227
|
+
resolvedContent = truncateClerkLabel(resolvedContent);
|
|
228
|
+
}
|
|
176
229
|
return {
|
|
177
230
|
item,
|
|
178
231
|
resolvedContent,
|
|
232
|
+
fullTitle,
|
|
179
233
|
stepNumber,
|
|
180
234
|
itemPadding: getItemOffset(item.depth),
|
|
181
235
|
lineOffset: getVisualLinePosition(item.depth),
|
|
182
236
|
};
|
|
183
237
|
}
|
|
238
|
+
function truncateClerkLabel(value) {
|
|
239
|
+
const normalized = value.trim();
|
|
240
|
+
if (normalized.length <= CLERK_MAX_LABEL_LENGTH)
|
|
241
|
+
return normalized;
|
|
242
|
+
return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
|
|
243
|
+
}
|
|
184
244
|
function buildOutlinePath(items) {
|
|
185
245
|
if (items.length === 0)
|
|
186
246
|
return '';
|
|
@@ -3,7 +3,7 @@ import { __rest } from '../../node_modules/.pnpm/@rollup_plugin-typescript@12.1.
|
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import * as Primitive from 'fumadocs-core/toc';
|
|
5
5
|
import { PageTOC, PageTOCTitle, PageTOCPopover, PageTOCPopoverTrigger, PageTOCPopoverContent } from 'fumadocs-ui/layouts/docs/page';
|
|
6
|
-
import { useRef, useState, useMemo, useLayoutEffect } from 'react';
|
|
6
|
+
import { useRef, useState, useMemo, useLayoutEffect, useEffect } from 'react';
|
|
7
7
|
import { themeSvgIconColor, themeIconColor } from '@windrun-huaiin/base-ui/lib';
|
|
8
8
|
|
|
9
9
|
// Base stroke width for both the inactive rail and the active highlight path.
|
|
@@ -16,6 +16,18 @@ const CLERK_TURN_CURVE_HEIGHT = 12;
|
|
|
16
16
|
const CLERK_TURN_CONTROL_FACTOR = 0.68;
|
|
17
17
|
// Safety margin that keeps turns away from the heading rows themselves.
|
|
18
18
|
const CLERK_TURN_GAP_MARGIN = 7;
|
|
19
|
+
// Shared duration for active rail fade transitions and endpoint dot movement.
|
|
20
|
+
const CLERK_ACTIVE_ANIMATION_DURATION_MS = 300;
|
|
21
|
+
// Easing curve for the active rail and dot; tuned for a slightly delayed, softer motion.
|
|
22
|
+
const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
23
|
+
// Horizontal gap between the path centerline and the heading text start.
|
|
24
|
+
const CLERK_TEXT_GAP_FROM_PATH = 12;
|
|
25
|
+
// Radius of numbered step badges rendered on top of the path centerline.
|
|
26
|
+
const CLERK_STEP_BADGE_RADIUS = 7;
|
|
27
|
+
// Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
|
|
28
|
+
const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42];
|
|
29
|
+
// Max number of characters rendered for a TOC label before trimming with ellipsis.
|
|
30
|
+
const CLERK_MAX_LABEL_LENGTH = 44;
|
|
19
31
|
function PortableClerkTOC({ toc, header, footer, title, emptyLabel = 'No headings', className, }) {
|
|
20
32
|
return (jsxs(PageTOC, { className: className, children: [header, title !== null && title !== void 0 ? title : jsx(PageTOCTitle, {}), jsx(PortableClerkTOCScrollArea, { children: jsx(PortableClerkTOCItems, { toc: toc, emptyLabel: emptyLabel }) }), footer] }));
|
|
21
33
|
}
|
|
@@ -95,49 +107,85 @@ function PortableClerkTOCItems(_a) {
|
|
|
95
107
|
if (toc.length === 0) {
|
|
96
108
|
return (jsx("div", { className: "rounded-lg border bg-fd-card p-3 text-xs text-fd-muted-foreground", children: emptyLabel }));
|
|
97
109
|
}
|
|
98
|
-
return (jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, itemPadding: meta.itemPadding, contentRef: (node) => {
|
|
110
|
+
return (jsxs("div", Object.assign({ ref: mergeRefs(containerRef, ref), className: cn('relative flex flex-col', className) }, props, { children: [jsx(ClerkOutline, { path: outlinePath, items: layout.items, activePath: activePath, activeAnchors: activeAnchors, activeEndpoint: activeEndpoint }), metas.map((meta, i) => (jsx(PortableClerkTOCItem, { item: meta.item, isActive: activeAnchors.includes(meta.item.url.slice(1)), resolvedContent: meta.resolvedContent, fullTitle: meta.fullTitle, itemPadding: meta.itemPadding, contentRef: (node) => {
|
|
99
111
|
contentRefs.current[i] = node;
|
|
100
112
|
}, ref: (node) => {
|
|
101
113
|
itemRefs.current[i] = node;
|
|
102
114
|
} }, meta.item.url)))] })));
|
|
103
115
|
}
|
|
104
|
-
function PortableClerkTOCItem({ item, isActive, resolvedContent, itemPadding, contentRef, ref, }) {
|
|
105
|
-
return (jsx(Primitive.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", style: {
|
|
116
|
+
function PortableClerkTOCItem({ item, isActive, resolvedContent, fullTitle, itemPadding, contentRef, ref, }) {
|
|
117
|
+
return (jsx(Primitive.TOCItem, { ref: ref, href: item.url, "data-clerk-item": "", title: fullTitle !== null && fullTitle !== void 0 ? fullTitle : undefined, style: {
|
|
106
118
|
paddingInlineStart: itemPadding,
|
|
107
|
-
}, className: cn('prose group relative py-1.5 text-sm transition-colors
|
|
119
|
+
}, className: cn('prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground', isActive ? themeIconColor : 'text-fd-muted-foreground'), children: jsx("span", { ref: contentRef, className: "relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap", children: resolvedContent }) }));
|
|
108
120
|
}
|
|
109
121
|
function ClerkOutline({ path, items, activePath, activeAnchors, activeEndpoint, }) {
|
|
122
|
+
const activeSet = new Set(activeAnchors);
|
|
123
|
+
const [displayPath, setDisplayPath] = useState(activePath);
|
|
124
|
+
const [fadingPath, setFadingPath] = useState(null);
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (activePath === displayPath)
|
|
127
|
+
return;
|
|
128
|
+
if (!displayPath) {
|
|
129
|
+
setDisplayPath(activePath);
|
|
130
|
+
setFadingPath(null);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
setFadingPath(displayPath);
|
|
134
|
+
setDisplayPath(activePath);
|
|
135
|
+
const timeout = window.setTimeout(() => {
|
|
136
|
+
setFadingPath(null);
|
|
137
|
+
}, CLERK_ACTIVE_ANIMATION_DURATION_MS);
|
|
138
|
+
return () => window.clearTimeout(timeout);
|
|
139
|
+
}, [activePath, displayPath]);
|
|
140
|
+
const dotTranslate = activeEndpoint
|
|
141
|
+
? `translate(${activeEndpoint.x - CLERK_ACTIVE_DOT_RADIUS}px, ${activeEndpoint.y - CLERK_ACTIVE_DOT_RADIUS}px)`
|
|
142
|
+
: undefined;
|
|
143
|
+
const transitionStyle = {
|
|
144
|
+
transitionDuration: `${CLERK_ACTIVE_ANIMATION_DURATION_MS}ms`,
|
|
145
|
+
transitionTimingFunction: CLERK_ACTIVE_ANIMATION_EASING,
|
|
146
|
+
};
|
|
110
147
|
if (!path)
|
|
111
148
|
return null;
|
|
112
|
-
|
|
113
|
-
return (jsxs(Fragment, { children: [jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: jsx("path", { d: path, className: "stroke-fd-foreground/15", fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round" }) }), jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: activePath ? (jsx("path", { d: activePath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: themeSvgIconColor })) : null }), jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: activeEndpoint ? (jsx("circle", { cx: activeEndpoint.x, cy: activeEndpoint.y, r: CLERK_ACTIVE_DOT_RADIUS, fill: themeSvgIconColor })) : null }), jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-1 overflow-visible", width: "100%", height: "100%", children: items.map((item) => {
|
|
149
|
+
return (jsxs(Fragment, { children: [jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: jsx("path", { d: path, className: "stroke-fd-foreground/15", fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round" }) }), jsxs("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0 overflow-visible", width: "100%", height: "100%", children: [fadingPath ? (jsx("path", { d: fadingPath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: themeSvgIconColor, className: "transition-opacity", style: Object.assign({ opacity: 0 }, transitionStyle) })) : null, displayPath ? (jsx("path", { d: displayPath, fill: "none", strokeWidth: CLERK_PATH_STROKE_WIDTH, strokeLinecap: "round", strokeLinejoin: "round", stroke: themeSvgIconColor, className: "transition-opacity", style: Object.assign({ opacity: 1 }, transitionStyle) })) : null] }), jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-0", children: jsx("div", { className: "absolute rounded-full transition-transform", style: Object.assign({ width: CLERK_ACTIVE_DOT_RADIUS * 2, height: CLERK_ACTIVE_DOT_RADIUS * 2, backgroundColor: themeSvgIconColor, transform: dotTranslate, opacity: activeEndpoint ? 1 : 0 }, transitionStyle) }) }), jsx("svg", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 z-1 overflow-visible", width: "100%", height: "100%", children: items.map((item) => {
|
|
114
150
|
if (!item.stepNumber)
|
|
115
151
|
return null;
|
|
116
152
|
const isActive = activeSet.has(item.url.slice(1));
|
|
117
|
-
return (jsxs("g", { transform: `translate(${item.x}, ${item.y})`, children: [jsx("circle", { r:
|
|
153
|
+
return (jsxs("g", { transform: `translate(${item.x}, ${item.y})`, children: [jsx("circle", { r: CLERK_STEP_BADGE_RADIUS, fill: isActive ? themeSvgIconColor : undefined, className: cn(!isActive && 'fill-black dark:fill-white') }), jsx("text", { y: "0.5", textAnchor: "middle", dominantBaseline: "middle", className: "fill-white text-[9px] font-medium dark:fill-black", children: item.stepNumber })] }, item.url));
|
|
118
154
|
}) })] }));
|
|
119
155
|
}
|
|
120
156
|
function getItemOffset(depth) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return 26;
|
|
125
|
-
return 36;
|
|
157
|
+
const lineOffset = getLineOffset(depth);
|
|
158
|
+
const badgeRadius = depth === 3 ? CLERK_STEP_BADGE_RADIUS : 0;
|
|
159
|
+
return lineOffset + badgeRadius + CLERK_TEXT_GAP_FROM_PATH;
|
|
126
160
|
}
|
|
127
161
|
function getLineOffset(depth) {
|
|
128
|
-
|
|
162
|
+
const group = getDepthGroup(depth);
|
|
163
|
+
return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
|
|
129
164
|
}
|
|
130
165
|
function getVisualLinePosition(depth) {
|
|
131
166
|
return getLineOffset(depth);
|
|
132
167
|
}
|
|
168
|
+
function getDepthGroup(depth) {
|
|
169
|
+
if (depth <= 2)
|
|
170
|
+
return 0;
|
|
171
|
+
if (depth === 3)
|
|
172
|
+
return 1;
|
|
173
|
+
if (depth === 4)
|
|
174
|
+
return 2;
|
|
175
|
+
return 3;
|
|
176
|
+
}
|
|
133
177
|
function resolveClerkItem(item) {
|
|
134
178
|
const isH3 = item.depth === 3;
|
|
135
179
|
const rawTitle = typeof item.title === 'string' ? item.title : '';
|
|
136
180
|
const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
|
|
137
181
|
let stepNumber = isH3 && isStep ? String(displayStep) : null;
|
|
138
182
|
let resolvedContent = item.title;
|
|
183
|
+
let fullTitle = rawTitle || null;
|
|
139
184
|
if (isH3 && isStep) {
|
|
140
185
|
resolvedContent = content !== null && content !== void 0 ? content : item.title;
|
|
186
|
+
if (typeof content === 'string') {
|
|
187
|
+
fullTitle = content;
|
|
188
|
+
}
|
|
141
189
|
}
|
|
142
190
|
if (isH3 && !stepNumber) {
|
|
143
191
|
const urlNum = getDigitsFromUrl(item.url);
|
|
@@ -148,18 +196,30 @@ function resolveClerkItem(item) {
|
|
|
148
196
|
const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
|
|
149
197
|
if (match === null || match === void 0 ? void 0 : match[2]) {
|
|
150
198
|
resolvedContent = match[2];
|
|
199
|
+
fullTitle = match[2];
|
|
151
200
|
}
|
|
152
201
|
}
|
|
153
202
|
}
|
|
154
203
|
}
|
|
204
|
+
if (typeof resolvedContent === 'string') {
|
|
205
|
+
fullTitle = resolvedContent;
|
|
206
|
+
resolvedContent = truncateClerkLabel(resolvedContent);
|
|
207
|
+
}
|
|
155
208
|
return {
|
|
156
209
|
item,
|
|
157
210
|
resolvedContent,
|
|
211
|
+
fullTitle,
|
|
158
212
|
stepNumber,
|
|
159
213
|
itemPadding: getItemOffset(item.depth),
|
|
160
214
|
lineOffset: getVisualLinePosition(item.depth),
|
|
161
215
|
};
|
|
162
216
|
}
|
|
217
|
+
function truncateClerkLabel(value) {
|
|
218
|
+
const normalized = value.trim();
|
|
219
|
+
if (normalized.length <= CLERK_MAX_LABEL_LENGTH)
|
|
220
|
+
return normalized;
|
|
221
|
+
return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
|
|
222
|
+
}
|
|
163
223
|
function buildOutlinePath(items) {
|
|
164
224
|
if (items.length === 0)
|
|
165
225
|
return '';
|
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* References most of its code and SVG animation design from
|
|
5
|
+
* https://github.com/fuma-nama/fumadocs/blob/dev/packages/radix-ui/src/components/toc/clerk.tsx
|
|
6
|
+
*/
|
|
3
7
|
import * as Primitive from 'fumadocs-core/toc';
|
|
4
8
|
import {
|
|
5
9
|
PageTOC,
|
|
@@ -11,6 +15,7 @@ import {
|
|
|
11
15
|
import {
|
|
12
16
|
type ComponentProps,
|
|
13
17
|
type ReactNode,
|
|
18
|
+
useEffect,
|
|
14
19
|
useLayoutEffect,
|
|
15
20
|
useMemo,
|
|
16
21
|
useRef,
|
|
@@ -35,6 +40,7 @@ type PortableClerkTOCProps = {
|
|
|
35
40
|
type ClerkItemMeta = {
|
|
36
41
|
item: TOCItemType;
|
|
37
42
|
resolvedContent: ReactNode;
|
|
43
|
+
fullTitle: string | null;
|
|
38
44
|
stepNumber: string | null;
|
|
39
45
|
itemPadding: number;
|
|
40
46
|
lineOffset: number;
|
|
@@ -57,6 +63,18 @@ const CLERK_TURN_CURVE_HEIGHT = 12;
|
|
|
57
63
|
const CLERK_TURN_CONTROL_FACTOR = 0.68;
|
|
58
64
|
// Safety margin that keeps turns away from the heading rows themselves.
|
|
59
65
|
const CLERK_TURN_GAP_MARGIN = 7;
|
|
66
|
+
// Shared duration for active rail fade transitions and endpoint dot movement.
|
|
67
|
+
const CLERK_ACTIVE_ANIMATION_DURATION_MS = 300;
|
|
68
|
+
// Easing curve for the active rail and dot; tuned for a slightly delayed, softer motion.
|
|
69
|
+
const CLERK_ACTIVE_ANIMATION_EASING = 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
70
|
+
// Horizontal gap between the path centerline and the heading text start.
|
|
71
|
+
const CLERK_TEXT_GAP_FROM_PATH = 12;
|
|
72
|
+
// Radius of numbered step badges rendered on top of the path centerline.
|
|
73
|
+
const CLERK_STEP_BADGE_RADIUS = 7;
|
|
74
|
+
// Visual line offsets by grouped heading depth: 1/2, 3, 4, >4.
|
|
75
|
+
const CLERK_DEPTH_GROUP_LINE_OFFSETS = [6, 18, 30, 42] as const;
|
|
76
|
+
// Max number of characters rendered for a TOC label before trimming with ellipsis.
|
|
77
|
+
const CLERK_MAX_LABEL_LENGTH = 44;
|
|
60
78
|
|
|
61
79
|
export function PortableClerkTOC({
|
|
62
80
|
toc,
|
|
@@ -237,6 +255,7 @@ export function PortableClerkTOCItems({
|
|
|
237
255
|
item={meta.item}
|
|
238
256
|
isActive={activeAnchors.includes(meta.item.url.slice(1))}
|
|
239
257
|
resolvedContent={meta.resolvedContent}
|
|
258
|
+
fullTitle={meta.fullTitle}
|
|
240
259
|
itemPadding={meta.itemPadding}
|
|
241
260
|
contentRef={(node: HTMLSpanElement | null) => {
|
|
242
261
|
contentRefs.current[i] = node;
|
|
@@ -254,6 +273,7 @@ function PortableClerkTOCItem({
|
|
|
254
273
|
item,
|
|
255
274
|
isActive,
|
|
256
275
|
resolvedContent,
|
|
276
|
+
fullTitle,
|
|
257
277
|
itemPadding,
|
|
258
278
|
contentRef,
|
|
259
279
|
ref,
|
|
@@ -261,6 +281,7 @@ function PortableClerkTOCItem({
|
|
|
261
281
|
item: TOCItemType;
|
|
262
282
|
isActive: boolean;
|
|
263
283
|
resolvedContent: ReactNode;
|
|
284
|
+
fullTitle: string | null;
|
|
264
285
|
itemPadding: number;
|
|
265
286
|
contentRef?: ((node: HTMLSpanElement | null) => void) | null;
|
|
266
287
|
ref?: ((node: HTMLAnchorElement | null) => void) | null;
|
|
@@ -270,15 +291,19 @@ function PortableClerkTOCItem({
|
|
|
270
291
|
ref={ref}
|
|
271
292
|
href={item.url}
|
|
272
293
|
data-clerk-item=""
|
|
294
|
+
title={fullTitle ?? undefined}
|
|
273
295
|
style={{
|
|
274
296
|
paddingInlineStart: itemPadding,
|
|
275
297
|
}}
|
|
276
298
|
className={cn(
|
|
277
|
-
'prose group relative py-1.5 text-sm transition-colors
|
|
299
|
+
'prose group relative py-1.5 text-sm transition-colors first:pt-0 last:pb-0 hover:text-fd-accent-foreground',
|
|
278
300
|
isActive ? themeIconColor : 'text-fd-muted-foreground',
|
|
279
301
|
)}
|
|
280
302
|
>
|
|
281
|
-
<span
|
|
303
|
+
<span
|
|
304
|
+
ref={contentRef}
|
|
305
|
+
className="relative z-10 block overflow-hidden text-ellipsis whitespace-nowrap"
|
|
306
|
+
>
|
|
282
307
|
{resolvedContent}
|
|
283
308
|
</span>
|
|
284
309
|
</Primitive.TOCItem>
|
|
@@ -298,9 +323,39 @@ function ClerkOutline({
|
|
|
298
323
|
activeAnchors: string[];
|
|
299
324
|
activeEndpoint: { x: number; y: number } | null;
|
|
300
325
|
}) {
|
|
301
|
-
if (!path) return null;
|
|
302
|
-
|
|
303
326
|
const activeSet = new Set(activeAnchors);
|
|
327
|
+
const [displayPath, setDisplayPath] = useState(activePath);
|
|
328
|
+
const [fadingPath, setFadingPath] = useState<string | null>(null);
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (activePath === displayPath) return;
|
|
332
|
+
if (!displayPath) {
|
|
333
|
+
setDisplayPath(activePath);
|
|
334
|
+
setFadingPath(null);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
setFadingPath(displayPath);
|
|
339
|
+
setDisplayPath(activePath);
|
|
340
|
+
|
|
341
|
+
const timeout = window.setTimeout(() => {
|
|
342
|
+
setFadingPath(null);
|
|
343
|
+
}, CLERK_ACTIVE_ANIMATION_DURATION_MS);
|
|
344
|
+
|
|
345
|
+
return () => window.clearTimeout(timeout);
|
|
346
|
+
}, [activePath, displayPath]);
|
|
347
|
+
|
|
348
|
+
const dotTranslate = activeEndpoint
|
|
349
|
+
? `translate(${activeEndpoint.x - CLERK_ACTIVE_DOT_RADIUS}px, ${
|
|
350
|
+
activeEndpoint.y - CLERK_ACTIVE_DOT_RADIUS
|
|
351
|
+
}px)`
|
|
352
|
+
: undefined;
|
|
353
|
+
const transitionStyle = {
|
|
354
|
+
transitionDuration: `${CLERK_ACTIVE_ANIMATION_DURATION_MS}ms`,
|
|
355
|
+
transitionTimingFunction: CLERK_ACTIVE_ANIMATION_EASING,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
if (!path) return null;
|
|
304
359
|
|
|
305
360
|
return (
|
|
306
361
|
<>
|
|
@@ -325,32 +380,50 @@ function ClerkOutline({
|
|
|
325
380
|
width="100%"
|
|
326
381
|
height="100%"
|
|
327
382
|
>
|
|
328
|
-
{
|
|
383
|
+
{fadingPath ? (
|
|
329
384
|
<path
|
|
330
|
-
d={
|
|
385
|
+
d={fadingPath}
|
|
331
386
|
fill="none"
|
|
332
387
|
strokeWidth={CLERK_PATH_STROKE_WIDTH}
|
|
333
388
|
strokeLinecap="round"
|
|
334
389
|
strokeLinejoin="round"
|
|
335
390
|
stroke={themeSvgIconColor}
|
|
391
|
+
className="transition-opacity"
|
|
392
|
+
style={{
|
|
393
|
+
opacity: 0,
|
|
394
|
+
...transitionStyle,
|
|
395
|
+
}}
|
|
336
396
|
/>
|
|
337
397
|
) : null}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
398
|
+
{displayPath ? (
|
|
399
|
+
<path
|
|
400
|
+
d={displayPath}
|
|
401
|
+
fill="none"
|
|
402
|
+
strokeWidth={CLERK_PATH_STROKE_WIDTH}
|
|
403
|
+
strokeLinecap="round"
|
|
404
|
+
strokeLinejoin="round"
|
|
405
|
+
stroke={themeSvgIconColor}
|
|
406
|
+
className="transition-opacity"
|
|
407
|
+
style={{
|
|
408
|
+
opacity: 1,
|
|
409
|
+
...transitionStyle,
|
|
410
|
+
}}
|
|
351
411
|
/>
|
|
352
412
|
) : null}
|
|
353
413
|
</svg>
|
|
414
|
+
<div aria-hidden="true" className="pointer-events-none absolute inset-0 z-0">
|
|
415
|
+
<div
|
|
416
|
+
className="absolute rounded-full transition-transform"
|
|
417
|
+
style={{
|
|
418
|
+
width: CLERK_ACTIVE_DOT_RADIUS * 2,
|
|
419
|
+
height: CLERK_ACTIVE_DOT_RADIUS * 2,
|
|
420
|
+
backgroundColor: themeSvgIconColor,
|
|
421
|
+
transform: dotTranslate,
|
|
422
|
+
opacity: activeEndpoint ? 1 : 0,
|
|
423
|
+
...transitionStyle,
|
|
424
|
+
}}
|
|
425
|
+
/>
|
|
426
|
+
</div>
|
|
354
427
|
<svg
|
|
355
428
|
aria-hidden="true"
|
|
356
429
|
className="pointer-events-none absolute inset-0 z-1 overflow-visible"
|
|
@@ -365,7 +438,7 @@ function ClerkOutline({
|
|
|
365
438
|
return (
|
|
366
439
|
<g key={item.url} transform={`translate(${item.x}, ${item.y})`}>
|
|
367
440
|
<circle
|
|
368
|
-
r=
|
|
441
|
+
r={CLERK_STEP_BADGE_RADIUS}
|
|
369
442
|
fill={isActive ? themeSvgIconColor : undefined}
|
|
370
443
|
className={cn(!isActive && 'fill-black dark:fill-white')}
|
|
371
444
|
/>
|
|
@@ -386,28 +459,40 @@ function ClerkOutline({
|
|
|
386
459
|
}
|
|
387
460
|
|
|
388
461
|
function getItemOffset(depth: number): number {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return
|
|
462
|
+
const lineOffset = getLineOffset(depth);
|
|
463
|
+
const badgeRadius = depth === 3 ? CLERK_STEP_BADGE_RADIUS : 0;
|
|
464
|
+
return lineOffset + badgeRadius + CLERK_TEXT_GAP_FROM_PATH;
|
|
392
465
|
}
|
|
393
466
|
|
|
394
467
|
function getLineOffset(depth: number): number {
|
|
395
|
-
|
|
468
|
+
const group = getDepthGroup(depth);
|
|
469
|
+
return CLERK_DEPTH_GROUP_LINE_OFFSETS[group];
|
|
396
470
|
}
|
|
397
471
|
|
|
398
472
|
function getVisualLinePosition(depth: number): number {
|
|
399
473
|
return getLineOffset(depth);
|
|
400
474
|
}
|
|
401
475
|
|
|
476
|
+
function getDepthGroup(depth: number): number {
|
|
477
|
+
if (depth <= 2) return 0;
|
|
478
|
+
if (depth === 3) return 1;
|
|
479
|
+
if (depth === 4) return 2;
|
|
480
|
+
return 3;
|
|
481
|
+
}
|
|
482
|
+
|
|
402
483
|
function resolveClerkItem(item: TOCItemType): ClerkItemMeta {
|
|
403
484
|
const isH3 = item.depth === 3;
|
|
404
485
|
const rawTitle = typeof item.title === 'string' ? item.title : '';
|
|
405
486
|
const { isStep, displayStep, content } = getStepInfoFromTitle(rawTitle);
|
|
406
487
|
let stepNumber: string | null = isH3 && isStep ? String(displayStep) : null;
|
|
407
488
|
let resolvedContent: ReactNode = item.title;
|
|
489
|
+
let fullTitle: string | null = rawTitle || null;
|
|
408
490
|
|
|
409
491
|
if (isH3 && isStep) {
|
|
410
492
|
resolvedContent = content ?? item.title;
|
|
493
|
+
if (typeof content === 'string') {
|
|
494
|
+
fullTitle = content;
|
|
495
|
+
}
|
|
411
496
|
}
|
|
412
497
|
|
|
413
498
|
if (isH3 && !stepNumber) {
|
|
@@ -419,20 +504,34 @@ function resolveClerkItem(item: TOCItemType): ClerkItemMeta {
|
|
|
419
504
|
const match = rawTitle.match(/^(\d+(?:\.\d+)*\.?)\s+(.+)$/);
|
|
420
505
|
if (match?.[2]) {
|
|
421
506
|
resolvedContent = match[2];
|
|
507
|
+
fullTitle = match[2];
|
|
422
508
|
}
|
|
423
509
|
}
|
|
424
510
|
}
|
|
425
511
|
}
|
|
426
512
|
|
|
513
|
+
if (typeof resolvedContent === 'string') {
|
|
514
|
+
fullTitle = resolvedContent;
|
|
515
|
+
resolvedContent = truncateClerkLabel(resolvedContent);
|
|
516
|
+
}
|
|
517
|
+
|
|
427
518
|
return {
|
|
428
519
|
item,
|
|
429
520
|
resolvedContent,
|
|
521
|
+
fullTitle,
|
|
430
522
|
stepNumber,
|
|
431
523
|
itemPadding: getItemOffset(item.depth),
|
|
432
524
|
lineOffset: getVisualLinePosition(item.depth),
|
|
433
525
|
};
|
|
434
526
|
}
|
|
435
527
|
|
|
528
|
+
function truncateClerkLabel(value: string): string {
|
|
529
|
+
const normalized = value.trim();
|
|
530
|
+
if (normalized.length <= CLERK_MAX_LABEL_LENGTH) return normalized;
|
|
531
|
+
|
|
532
|
+
return `${normalized.slice(0, CLERK_MAX_LABEL_LENGTH).trimEnd()}...`;
|
|
533
|
+
}
|
|
534
|
+
|
|
436
535
|
function buildOutlinePath(items: ClerkItemMeasure[]): string {
|
|
437
536
|
if (items.length === 0) return '';
|
|
438
537
|
|
|
@@ -614,7 +713,7 @@ function mergeRefs<T>(
|
|
|
614
713
|
}
|
|
615
714
|
|
|
616
715
|
try {
|
|
617
|
-
(ref as React.
|
|
716
|
+
(ref as React.RefObject<T | null>).current = node;
|
|
618
717
|
} catch {
|
|
619
718
|
// ignore readonly refs
|
|
620
719
|
}
|
package/src/styles/fuma.css
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
@apply top-20!;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
@media (min-width: 1280px) {
|
|
7
|
+
/* 给桌面端固定 TOC 留一点底部呼吸感,避免视觉上直接压到 footer */
|
|
8
|
+
#nd-toc {
|
|
9
|
+
bottom: 1.25rem !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* 文档主区域补一小段底部缓冲,减少 footer 刚进入视口就与 TOC 相撞 */
|
|
13
|
+
#nd-page {
|
|
14
|
+
padding-bottom: 5rem !important;
|
|
15
|
+
min-height: calc(100dvh - 5rem) !important;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
/* 移动端文章目录下拉框的边距,建议移动端不要目录导航 96px */
|
|
7
20
|
#nd-tocnav {
|
|
8
21
|
@apply top-24!;
|
|
@@ -35,7 +48,7 @@ button[aria-label="Open Search"][data-search=""] {
|
|
|
35
48
|
|
|
36
49
|
/* Custome Fuma Steps */
|
|
37
50
|
.fd-step::before {
|
|
38
|
-
@apply size-5 -
|
|
51
|
+
@apply size-5 -inset-s-2.5 rounded-full;
|
|
39
52
|
background-color: #000;
|
|
40
53
|
color: #fff;
|
|
41
54
|
font-size: 0.75rem;
|
|
@@ -128,4 +141,4 @@ button[aria-label="Open Search"][data-search=""] {
|
|
|
128
141
|
[data-rmiz-modal-img] {
|
|
129
142
|
transition-duration: 0.01ms !important;
|
|
130
143
|
}
|
|
131
|
-
}
|
|
144
|
+
}
|