@syscore/ui-library 1.20.0 → 1.22.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/client/components/icons/AlphaIcon.tsx +33 -4
- package/client/components/icons/UtilityClose.tsx +1 -1
- package/client/components/icons/UtilityCompare.tsx +18 -18
- package/client/components/icons/UtilityFeedback.tsx +3 -3
- package/client/components/icons/UtilityMessage.tsx +4 -4
- package/client/components/icons/UtilityTrash.tsx +9 -9
- package/client/components/icons/achievement-badges/BadgeCertificationGold.tsx +25 -25
- package/client/components/icons/imperative-badges/BadgeImperativePrimary.tsx +9 -9
- package/client/components/icons/imperative-badges/BadgeImperativeSecondary.tsx +13 -13
- package/client/components/ui/accordion.tsx +4 -3
- package/client/components/ui/dialog.tsx +8 -3
- package/client/components/ui/tabs.tsx +352 -204
- package/client/components/ui/tooltip.tsx +2 -2
- package/client/global.css +89 -18
- package/client/ui/MobileNav.stories.tsx +1 -15
- package/client/ui/Tabs.stories.tsx +156 -53
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.es.js +437 -271
- package/package.json +10 -2
|
@@ -1,238 +1,386 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
AnimatePresence,
|
|
5
|
+
animate,
|
|
6
|
+
motion,
|
|
7
|
+
Transition,
|
|
8
|
+
useMotionValue,
|
|
9
|
+
} from "motion/react";
|
|
8
10
|
import { cn } from "@/lib/utils";
|
|
11
|
+
import {
|
|
12
|
+
Children,
|
|
13
|
+
cloneElement,
|
|
14
|
+
createContext,
|
|
15
|
+
isValidElement,
|
|
16
|
+
useCallback,
|
|
17
|
+
useContext,
|
|
18
|
+
useEffect,
|
|
19
|
+
useLayoutEffect,
|
|
20
|
+
useRef,
|
|
21
|
+
useState,
|
|
22
|
+
ReactNode,
|
|
23
|
+
ReactElement,
|
|
24
|
+
} from "react";
|
|
9
25
|
|
|
10
|
-
export type
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
export type TabVariant = "default" | "danger";
|
|
27
|
+
|
|
28
|
+
interface TabsContextValue {
|
|
29
|
+
selectedValue: string;
|
|
30
|
+
setSelectedValue: (value: string) => void;
|
|
31
|
+
direction: number;
|
|
32
|
+
registerValue: (value: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const TabsContext = createContext<TabsContextValue | null>(null);
|
|
13
36
|
|
|
14
|
-
|
|
15
|
-
|
|
37
|
+
function useTabsContext() {
|
|
38
|
+
const ctx = useContext(TabsContext);
|
|
39
|
+
if (!ctx)
|
|
40
|
+
throw new Error("Tabs compound components must be used inside <Tabs>");
|
|
41
|
+
return ctx;
|
|
16
42
|
}
|
|
17
43
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
44
|
+
interface TabsNavContextValue {
|
|
45
|
+
registerRef: (value: string, el: HTMLButtonElement | null) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const TabsNavContext = createContext<TabsNavContextValue | null>(null);
|
|
49
|
+
|
|
50
|
+
function useTabsNavContext() {
|
|
51
|
+
const ctx = useContext(TabsNavContext);
|
|
52
|
+
if (!ctx) throw new Error("TabsTrigger must be used inside <TabsList>");
|
|
53
|
+
return ctx;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const transition: Transition = {
|
|
57
|
+
type: "tween",
|
|
58
|
+
ease: "easeOut",
|
|
21
59
|
duration: 0.15,
|
|
22
60
|
};
|
|
23
61
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
62
|
+
const contentVariants = {
|
|
63
|
+
enter: (dir: number) => ({ opacity: 0, x: dir * 20 }),
|
|
64
|
+
center: { opacity: 1, x: 0 },
|
|
65
|
+
exit: (dir: number) => ({ opacity: 0, x: dir * -20 }),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
function AnimatedHeight({
|
|
69
|
+
children,
|
|
70
|
+
selectedValue,
|
|
71
|
+
}: {
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
selectedValue: string;
|
|
74
|
+
}) {
|
|
75
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
76
|
+
const innerRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
const currentHeight = useRef(0);
|
|
78
|
+
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
const container = containerRef.current;
|
|
81
|
+
const inner = innerRef.current;
|
|
82
|
+
if (!container || !inner) return;
|
|
83
|
+
|
|
84
|
+
const initialHeight = inner.offsetHeight;
|
|
85
|
+
if (currentHeight.current === 0) {
|
|
86
|
+
currentHeight.current = initialHeight;
|
|
87
|
+
container.style.height = `${initialHeight}px`;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Keep observing until the height differs from current (new content has mounted)
|
|
92
|
+
const ro = new ResizeObserver(() => {
|
|
93
|
+
const targetHeight = inner.offsetHeight;
|
|
94
|
+
if (targetHeight > 0 && targetHeight !== currentHeight.current) {
|
|
95
|
+
currentHeight.current = targetHeight;
|
|
96
|
+
ro.disconnect();
|
|
97
|
+
animate(container, { height: targetHeight }, { duration: 0.25, ease: "easeInOut" });
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
ro.observe(inner);
|
|
101
|
+
return () => ro.disconnect();
|
|
102
|
+
}, [selectedValue]);
|
|
30
103
|
|
|
31
|
-
const TabContent = ({ tab }: { tab: Tab }) => {
|
|
32
104
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
animate={{ opacity: 1, y: 0 }}
|
|
37
|
-
exit={{ opacity: 0, y: -10 }}
|
|
38
|
-
transition={transition as Transition}
|
|
39
|
-
>
|
|
40
|
-
{tab.content}
|
|
41
|
-
</motion.div>
|
|
105
|
+
<div ref={containerRef} className="overflow-clip">
|
|
106
|
+
<div ref={innerRef}>{children}</div>
|
|
107
|
+
</div>
|
|
42
108
|
);
|
|
43
|
-
}
|
|
109
|
+
}
|
|
44
110
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
111
|
+
function Tabs({
|
|
112
|
+
children,
|
|
113
|
+
defaultValue,
|
|
114
|
+
className,
|
|
49
115
|
}: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
54
|
-
const [
|
|
55
|
-
|
|
56
|
-
>([]);
|
|
116
|
+
children: ReactNode;
|
|
117
|
+
defaultValue: string;
|
|
118
|
+
className?: string;
|
|
119
|
+
}) {
|
|
120
|
+
const [selectedValue, setSelectedValue] = useState(defaultValue);
|
|
121
|
+
const [direction, setDirection] = useState(0);
|
|
57
122
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
123
|
+
// Stable ordered registry — TabsTrigger registers on mount in render order.
|
|
124
|
+
const valuesRef = useRef<string[]>([]);
|
|
125
|
+
const selectedValueRef = useRef(defaultValue);
|
|
61
126
|
|
|
62
|
-
const
|
|
63
|
-
|
|
127
|
+
const registerValue = useCallback((value: string) => {
|
|
128
|
+
if (!valuesRef.current.includes(value)) {
|
|
129
|
+
valuesRef.current.push(value);
|
|
130
|
+
}
|
|
131
|
+
}, []);
|
|
64
132
|
|
|
65
|
-
const
|
|
133
|
+
const handleSetSelectedValue = useCallback((value: string) => {
|
|
134
|
+
const currentIdx = valuesRef.current.indexOf(selectedValueRef.current);
|
|
135
|
+
const nextIdx = valuesRef.current.indexOf(value);
|
|
136
|
+
setDirection(nextIdx > currentIdx ? 1 : -1);
|
|
137
|
+
selectedValueRef.current = value;
|
|
138
|
+
setSelectedValue(value);
|
|
139
|
+
}, []);
|
|
66
140
|
|
|
67
|
-
const
|
|
68
|
-
|
|
141
|
+
const childArray = Children.toArray(children);
|
|
142
|
+
const contentChildren = childArray.filter(
|
|
143
|
+
(child): child is ReactElement<{ value: string }> =>
|
|
144
|
+
isValidElement(child) && child.type === TabsContent,
|
|
145
|
+
);
|
|
146
|
+
const nonContentChildren = childArray.filter(
|
|
147
|
+
(child) => !(isValidElement(child) && child.type === TabsContent),
|
|
148
|
+
);
|
|
149
|
+
const activeContent = contentChildren.find(
|
|
150
|
+
(child) => child.props.value === selectedValue,
|
|
69
151
|
);
|
|
70
|
-
const hoveredRect =
|
|
71
|
-
buttonRefs[hoveredTabIndex ?? -1]?.getBoundingClientRect();
|
|
72
152
|
|
|
73
153
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
className="tabs-nav"
|
|
77
|
-
onPointerLeave={() => setHoveredTabIndex(null)}
|
|
154
|
+
<TabsContext.Provider
|
|
155
|
+
value={{ selectedValue, setSelectedValue: handleSetSelectedValue, direction, registerValue }}
|
|
78
156
|
>
|
|
79
|
-
{tabs
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
>
|
|
90
|
-
<motion.span
|
|
91
|
-
ref={(el) => {
|
|
92
|
-
buttonRefs[i] = el as HTMLButtonElement;
|
|
93
|
-
}}
|
|
94
|
-
className={cn("tabs-nav-button-text", {
|
|
95
|
-
"tabs-nav-button-text--inactive": !isActive,
|
|
96
|
-
"tabs-nav-button-text--active": isActive,
|
|
97
|
-
})}
|
|
98
|
-
>
|
|
99
|
-
<small
|
|
100
|
-
className={item.value === "danger-zone" ? "tabs-nav-button-text--danger" : ""}
|
|
101
|
-
>
|
|
102
|
-
{item.label}
|
|
103
|
-
</small>
|
|
104
|
-
</motion.span>
|
|
105
|
-
</button>
|
|
106
|
-
);
|
|
107
|
-
})}
|
|
108
|
-
|
|
109
|
-
{/* <AnimatePresence>
|
|
110
|
-
{hoveredRect && navRect && (
|
|
111
|
-
<motion.div
|
|
112
|
-
key="hover"
|
|
113
|
-
className={`absolute z-10 top-0 left-0 rounded-md ${
|
|
114
|
-
hoveredTabIndex ===
|
|
115
|
-
tabs.findIndex(({ value }) => value === "danger-zone")
|
|
116
|
-
? "bg-red-500/50"
|
|
117
|
-
: "bg-gray-100/50"
|
|
118
|
-
}`}
|
|
119
|
-
initial={{
|
|
120
|
-
...getHoverAnimationProps(hoveredRect, navRect),
|
|
121
|
-
opacity: 0,
|
|
122
|
-
}}
|
|
123
|
-
animate={{
|
|
124
|
-
...getHoverAnimationProps(hoveredRect, navRect),
|
|
125
|
-
opacity: 1,
|
|
126
|
-
}}
|
|
127
|
-
exit={{
|
|
128
|
-
...getHoverAnimationProps(hoveredRect, navRect),
|
|
129
|
-
opacity: 0,
|
|
130
|
-
}}
|
|
131
|
-
transition={transition as Transition}
|
|
132
|
-
/>
|
|
133
|
-
)}
|
|
134
|
-
</AnimatePresence> */}
|
|
135
|
-
|
|
136
|
-
<AnimatePresence>
|
|
137
|
-
{selectedRect && navRect && (
|
|
138
|
-
<motion.div
|
|
139
|
-
className={cn("tabs-nav-indicator", {
|
|
140
|
-
"tabs-nav-indicator--danger": selectedTabIndex ===
|
|
141
|
-
tabs.findIndex(({ value }) => value === "danger-zone"),
|
|
142
|
-
"tabs-nav-indicator--default": selectedTabIndex !==
|
|
143
|
-
tabs.findIndex(({ value }) => value === "danger-zone"),
|
|
144
|
-
})}
|
|
145
|
-
initial={false}
|
|
146
|
-
animate={{
|
|
147
|
-
width: selectedRect.width,
|
|
148
|
-
x: selectedRect.left - navRect.left,
|
|
149
|
-
opacity: 1,
|
|
150
|
-
}}
|
|
151
|
-
transition={transition as Transition}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
</AnimatePresence>
|
|
155
|
-
|
|
156
|
-
<div className="tabs-nav-underline" />
|
|
157
|
-
</nav>
|
|
157
|
+
<div className={cn("tabs-container", className)}>
|
|
158
|
+
{nonContentChildren}
|
|
159
|
+
<AnimatedHeight selectedValue={selectedValue}>
|
|
160
|
+
<AnimatePresence mode="wait" custom={direction}>
|
|
161
|
+
{activeContent &&
|
|
162
|
+
cloneElement(activeContent, { key: activeContent.props.value })}
|
|
163
|
+
</AnimatePresence>
|
|
164
|
+
</AnimatedHeight>
|
|
165
|
+
</div>
|
|
166
|
+
</TabsContext.Provider>
|
|
158
167
|
);
|
|
159
|
-
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function TabsList({
|
|
171
|
+
children,
|
|
172
|
+
className,
|
|
173
|
+
containerClassName,
|
|
174
|
+
fullWidth = false,
|
|
175
|
+
}: {
|
|
176
|
+
children: ReactNode;
|
|
177
|
+
className?: string;
|
|
178
|
+
containerClassName?: string;
|
|
179
|
+
fullWidth?: boolean;
|
|
180
|
+
}) {
|
|
181
|
+
const { selectedValue } = useTabsContext();
|
|
182
|
+
const navRef = useRef<HTMLDivElement>(null);
|
|
183
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
184
|
+
const buttonRefs = useRef<Map<string, HTMLButtonElement | null>>(new Map());
|
|
185
|
+
|
|
186
|
+
// Motion values for the indicator — bypasses React state so updates are
|
|
187
|
+
// applied directly to the DOM without re-renders.
|
|
188
|
+
const indicatorX = useMotionValue(-999);
|
|
189
|
+
const indicatorWidth = useMotionValue(0);
|
|
190
|
+
// Only isDanger requires React state (drives a CSS class change).
|
|
191
|
+
const [indicatorColor, setIndicatorColor] = useState<
|
|
192
|
+
"default" | "danger" | null
|
|
193
|
+
>(null);
|
|
194
|
+
|
|
195
|
+
// On initial mount, snap the indicator without animation.
|
|
196
|
+
const isFirstRender = useRef(true);
|
|
197
|
+
|
|
198
|
+
const selectedValueRef = useRef(selectedValue);
|
|
199
|
+
selectedValueRef.current = selectedValue;
|
|
200
|
+
|
|
201
|
+
const updateIndicatorPosition = useCallback(() => {
|
|
202
|
+
const nav = navRef.current;
|
|
203
|
+
const selectedEl = buttonRefs.current.get(selectedValueRef.current);
|
|
204
|
+
if (!nav || !selectedEl) return;
|
|
205
|
+
const navRect = nav.getBoundingClientRect();
|
|
206
|
+
const selectedRect = selectedEl.getBoundingClientRect();
|
|
207
|
+
indicatorX.set(selectedRect.left - navRect.left);
|
|
208
|
+
indicatorWidth.set(selectedRect.width);
|
|
209
|
+
}, [indicatorX, indicatorWidth]);
|
|
160
210
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
tabs.find((tab) => tab.value === "home")?.value || tabs[0].value;
|
|
211
|
+
useEffect(() => {
|
|
212
|
+
const el = containerRef.current;
|
|
213
|
+
if (!el) return;
|
|
165
214
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
215
|
+
const updateScroll = () => {
|
|
216
|
+
const canLeft = el.scrollLeft > 0;
|
|
217
|
+
const canRight = el.scrollLeft < el.scrollWidth - el.clientWidth - 1;
|
|
218
|
+
el.dataset.scrollLeft = canLeft ? "true" : "";
|
|
219
|
+
el.dataset.scrollRight = canRight ? "true" : "";
|
|
171
220
|
};
|
|
172
|
-
});
|
|
173
221
|
|
|
174
|
-
|
|
222
|
+
updateScroll();
|
|
223
|
+
el.addEventListener("scroll", updateScroll, { passive: true });
|
|
224
|
+
const ro = new ResizeObserver(() => {
|
|
225
|
+
updateScroll();
|
|
226
|
+
updateIndicatorPosition();
|
|
227
|
+
});
|
|
228
|
+
ro.observe(el);
|
|
229
|
+
return () => {
|
|
230
|
+
el.removeEventListener("scroll", updateScroll);
|
|
231
|
+
ro.disconnect();
|
|
232
|
+
};
|
|
233
|
+
}, [updateIndicatorPosition]);
|
|
234
|
+
|
|
235
|
+
const registerRef = useCallback(
|
|
236
|
+
(value: string, el: HTMLButtonElement | null) => {
|
|
237
|
+
buttonRefs.current.set(value, el);
|
|
238
|
+
},
|
|
239
|
+
[],
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
useLayoutEffect(() => {
|
|
243
|
+
const nav = navRef.current;
|
|
244
|
+
const container = containerRef.current;
|
|
245
|
+
const selectedEl = buttonRefs.current.get(selectedValue);
|
|
246
|
+
if (!nav || !container || !selectedEl) return;
|
|
247
|
+
|
|
248
|
+
const navRect = nav.getBoundingClientRect();
|
|
249
|
+
const selectedRect = selectedEl.getBoundingClientRect();
|
|
250
|
+
const P = selectedRect.left - navRect.left;
|
|
251
|
+
const W = selectedRect.width;
|
|
252
|
+
const danger = selectedEl.dataset?.variant === "danger";
|
|
253
|
+
|
|
254
|
+
setIndicatorColor(danger ? "danger" : "default");
|
|
255
|
+
|
|
256
|
+
if (isFirstRender.current) {
|
|
257
|
+
isFirstRender.current = false;
|
|
258
|
+
indicatorX.set(P);
|
|
259
|
+
indicatorWidth.set(W);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const target = P - container.clientWidth / 2 + W / 2;
|
|
264
|
+
const clampedTarget = Math.max(
|
|
265
|
+
0,
|
|
266
|
+
Math.min(target, container.scrollWidth - container.clientWidth),
|
|
267
|
+
);
|
|
268
|
+
const needsScroll = Math.abs(clampedTarget - container.scrollLeft) > 1;
|
|
269
|
+
|
|
270
|
+
if (needsScroll) {
|
|
271
|
+
indicatorX.set(P);
|
|
272
|
+
indicatorWidth.set(W);
|
|
273
|
+
container.scrollTo({ left: clampedTarget, behavior: "smooth" });
|
|
274
|
+
} else {
|
|
275
|
+
animate(indicatorX, P, transition);
|
|
276
|
+
animate(indicatorWidth, W, transition);
|
|
277
|
+
}
|
|
278
|
+
}, [selectedValue, indicatorX, indicatorWidth]);
|
|
175
279
|
|
|
176
280
|
return (
|
|
177
|
-
<
|
|
178
|
-
<div
|
|
179
|
-
|
|
281
|
+
<TabsNavContext.Provider value={{ registerRef }}>
|
|
282
|
+
<div
|
|
283
|
+
ref={containerRef}
|
|
284
|
+
className={cn("tabs-container-inner", containerClassName)}
|
|
285
|
+
>
|
|
286
|
+
<nav
|
|
287
|
+
ref={navRef}
|
|
288
|
+
className={cn(
|
|
289
|
+
"tabs-nav",
|
|
290
|
+
{ "tabs-nav--full-width": fullWidth },
|
|
291
|
+
className,
|
|
292
|
+
)}
|
|
293
|
+
>
|
|
294
|
+
{children}
|
|
295
|
+
|
|
296
|
+
{indicatorColor !== null && (
|
|
297
|
+
<motion.div
|
|
298
|
+
className={cn("tabs-nav-indicator", {
|
|
299
|
+
"tabs-nav-indicator--danger": indicatorColor === "danger",
|
|
300
|
+
"tabs-nav-indicator--default": indicatorColor === "default",
|
|
301
|
+
})}
|
|
302
|
+
style={{ x: indicatorX, width: indicatorWidth }}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
|
|
306
|
+
<div className="tabs-nav-underline" />
|
|
307
|
+
</nav>
|
|
180
308
|
</div>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
309
|
+
</TabsNavContext.Provider>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function TabsTrigger({
|
|
314
|
+
value,
|
|
315
|
+
children,
|
|
316
|
+
className,
|
|
317
|
+
variant = "default",
|
|
318
|
+
}: {
|
|
319
|
+
value: string;
|
|
320
|
+
children: ReactNode;
|
|
321
|
+
className?: string;
|
|
322
|
+
variant?: TabVariant;
|
|
323
|
+
}) {
|
|
324
|
+
const { selectedValue, setSelectedValue, registerValue } = useTabsContext();
|
|
325
|
+
const { registerRef } = useTabsNavContext();
|
|
326
|
+
|
|
327
|
+
useLayoutEffect(() => {
|
|
328
|
+
registerValue(value);
|
|
329
|
+
}, [registerValue, value]);
|
|
330
|
+
const isActive = selectedValue === value;
|
|
331
|
+
|
|
332
|
+
const refCallback = useCallback(
|
|
333
|
+
(el: HTMLButtonElement | null) => registerRef(value, el),
|
|
334
|
+
[registerRef, value],
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const handleClick = useCallback(
|
|
338
|
+
() => setSelectedValue(value),
|
|
339
|
+
[setSelectedValue, value],
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<button
|
|
344
|
+
className={cn("tabs-nav-button", className)}
|
|
345
|
+
data-variant={variant}
|
|
346
|
+
onClick={handleClick}
|
|
347
|
+
ref={refCallback}
|
|
348
|
+
>
|
|
349
|
+
<span
|
|
350
|
+
className={cn("tabs-nav-button-text overline-large", {
|
|
351
|
+
"tabs-nav-button-text--inactive": !isActive,
|
|
352
|
+
"tabs-nav-button-text--active": isActive,
|
|
353
|
+
})}
|
|
354
|
+
>
|
|
355
|
+
{children}
|
|
356
|
+
</span>
|
|
357
|
+
</button>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function TabsContent({
|
|
362
|
+
children,
|
|
363
|
+
className,
|
|
364
|
+
}: {
|
|
365
|
+
value: string;
|
|
366
|
+
children: ReactNode;
|
|
367
|
+
className?: string;
|
|
368
|
+
}) {
|
|
369
|
+
const { direction } = useTabsContext();
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<motion.div
|
|
373
|
+
className={className}
|
|
374
|
+
custom={direction}
|
|
375
|
+
variants={contentVariants}
|
|
376
|
+
initial="enter"
|
|
377
|
+
animate="center"
|
|
378
|
+
exit="exit"
|
|
379
|
+
transition={transition}
|
|
380
|
+
>
|
|
381
|
+
{children}
|
|
382
|
+
</motion.div>
|
|
185
383
|
);
|
|
186
384
|
}
|
|
187
385
|
|
|
188
|
-
|
|
189
|
-
const TabsRoot = React.forwardRef<
|
|
190
|
-
React.ElementRef<typeof TabsPrimitive.Root>,
|
|
191
|
-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
|
192
|
-
>(({ className, ...props }, ref) => (
|
|
193
|
-
<TabsPrimitive.Root
|
|
194
|
-
ref={ref}
|
|
195
|
-
className={cn("tabs", className)}
|
|
196
|
-
{...props}
|
|
197
|
-
/>
|
|
198
|
-
));
|
|
199
|
-
TabsRoot.displayName = TabsPrimitive.Root.displayName;
|
|
200
|
-
|
|
201
|
-
const TabsList = React.forwardRef<
|
|
202
|
-
React.ElementRef<typeof TabsPrimitive.List>,
|
|
203
|
-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
|
204
|
-
>(({ className, ...props }, ref) => (
|
|
205
|
-
<TabsPrimitive.List
|
|
206
|
-
ref={ref}
|
|
207
|
-
className={cn("tabs-list", className)}
|
|
208
|
-
{...props}
|
|
209
|
-
/>
|
|
210
|
-
));
|
|
211
|
-
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
212
|
-
|
|
213
|
-
const TabsTrigger = React.forwardRef<
|
|
214
|
-
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
215
|
-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
|
216
|
-
>(({ className, ...props }, ref) => (
|
|
217
|
-
<TabsPrimitive.Trigger
|
|
218
|
-
ref={ref}
|
|
219
|
-
className={cn("tabs-trigger", className)}
|
|
220
|
-
{...props}
|
|
221
|
-
/>
|
|
222
|
-
));
|
|
223
|
-
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
224
|
-
|
|
225
|
-
const TabsContent = React.forwardRef<
|
|
226
|
-
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
227
|
-
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
|
228
|
-
>(({ className, ...props }, ref) => (
|
|
229
|
-
<TabsPrimitive.Content
|
|
230
|
-
ref={ref}
|
|
231
|
-
className={cn("tabs-content", className)}
|
|
232
|
-
{...props}
|
|
233
|
-
/>
|
|
234
|
-
));
|
|
235
|
-
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
236
|
-
|
|
237
|
-
// Export standard Radix UI tabs components
|
|
238
|
-
export { TabsRoot as Tabs, TabsList, TabsTrigger, TabsContent };
|
|
386
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -228,7 +228,7 @@ function TooltipContent({
|
|
|
228
228
|
</div>
|
|
229
229
|
)}
|
|
230
230
|
{variant !== "simple" && !hideClose && (
|
|
231
|
-
<ToggleClose
|
|
231
|
+
<ToggleClose />
|
|
232
232
|
)}
|
|
233
233
|
{children}
|
|
234
234
|
</TooltipPrimitive.Content>
|
|
@@ -243,7 +243,7 @@ function ToggleClose({ className }: { className?: string }) {
|
|
|
243
243
|
<button
|
|
244
244
|
data-slot="tooltip-close"
|
|
245
245
|
onClick={close}
|
|
246
|
-
className={
|
|
246
|
+
className={className}
|
|
247
247
|
>
|
|
248
248
|
<UtilityClose />
|
|
249
249
|
</button>
|