@syscore/ui-library 1.1.9 → 1.1.11
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/App.tsx +47 -0
- package/client/components/icons/ConceptIcons.tsx +667 -0
- package/client/components/icons/NavAccount.tsx +31 -0
- package/client/components/icons/NavBullet.tsx +19 -0
- package/client/components/icons/NavLogo.tsx +36 -0
- package/client/components/icons/ProviderBadges.tsx +295 -0
- package/client/components/icons/ProviderSeals.tsx +319 -0
- package/client/components/icons/SealHealthSafetyRating.tsx +65 -0
- package/client/components/icons/SealIwbiMember.tsx +86 -0
- package/client/components/icons/SealWell.tsx +84 -0
- package/client/components/icons/SealWellCertification.tsx +138 -0
- package/client/components/icons/SealWellCommunity.tsx +122 -0
- package/client/components/icons/SealWellResidence.tsx +122 -0
- package/client/components/icons/SealWorksWithWell.tsx +140 -0
- package/client/components/icons/UtilityAccordion.tsx +21 -0
- package/client/components/icons/UtilityChevronDown.tsx +36 -0
- package/client/components/icons/UtilityClassification.tsx +45 -0
- package/client/components/icons/UtilityClose.tsx +41 -0
- package/client/components/icons/UtilityDrag.tsx +69 -0
- package/client/components/icons/UtilityEdit.tsx +42 -0
- package/client/components/icons/UtilityOptions.tsx +45 -0
- package/client/components/icons/UtilityPortfolio.tsx +87 -0
- package/client/components/icons/UtilityReset.tsx +41 -0
- package/client/components/icons/UtilityScoring.tsx +43 -0
- package/client/components/icons/UtilitySearch.tsx +38 -0
- package/client/components/icons/UtilitySort.tsx +52 -0
- package/client/components/icons/UtilityText.tsx +34 -0
- package/client/components/icons/WaterMarkWWWProducts.tsx +26 -0
- package/client/components/icons/WaterMarkWellProjects.tsx +30 -0
- package/client/components/icons/WatermarkMemberOrg.tsx +59 -0
- package/client/components/icons/WellSeal.tsx +79 -0
- package/client/components/icons/X.tsx +35 -0
- package/client/components/ui/Navigation.tsx +958 -0
- package/client/components/ui/SearchField.tsx +157 -0
- package/client/components/ui/StrategyTable.tsx +303 -0
- package/client/components/ui/Tag.tsx +127 -0
- package/client/components/ui/alert-dialog.tsx +1 -1
- package/client/components/ui/button.tsx +67 -127
- package/client/components/ui/calendar.tsx +2 -2
- package/client/components/ui/card.tsx +10 -13
- package/client/components/ui/carousel.tsx +56 -46
- package/client/components/ui/command.tsx +27 -16
- package/client/components/ui/dialog.tsx +113 -92
- package/client/components/ui/label.tsx +5 -3
- package/client/components/ui/menubar.tsx +1 -1
- package/client/components/ui/pagination.tsx +3 -3
- package/client/components/ui/sidebar.tsx +1 -1
- package/client/components/ui/tabs.tsx +350 -5
- package/client/components/ui/toggle.tsx +71 -19
- package/client/components/ui/tooltip.tsx +69 -18
- package/client/global.css +635 -58
- package/client/hooks/UseTabs.tsx +35 -0
- package/client/hooks/use-mobile.tsx +21 -0
- package/client/hooks/use-segmented-control.ts +42 -0
- package/client/hooks/use-toast.ts +188 -0
- package/client/pages/Index.tsx +88 -0
- package/client/pages/NotFound.tsx +29 -0
- package/client/ui/Accordion/Accordion.stories.tsx +74 -0
- package/client/ui/Alert/Alert.stories.tsx +82 -0
- package/client/ui/AlertDialog/AlertDialog.stories.tsx +106 -0
- package/client/ui/AspectRatio.stories.tsx +78 -0
- package/client/ui/Avatar/Avatar.stories.tsx +94 -0
- package/client/ui/Badge/Badge.stories.tsx +60 -0
- package/client/ui/Breadcrumb/Breadcrumb.stories.tsx +97 -0
- package/client/ui/Button.stories.tsx +429 -0
- package/client/ui/Calendar/Calendar.stories.tsx +99 -0
- package/client/ui/Card.stories.tsx +84 -0
- package/client/ui/Carousel/Carousel.stories.tsx +85 -0
- package/client/ui/Chart/Chart.stories.tsx +58 -0
- package/client/ui/Checkbox/Checkbox.stories.tsx +112 -0
- package/client/ui/Collapsible/Collapsible.stories.tsx +101 -0
- package/client/ui/Colors.stories.tsx +1041 -0
- package/client/ui/Command/Command.stories.tsx +97 -0
- package/client/ui/ContextMenu/ContextMenu.stories.tsx +74 -0
- package/client/ui/Dialog.stories.tsx +69 -0
- package/client/ui/Drawer/Drawer.stories.tsx +87 -0
- package/client/ui/DropdownMenu/DropdownMenu.stories.tsx +139 -0
- package/client/ui/Form/Form.stories.tsx +74 -0
- package/client/ui/HoverCard/HoverCard.stories.tsx +94 -0
- package/client/ui/Icons.stories.tsx +328 -0
- package/client/ui/Input/Input.stories.tsx +69 -0
- package/client/ui/InputOTP/InputOTP.stories.tsx +85 -0
- package/client/ui/Label.stories.tsx +66 -0
- package/client/ui/Menubar/Menubar.stories.tsx +88 -0
- package/client/ui/Navigation.stories.tsx +57 -0
- package/client/ui/NavigationMenu/NavigationMenu.stories.tsx +106 -0
- package/client/ui/Pagination/Pagination.stories.tsx +115 -0
- package/client/ui/Popover/Popover.stories.tsx +99 -0
- package/client/ui/Progress/Progress.stories.tsx +63 -0
- package/client/ui/RadioGroup/RadioGroup.stories.tsx +110 -0
- package/client/ui/Resizable/Resizable.stories.tsx +88 -0
- package/client/ui/ScrollArea/ScrollArea.stories.tsx +64 -0
- package/client/ui/SearchField.stories.tsx +63 -0
- package/client/ui/Select/Select.stories.tsx +111 -0
- package/client/ui/Separator/Separator.stories.tsx +67 -0
- package/client/ui/Sheet/Sheet.stories.tsx +138 -0
- package/client/ui/Sidebar/Sidebar.stories.tsx +92 -0
- package/client/ui/Skeleton/Skeleton.stories.tsx +65 -0
- package/client/ui/Slider/Slider.stories.tsx +101 -0
- package/client/ui/Sonner/Sonner.stories.tsx +48 -0
- package/client/ui/StrategyTable.stories.tsx +138 -0
- package/client/ui/Switch/Switch.stories.tsx +96 -0
- package/client/ui/Table/Table.stories.tsx +135 -0
- package/client/ui/Tabs.stories.tsx +33 -0
- package/client/ui/Tag.stories.tsx +190 -0
- package/client/ui/Textarea/Textarea.stories.tsx +56 -0
- package/client/ui/Toast/Toast.stories.tsx +76 -0
- package/client/ui/Toaster/Toaster.stories.tsx +52 -0
- package/client/ui/Toggle.stories.tsx +248 -0
- package/client/ui/ToggleGroup/ToggleGroup.stories.tsx +88 -0
- package/client/ui/Tooltip.stories.tsx +72 -0
- package/client/ui/Typography.stories.tsx +421 -0
- package/client/ui/WELLDashboard/WELLDashboard.stories.tsx +115 -0
- package/client/ui/WELLDashboard/index.tsx +221 -0
- package/client/vite-env.d.ts +1 -0
- package/dist/ui/fonts/FT-Made/FTMade-Regular.otf +0 -0
- package/dist/ui/fonts/FT-Made/FTMade-Regular.ttf +0 -0
- package/dist/ui/fonts/FT-Made/FTMade-Regular.woff +0 -0
- package/dist/ui/fonts/FT-Made/FTMade-Regular.woff2 +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-black.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-blackitalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-bold.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-bolditalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-extrabold.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-extrabolditalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-extralight.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-extralightitalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-italic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-light.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-lightitalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-medium.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-mediumitalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-regular.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-semibold.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-semibolditalic.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-thin.otf +0 -0
- package/dist/ui/fonts/Mazzard-M/mazzardsoftm-thinitalic.otf +0 -0
- package/dist/ui/index.cjs.js +1 -1
- package/dist/ui/index.d.ts +2 -1
- package/dist/ui/index.es.js +563 -329
- package/package.json +4 -2
|
@@ -1,9 +1,353 @@
|
|
|
1
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
2
6
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
3
7
|
|
|
8
|
+
import { useTabs, type Tab } from "../../hooks/UseTabs";
|
|
4
9
|
import { cn } from "@/lib/utils";
|
|
5
10
|
|
|
6
|
-
|
|
11
|
+
interface AnimatedTabsProps {
|
|
12
|
+
tabs: Tab[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const transition = {
|
|
16
|
+
type: "tween" as const,
|
|
17
|
+
ease: "easeOut" as const,
|
|
18
|
+
duration: 0.15,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getHoverAnimationProps = (hoveredRect: DOMRect, navRect: DOMRect) => ({
|
|
22
|
+
x: hoveredRect.left - navRect.left - 10,
|
|
23
|
+
y: hoveredRect.top - navRect.top - 4,
|
|
24
|
+
width: hoveredRect.width + 20,
|
|
25
|
+
height: hoveredRect.height + 10,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const TabContent = ({ tab }: { tab: Tab }) => {
|
|
29
|
+
return (
|
|
30
|
+
<motion.div
|
|
31
|
+
initial={{ opacity: 0, y: 10 }}
|
|
32
|
+
animate={{ opacity: 1, y: 0 }}
|
|
33
|
+
exit={{ opacity: 0, y: -10 }}
|
|
34
|
+
transition={transition}
|
|
35
|
+
className="p-6 bg-zinc-50 dark:bg-zinc-900 rounded-lg mt-4 h-[55vh]"
|
|
36
|
+
>
|
|
37
|
+
{tab.value === "home" && (
|
|
38
|
+
<div className="space-y-6">
|
|
39
|
+
<div className="border-b pb-6 dark:border-zinc-800">
|
|
40
|
+
<h2 className="text-3xl font-bold mb-4">Welcome to Our Platform</h2>
|
|
41
|
+
<p className="text-zinc-600 dark:text-zinc-400 mb-4">
|
|
42
|
+
Discover a new way to manage your projects and collaborate with
|
|
43
|
+
your team.
|
|
44
|
+
</p>
|
|
45
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
46
|
+
<div className="p-4 bg-white dark:bg-zinc-800 rounded-lg">
|
|
47
|
+
<h3 className="font-semibold mb-2">Quick Start</h3>
|
|
48
|
+
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
49
|
+
Get started with our platform in minutes
|
|
50
|
+
</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="p-4 bg-white dark:bg-zinc-800 rounded-lg">
|
|
53
|
+
<h3 className="font-semibold mb-2">Latest Updates</h3>
|
|
54
|
+
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
55
|
+
See what's new in our latest release
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="p-4 bg-white dark:bg-zinc-800 rounded-lg">
|
|
59
|
+
<h3 className="font-semibold mb-2">Resources</h3>
|
|
60
|
+
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
|
61
|
+
Access guides and documentation
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
<div>
|
|
67
|
+
<h3 className="text-xl font-semibold mb-4">Recent Activity</h3>
|
|
68
|
+
<div className="space-y-3">
|
|
69
|
+
<div className="flex items-center gap-3 p-3 bg-white dark:bg-zinc-800 rounded-lg">
|
|
70
|
+
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
|
71
|
+
<p className="text-sm">
|
|
72
|
+
Project "Marketing Campaign" was updated 2 hours ago
|
|
73
|
+
</p>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="flex items-center gap-3 p-3 bg-white dark:bg-zinc-800 rounded-lg">
|
|
76
|
+
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
77
|
+
<p className="text-sm">
|
|
78
|
+
New team member John Doe joined the platform
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
{tab.value === "about" && (
|
|
86
|
+
<div className="space-y-8">
|
|
87
|
+
<div>
|
|
88
|
+
<h2 className="text-3xl font-bold mb-4">About Us</h2>
|
|
89
|
+
<p className="text-zinc-600 dark:text-zinc-400 mb-6">
|
|
90
|
+
We're a team of passionate individuals working together to
|
|
91
|
+
create amazing solutions for our clients.
|
|
92
|
+
</p>
|
|
93
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
94
|
+
<div>
|
|
95
|
+
<h3 className="text-xl font-semibold mb-3">Our Mission</h3>
|
|
96
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
97
|
+
To empower businesses with innovative tools and solutions that
|
|
98
|
+
drive growth and success in the digital age.
|
|
99
|
+
</p>
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<h3 className="text-xl font-semibold mb-3">Our Vision</h3>
|
|
103
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
104
|
+
To become the leading platform for business transformation and
|
|
105
|
+
digital innovation worldwide.
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
{tab.value === "contact" && (
|
|
113
|
+
<div className="max-w-2xl mx-auto">
|
|
114
|
+
<h2 className="text-3xl font-bold mb-6">Get in Touch</h2>
|
|
115
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
|
|
116
|
+
<div>
|
|
117
|
+
<h3 className="text-xl font-semibold mb-4">
|
|
118
|
+
Contact Information
|
|
119
|
+
</h3>
|
|
120
|
+
<div className="space-y-4">
|
|
121
|
+
<div>
|
|
122
|
+
<p className="font-medium">Address</p>
|
|
123
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
124
|
+
123 Business Street
|
|
125
|
+
</p>
|
|
126
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
127
|
+
San Francisco, CA 94105
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
<div>
|
|
131
|
+
<p className="font-medium">Email</p>
|
|
132
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
133
|
+
contact@example.com
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
<div>
|
|
137
|
+
<p className="font-medium">Phone</p>
|
|
138
|
+
<p className="text-zinc-600 dark:text-zinc-400">
|
|
139
|
+
+1 (555) 123-4567
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div className="space-y-4">
|
|
145
|
+
<div>
|
|
146
|
+
<label className="block text-sm font-medium mb-1">Name</label>
|
|
147
|
+
<input
|
|
148
|
+
type="text"
|
|
149
|
+
className="w-full p-2 rounded-md border dark:bg-zinc-800 dark:border-zinc-700"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
<div>
|
|
153
|
+
<label className="block text-sm font-medium mb-1">Email</label>
|
|
154
|
+
<input
|
|
155
|
+
type="email"
|
|
156
|
+
className="w-full p-2 rounded-md border dark:bg-zinc-800 dark:border-zinc-700"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
<button className="w-full bg-black text-white dark:bg-white dark:text-black py-2 rounded-md font-medium">
|
|
160
|
+
Send Message
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
{tab.value === "danger-zone" && (
|
|
167
|
+
<div className="max-w-2xl mx-auto">
|
|
168
|
+
<h2 className="text-2xl font-bold mb-6 text-red-500">Caution!</h2>
|
|
169
|
+
<div className="space-y-6">
|
|
170
|
+
<div className="border border-red-200 dark:border-red-900 rounded-lg p-6 bg-red-50 dark:bg-red-900/20">
|
|
171
|
+
<h3 className="text-lg font-semibold text-red-600 dark:text-red-400 mb-2">
|
|
172
|
+
Delete Account
|
|
173
|
+
</h3>
|
|
174
|
+
<p className="text-red-600/80 dark:text-red-400/80 mb-4">
|
|
175
|
+
This action cannot be undone. This will permanently delete your
|
|
176
|
+
account and remove your data from our servers.
|
|
177
|
+
</p>
|
|
178
|
+
<div className="flex items-center gap-4">
|
|
179
|
+
<input
|
|
180
|
+
type="text"
|
|
181
|
+
placeholder="Type 'delete' to confirm"
|
|
182
|
+
className="flex-1 p-2 rounded-md border border-red-200 dark:border-red-800 dark:bg-red-900/30"
|
|
183
|
+
/>
|
|
184
|
+
<button className="bg-red-500 text-white px-4 py-2 rounded-md hover:bg-red-600 transition-colors">
|
|
185
|
+
Delete Account
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</motion.div>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const Tabs = ({
|
|
197
|
+
tabs,
|
|
198
|
+
selectedTabIndex,
|
|
199
|
+
setSelectedTab,
|
|
200
|
+
}: {
|
|
201
|
+
tabs: Tab[];
|
|
202
|
+
selectedTabIndex: number;
|
|
203
|
+
setSelectedTab: (input: [number, number]) => void;
|
|
204
|
+
}) => {
|
|
205
|
+
const [buttonRefs, setButtonRefs] = React.useState<
|
|
206
|
+
Array<HTMLButtonElement | null>
|
|
207
|
+
>([]);
|
|
208
|
+
|
|
209
|
+
React.useEffect(() => {
|
|
210
|
+
setButtonRefs((prev) => prev.slice(0, tabs.length));
|
|
211
|
+
}, [tabs.length]);
|
|
212
|
+
|
|
213
|
+
const navRef = React.useRef<HTMLDivElement>(null);
|
|
214
|
+
const navRect = navRef.current?.getBoundingClientRect();
|
|
215
|
+
|
|
216
|
+
const selectedRect = buttonRefs[selectedTabIndex]?.getBoundingClientRect();
|
|
217
|
+
|
|
218
|
+
const [hoveredTabIndex, setHoveredTabIndex] = React.useState<number | null>(
|
|
219
|
+
null,
|
|
220
|
+
);
|
|
221
|
+
const hoveredRect =
|
|
222
|
+
buttonRefs[hoveredTabIndex ?? -1]?.getBoundingClientRect();
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<nav
|
|
226
|
+
ref={navRef}
|
|
227
|
+
className="flex flex-shrink-0 justify-center items-center relative z-0 py-2"
|
|
228
|
+
onPointerLeave={() => setHoveredTabIndex(null)}
|
|
229
|
+
>
|
|
230
|
+
{tabs.map((item, i) => {
|
|
231
|
+
const isActive = selectedTabIndex === i;
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<button
|
|
235
|
+
key={item.value}
|
|
236
|
+
className="text-sm relative rounded-md flex items-center h-8 px-4 z-20 bg-transparent cursor-pointer select-none transition-colors"
|
|
237
|
+
onPointerEnter={() => setHoveredTabIndex(i)}
|
|
238
|
+
onFocus={() => setHoveredTabIndex(i)}
|
|
239
|
+
onClick={() => setSelectedTab([i, i > selectedTabIndex ? 1 : -1])}
|
|
240
|
+
>
|
|
241
|
+
<motion.span
|
|
242
|
+
ref={(el) => {
|
|
243
|
+
buttonRefs[i] = el as HTMLButtonElement;
|
|
244
|
+
}}
|
|
245
|
+
className={cn("block", {
|
|
246
|
+
"text-gray-500": !isActive,
|
|
247
|
+
"text-gray-800 dark:text-white font-semibold": isActive,
|
|
248
|
+
})}
|
|
249
|
+
>
|
|
250
|
+
<small
|
|
251
|
+
className={item.value === "danger-zone" ? "text-red-500" : ""}
|
|
252
|
+
>
|
|
253
|
+
{item.label}
|
|
254
|
+
</small>
|
|
255
|
+
</motion.span>
|
|
256
|
+
</button>
|
|
257
|
+
);
|
|
258
|
+
})}
|
|
259
|
+
|
|
260
|
+
<AnimatePresence>
|
|
261
|
+
{hoveredRect && navRect && (
|
|
262
|
+
<motion.div
|
|
263
|
+
key="hover"
|
|
264
|
+
className={`absolute z-10 top-0 left-0 rounded-md ${
|
|
265
|
+
hoveredTabIndex ===
|
|
266
|
+
tabs.findIndex(({ value }) => value === "danger-zone")
|
|
267
|
+
? "bg-red-500/50"
|
|
268
|
+
: "bg-gray-100/50"
|
|
269
|
+
}`}
|
|
270
|
+
initial={{
|
|
271
|
+
...getHoverAnimationProps(hoveredRect, navRect),
|
|
272
|
+
opacity: 0,
|
|
273
|
+
}}
|
|
274
|
+
animate={{
|
|
275
|
+
...getHoverAnimationProps(hoveredRect, navRect),
|
|
276
|
+
opacity: 1,
|
|
277
|
+
}}
|
|
278
|
+
exit={{
|
|
279
|
+
...getHoverAnimationProps(hoveredRect, navRect),
|
|
280
|
+
opacity: 0,
|
|
281
|
+
}}
|
|
282
|
+
transition={transition}
|
|
283
|
+
/>
|
|
284
|
+
)}
|
|
285
|
+
</AnimatePresence>
|
|
286
|
+
|
|
287
|
+
<AnimatePresence>
|
|
288
|
+
{selectedRect && navRect && (
|
|
289
|
+
<motion.div
|
|
290
|
+
className={`absolute z-10 bottom-0 left-0 h-[2px] ${
|
|
291
|
+
selectedTabIndex ===
|
|
292
|
+
tabs.findIndex(({ value }) => value === "danger-zone")
|
|
293
|
+
? "bg-red-500"
|
|
294
|
+
: "bg-cyan-700"
|
|
295
|
+
}`}
|
|
296
|
+
initial={false}
|
|
297
|
+
animate={{
|
|
298
|
+
width: selectedRect.width + 18,
|
|
299
|
+
x: `calc(${selectedRect.left - navRect.left - 9}px)`,
|
|
300
|
+
opacity: 1,
|
|
301
|
+
}}
|
|
302
|
+
transition={transition}
|
|
303
|
+
/>
|
|
304
|
+
)}
|
|
305
|
+
</AnimatePresence>
|
|
306
|
+
</nav>
|
|
307
|
+
);
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export function AnimatedTabs({ tabs }: AnimatedTabsProps) {
|
|
311
|
+
const [hookProps] = React.useState(() => {
|
|
312
|
+
const initialTabId =
|
|
313
|
+
tabs.find((tab) => tab.value === "home")?.value || tabs[0].value;
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
tabs: tabs.map(({ label, value, subRoutes }) => ({
|
|
317
|
+
label,
|
|
318
|
+
value,
|
|
319
|
+
subRoutes,
|
|
320
|
+
})),
|
|
321
|
+
initialTabId,
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const framer = useTabs(hookProps);
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<div className="w-full">
|
|
329
|
+
<div className="relative flex w-full shrink-0 items-center justify-between overflow-x-auto overflow-y-hidden">
|
|
330
|
+
<Tabs {...framer.tabProps} />
|
|
331
|
+
</div>
|
|
332
|
+
<AnimatePresence mode="wait">
|
|
333
|
+
<TabContent tab={framer.selectedTab} />
|
|
334
|
+
</AnimatePresence>
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Standard Radix UI Tabs Components
|
|
340
|
+
const TabsRoot = React.forwardRef<
|
|
341
|
+
React.ElementRef<typeof TabsPrimitive.Root>,
|
|
342
|
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
|
|
343
|
+
>(({ className, ...props }, ref) => (
|
|
344
|
+
<TabsPrimitive.Root
|
|
345
|
+
ref={ref}
|
|
346
|
+
className={cn("w-full", className)}
|
|
347
|
+
{...props}
|
|
348
|
+
/>
|
|
349
|
+
));
|
|
350
|
+
TabsRoot.displayName = TabsPrimitive.Root.displayName;
|
|
7
351
|
|
|
8
352
|
const TabsList = React.forwardRef<
|
|
9
353
|
React.ElementRef<typeof TabsPrimitive.List>,
|
|
@@ -27,7 +371,7 @@ const TabsTrigger = React.forwardRef<
|
|
|
27
371
|
<TabsPrimitive.Trigger
|
|
28
372
|
ref={ref}
|
|
29
373
|
className={cn(
|
|
30
|
-
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-
|
|
374
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
31
375
|
className,
|
|
32
376
|
)}
|
|
33
377
|
{...props}
|
|
@@ -42,7 +386,7 @@ const TabsContent = React.forwardRef<
|
|
|
42
386
|
<TabsPrimitive.Content
|
|
43
387
|
ref={ref}
|
|
44
388
|
className={cn(
|
|
45
|
-
"mt-2 ring-offset-background focus-visible:outline-
|
|
389
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
46
390
|
className,
|
|
47
391
|
)}
|
|
48
392
|
{...props}
|
|
@@ -50,4 +394,5 @@ const TabsContent = React.forwardRef<
|
|
|
50
394
|
));
|
|
51
395
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
52
396
|
|
|
53
|
-
|
|
397
|
+
// Export standard Radix UI tabs components
|
|
398
|
+
export { TabsRoot as Tabs, TabsList, TabsTrigger, TabsContent };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
|
3
2
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
-
|
|
5
|
-
import {
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
import { useSegmentedControl } from "../../hooks/use-segmented-control";
|
|
6
5
|
|
|
7
6
|
const toggleVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-
|
|
7
|
+
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
|
|
9
8
|
{
|
|
10
9
|
variants: {
|
|
11
10
|
variant: {
|
|
@@ -26,18 +25,71 @@ const toggleVariants = cva(
|
|
|
26
25
|
},
|
|
27
26
|
);
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
React.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
// Segmented Control Component
|
|
29
|
+
export interface SegmentedControlProps<T extends string = string>
|
|
30
|
+
extends React.HTMLAttributes<HTMLDivElement> {
|
|
31
|
+
options: Array<{ label: React.ReactNode; value: T }>;
|
|
32
|
+
value?: T;
|
|
33
|
+
defaultValue?: T;
|
|
34
|
+
onValueChange?: (value: T) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const SegmentedControlInner = React.forwardRef<
|
|
38
|
+
HTMLDivElement,
|
|
39
|
+
SegmentedControlProps
|
|
40
|
+
>(
|
|
41
|
+
(
|
|
42
|
+
{ className, options, value, defaultValue, onValueChange, ...props },
|
|
43
|
+
ref,
|
|
44
|
+
) => {
|
|
45
|
+
const { value: selectedValue, onValueChange: handleValueChange } =
|
|
46
|
+
useSegmentedControl({
|
|
47
|
+
value,
|
|
48
|
+
defaultValue: defaultValue ?? options[0]?.value,
|
|
49
|
+
onValueChange,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div ref={ref} className={cn(className)} {...props}>
|
|
54
|
+
{options.map((option) => {
|
|
55
|
+
const isReactElement = React.isValidElement(option.label);
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<button
|
|
59
|
+
key={option.value}
|
|
60
|
+
className={cn(
|
|
61
|
+
"inline-flex items-center justify-center h-8 px-6 rounded-full transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
62
|
+
selectedValue === option.value
|
|
63
|
+
? "bg-cyan-700 text-white font-semibold"
|
|
64
|
+
: "text-gray-600 hover:bg-gray-50 font-medium",
|
|
65
|
+
)}
|
|
66
|
+
onClick={() => handleValueChange(option.value)}
|
|
67
|
+
type="button"
|
|
68
|
+
data-active={selectedValue === option.value}
|
|
69
|
+
>
|
|
70
|
+
{isReactElement ? (
|
|
71
|
+
option.label
|
|
72
|
+
) : (
|
|
73
|
+
<span className="body-small font-medium">{option.label}</span>
|
|
74
|
+
)}
|
|
75
|
+
</button>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
SegmentedControlInner.displayName = "SegmentedControl";
|
|
84
|
+
|
|
85
|
+
export const SegmentedControl = SegmentedControlInner as <
|
|
86
|
+
T extends string = string,
|
|
87
|
+
>(
|
|
88
|
+
props: SegmentedControlProps<T> & { ref?: React.Ref<HTMLDivElement> },
|
|
89
|
+
) => React.ReactElement;
|
|
90
|
+
|
|
91
|
+
// Main Toggle export for backward compatibility
|
|
92
|
+
export const Toggle = SegmentedControl;
|
|
93
|
+
|
|
94
|
+
export { toggleVariants };
|
|
95
|
+
export type { VariantProps };
|
|
@@ -1,28 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import * as React from "react";
|
|
2
4
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
5
|
|
|
4
6
|
import { cn } from "@/lib/utils";
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
7
20
|
|
|
8
|
-
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return (
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
27
|
+
</TooltipProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
9
30
|
|
|
10
|
-
|
|
31
|
+
function TooltipTrigger({
|
|
32
|
+
...props
|
|
33
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
34
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
35
|
+
}
|
|
11
36
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
function TooltipContent({
|
|
38
|
+
className,
|
|
39
|
+
sideOffset = 0,
|
|
40
|
+
side = "bottom",
|
|
41
|
+
children,
|
|
42
|
+
alignOffset = 0,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
45
|
+
return (
|
|
46
|
+
<TooltipPrimitive.Portal>
|
|
47
|
+
<TooltipPrimitive.Content
|
|
48
|
+
data-slot="tooltip-content"
|
|
49
|
+
sideOffset={sideOffset}
|
|
50
|
+
alignOffset={alignOffset}
|
|
51
|
+
side={side}
|
|
52
|
+
className={cn(
|
|
53
|
+
"group relative bg-gray-700 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-lg px-6 pt-8 pb-6 text-balance shadow-sm",
|
|
54
|
+
className,
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{/* Marker/Arrow */}
|
|
59
|
+
<div className="absolute left-1/2 -translate-x-1/2 flex justify-center group-data-[side=top]:bottom-0 group-data-[side=top]:rotate-180 group-data-[side=bottom]:top-0 group-data-[side=left]:right-0 group-data-[side=right]:left-0">
|
|
60
|
+
<svg
|
|
61
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
62
|
+
width="10"
|
|
63
|
+
height="5"
|
|
64
|
+
viewBox="0 0 10 5"
|
|
65
|
+
fill="none"
|
|
66
|
+
>
|
|
67
|
+
<path
|
|
68
|
+
d="M10 -1.74846e-06L-7.36867e-07 0C-3.29906e-07 2.76142 2.23858 5 5 5C7.76142 5 10 2.76142 10 -1.74846e-06Z"
|
|
69
|
+
fill="#39C9EA"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
</div>
|
|
73
|
+
{children}
|
|
74
|
+
</TooltipPrimitive.Content>
|
|
75
|
+
</TooltipPrimitive.Portal>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
27
78
|
|
|
28
79
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|