@axzydev/axzy_ui_system 1.2.1 → 1.2.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.
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- package/src/utils/table.utils.ts +10 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { FaChevronDown } from "react-icons/fa";
|
|
3
|
+
import { ITNavigationItem, ITSidebarProps } from "./sidebar.props";
|
|
4
|
+
import ITText from "@/components/text/text";
|
|
5
|
+
|
|
6
|
+
export default function ITSidebar({
|
|
7
|
+
navigationItems = [],
|
|
8
|
+
isCollapsed = false,
|
|
9
|
+
onToggleCollapse,
|
|
10
|
+
className = "",
|
|
11
|
+
visibleOnMobile = false,
|
|
12
|
+
onItemClick,
|
|
13
|
+
onSubItemClick,
|
|
14
|
+
subitemConnector = 'dot',
|
|
15
|
+
}: ITSidebarProps) {
|
|
16
|
+
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
|
17
|
+
const [isHovering, setIsHovering] = useState(false);
|
|
18
|
+
const sidebarRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
20
|
+
const leaveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const handleMouseEnter = () => {
|
|
24
|
+
if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
|
|
25
|
+
if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
|
|
26
|
+
setIsHovering(true);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handleMouseLeave = () => {
|
|
30
|
+
leaveTimeoutRef.current = setTimeout(() => {
|
|
31
|
+
setIsHovering(false);
|
|
32
|
+
}, 300);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const sidebar = sidebarRef.current;
|
|
36
|
+
if (sidebar) {
|
|
37
|
+
sidebar.addEventListener("mouseenter", handleMouseEnter);
|
|
38
|
+
sidebar.addEventListener("mouseleave", handleMouseLeave);
|
|
39
|
+
}
|
|
40
|
+
return () => {
|
|
41
|
+
if (sidebar) {
|
|
42
|
+
sidebar.removeEventListener("mouseenter", handleMouseEnter);
|
|
43
|
+
sidebar.removeEventListener("mouseleave", handleMouseLeave);
|
|
44
|
+
}
|
|
45
|
+
if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
|
|
46
|
+
if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
|
|
47
|
+
};
|
|
48
|
+
}, [isCollapsed]);
|
|
49
|
+
|
|
50
|
+
// Auto-expand parent items when a subitem is active
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const activeParents = new Set<string>();
|
|
53
|
+
navigationItems.forEach(item => {
|
|
54
|
+
if (item.subitems && item.subitems.some(sub => sub.isActive)) {
|
|
55
|
+
activeParents.add(item.id);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (activeParents.size > 0) {
|
|
60
|
+
setExpandedItems(prev => {
|
|
61
|
+
const next = new Set(prev);
|
|
62
|
+
let changed = false;
|
|
63
|
+
activeParents.forEach(id => {
|
|
64
|
+
if (!next.has(id)) {
|
|
65
|
+
next.add(id);
|
|
66
|
+
changed = true;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return changed ? next : prev;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}, [navigationItems]);
|
|
73
|
+
|
|
74
|
+
const toggleExpanded = (itemId: string) => {
|
|
75
|
+
const newExpanded = new Set(expandedItems);
|
|
76
|
+
if (newExpanded.has(itemId)) newExpanded.delete(itemId);
|
|
77
|
+
else newExpanded.add(itemId);
|
|
78
|
+
setExpandedItems(newExpanded);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleItemClick = (item: ITNavigationItem) => {
|
|
82
|
+
if (item.subitems && item.subitems.length > 0) {
|
|
83
|
+
toggleExpanded(item.id);
|
|
84
|
+
} else {
|
|
85
|
+
if (item.action) item.action();
|
|
86
|
+
if (onItemClick) onItemClick(item);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const isSidebarCollapsed = visibleOnMobile ? false : (!isHovering && isCollapsed);
|
|
91
|
+
const sidebarWidth = isSidebarCollapsed ? "w-[88px]" : "w-[280px]";
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<aside
|
|
95
|
+
ref={sidebarRef}
|
|
96
|
+
className={`
|
|
97
|
+
relative flex flex-col
|
|
98
|
+
transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)]
|
|
99
|
+
${sidebarWidth}
|
|
100
|
+
${className}
|
|
101
|
+
${!visibleOnMobile ? "hidden lg:flex" : "flex"}
|
|
102
|
+
shadow-[4px_0_24px_rgba(0,0,0,0.02)]
|
|
103
|
+
`}
|
|
104
|
+
style={{
|
|
105
|
+
zIndex: 50,
|
|
106
|
+
backgroundColor: "var(--sidebar-bg, rgba(255, 255, 255, 0.90))",
|
|
107
|
+
borderRight: "1px solid var(--sidebar-border, #e2e8f0)",
|
|
108
|
+
WebkitBackdropFilter: 'blur(12px)',
|
|
109
|
+
backdropFilter: 'blur(12px)',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
{/* Navigation Items */}
|
|
113
|
+
<nav className="flex-1 py-6 overflow-y-auto overflow-x-hidden custom-scrollbar px-4">
|
|
114
|
+
<ul className="space-y-2">
|
|
115
|
+
{navigationItems.map((item) => (
|
|
116
|
+
<li key={item.id} className="relative group/navitem">
|
|
117
|
+
<div
|
|
118
|
+
className={`flex items-center cursor-pointer
|
|
119
|
+
transition-all duration-300 ease-[cubic-bezier(0.2,0,0,1)]
|
|
120
|
+
rounded-xl relative overflow-visible
|
|
121
|
+
${isSidebarCollapsed ? "justify-center p-2.5 mb-2" : "justify-between px-3.5 py-3 mb-1"}
|
|
122
|
+
`}
|
|
123
|
+
style={{
|
|
124
|
+
backgroundColor: item.isActive ? "var(--sidebar-active-bg, #f8fafc)" : 'transparent',
|
|
125
|
+
boxShadow: item.isActive ? '0 1px 2px 0 rgba(0, 0, 0, 0.05)' : 'none',
|
|
126
|
+
border: item.isActive ? "1px solid var(--sidebar-border, #e2e8f0)" : '1px solid transparent'
|
|
127
|
+
}}
|
|
128
|
+
onMouseEnter={(e) => {
|
|
129
|
+
if (!item.isActive) e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, #f1f5f9)";
|
|
130
|
+
}}
|
|
131
|
+
onMouseLeave={(e) => {
|
|
132
|
+
if (!item.isActive) e.currentTarget.style.backgroundColor = 'transparent';
|
|
133
|
+
}}
|
|
134
|
+
onClick={() => handleItemClick(item)}
|
|
135
|
+
>
|
|
136
|
+
{item.isActive && !isSidebarCollapsed && (
|
|
137
|
+
<div
|
|
138
|
+
className="absolute left-0 top-1/4 bottom-1/4 w-[3px] rounded-r-full transition-all"
|
|
139
|
+
style={{ backgroundColor: "var(--sidebar-active-icon, #10b981)", boxShadow: "0 0 10px var(--sidebar-active-icon, #10b981)" }}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
<div className={`flex items-center ${!isSidebarCollapsed ? "gap-3.5" : "justify-center"} relative z-10 w-full`}>
|
|
144
|
+
{item.icon && (
|
|
145
|
+
<div
|
|
146
|
+
className={`transition-all duration-300 flex-shrink-0 flex items-center justify-center`}
|
|
147
|
+
style={{
|
|
148
|
+
color: item.isActive ? "var(--sidebar-active-icon, #10b981)" : "var(--sidebar-icon-color, #9ca3af)",
|
|
149
|
+
opacity: item.isActive ? 1 : 0.8,
|
|
150
|
+
fontSize: item.isActive ? '1.35rem' : '1.25rem',
|
|
151
|
+
filter: item.isActive ? 'drop-shadow(0 0 8px rgba(255,255,255,0.2))' : 'none'
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
{item.icon}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{!isSidebarCollapsed && (
|
|
159
|
+
<ITText as="span"
|
|
160
|
+
className={`transition-all duration-300 truncate tracking-wide`}
|
|
161
|
+
style={{
|
|
162
|
+
color: item.isActive ? "var(--sidebar-active-color, #ffffff)" : "var(--sidebar-label-color, #d1d5db)",
|
|
163
|
+
fontSize: '0.9rem',
|
|
164
|
+
fontWeight: item.isActive ? '600' : '500'
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{item.label}
|
|
168
|
+
</ITText>
|
|
169
|
+
)}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
|
|
173
|
+
<div className={`flex-shrink-0 transition-transform duration-300 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "rotate-180" : ""}`}
|
|
174
|
+
style={{ color: item.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-icon-color, #64748b)", opacity: 0.7 }}>
|
|
175
|
+
<FaChevronDown className="w-3 h-3" />
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
|
|
179
|
+
{item.badge && (
|
|
180
|
+
<span
|
|
181
|
+
className={`
|
|
182
|
+
absolute flex items-center justify-center font-bold shadow-md
|
|
183
|
+
${isSidebarCollapsed
|
|
184
|
+
? "top-1 right-1 w-2.5 h-2.5 rounded-full ring-2 ring-white"
|
|
185
|
+
: "right-3 top-1/2 transform -translate-y-1/2 px-2 py-0.5 text-[10px] rounded-full backdrop-blur-sm"}
|
|
186
|
+
`}
|
|
187
|
+
style={{
|
|
188
|
+
backgroundColor: "var(--sidebar-badge-bg, #10b981)",
|
|
189
|
+
color: "var(--sidebar-badge-color, #ffffff)",
|
|
190
|
+
boxShadow: isSidebarCollapsed ? "0 0 0 2px var(--sidebar-bg, #111827)" : 'none'
|
|
191
|
+
}}
|
|
192
|
+
>
|
|
193
|
+
{isSidebarCollapsed ? "" : item.badge}
|
|
194
|
+
</span>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* Glassmorphism Collapsed Tooltip / Submenu */}
|
|
199
|
+
{isSidebarCollapsed && (
|
|
200
|
+
<div
|
|
201
|
+
className="absolute left-full top-0 ml-4 rounded-2xl opacity-0 invisible group-hover/navitem:opacity-100 group-hover/navitem:visible transition-all duration-300 pointer-events-none z-50 min-w-[220px] overflow-hidden -translate-x-2 group-hover/navitem:translate-x-0 shadow-[0_10px_40px_-10px_rgba(0,0,0,0.15)]"
|
|
202
|
+
style={{
|
|
203
|
+
backgroundColor: "var(--sidebar-bg, #ffffff)",
|
|
204
|
+
border: "1px solid var(--sidebar-border, #e2e8f0)",
|
|
205
|
+
WebkitBackdropFilter: 'blur(16px)',
|
|
206
|
+
backdropFilter: 'blur(16px)',
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<div className="px-5 py-4 flex items-center gap-3 font-semibold border-b" style={{ borderColor: "var(--sidebar-border, #e2e8f0)", color: "var(--sidebar-active-color, #0f172a)" }}>
|
|
210
|
+
{item.icon && <span style={{ color: "var(--sidebar-active-icon, #10b981)" }} className="text-xl drop-shadow-sm">{item.icon}</span>}
|
|
211
|
+
<ITText as="span" className="tracking-wide text-[15px]">{item.label}</ITText>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{item.subitems && item.subitems.length > 0 ? (
|
|
215
|
+
<div className="py-2">
|
|
216
|
+
{item.subitems.map((subitem) => (
|
|
217
|
+
<div
|
|
218
|
+
key={subitem.id}
|
|
219
|
+
className={`px-5 py-2.5 text-sm flex items-center gap-3 transition-colors relative`}
|
|
220
|
+
>
|
|
221
|
+
{subitem.isActive && subitemConnector === '|' && (
|
|
222
|
+
<div
|
|
223
|
+
className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full"
|
|
224
|
+
style={{
|
|
225
|
+
backgroundColor: "var(--sidebar-active-icon, #10b981)",
|
|
226
|
+
boxShadow: "0 0 6px color-mix(in srgb, var(--sidebar-active-icon, #10b981) 25%, transparent)",
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
)}
|
|
230
|
+
<span className={`w-1.5 h-1.5 rounded-full transition-all ${subitem.isActive ? "scale-125" : ""}`} style={{ backgroundColor: subitem.isActive ? "var(--sidebar-active-icon, #10b981)" : "var(--sidebar-icon-color, #94a3b8)" }} />
|
|
231
|
+
<ITText as="span" style={{ color: subitem.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-label-color, #475569)", fontWeight: subitem.isActive ? 600 : 500 }}>{subitem.label}</ITText>
|
|
232
|
+
</div>
|
|
233
|
+
))}
|
|
234
|
+
</div>
|
|
235
|
+
) : (
|
|
236
|
+
<ITText as="div" className="px-5 py-3 text-sm italic" style={{ color: "var(--sidebar-label-color, #71717a)" }}>No hay submenú</ITText>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
)}
|
|
240
|
+
|
|
241
|
+
{/* Submenu - smooth height/opacity when not collapsed */}
|
|
242
|
+
{!isSidebarCollapsed && item.subitems && item.subitems.length > 0 && (
|
|
243
|
+
<div className={`overflow-hidden transition-all duration-400 ease-[cubic-bezier(0.2,0,0,1)] ${expandedItems.has(item.id) ? "max-h-[500px] opacity-100 mt-1" : "max-h-0 opacity-0"}`}>
|
|
244
|
+
<ul
|
|
245
|
+
className="ml-5 flex flex-col gap-0.5 py-1"
|
|
246
|
+
style={{
|
|
247
|
+
borderLeft: subitemConnector === '|'
|
|
248
|
+
? "1px solid var(--sidebar-border, #e2e8f0)"
|
|
249
|
+
: 'none'
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
{item.subitems.map((subitem) => (
|
|
253
|
+
<li key={subitem.id} className="relative">
|
|
254
|
+
<button
|
|
255
|
+
onClick={() => {
|
|
256
|
+
if (subitem.action) subitem.action();
|
|
257
|
+
if (onSubItemClick) onSubItemClick(subitem);
|
|
258
|
+
}}
|
|
259
|
+
className={`flex items-center gap-2.5 w-full text-left px-3 py-2 rounded-xl transition-all duration-300`}
|
|
260
|
+
style={{
|
|
261
|
+
color: subitem.isActive ? "var(--sidebar-active-color, #0f172a)" : "var(--sidebar-label-color, #475569)",
|
|
262
|
+
backgroundColor: subitem.isActive ? "var(--sidebar-active-bg, #f8fafc)" : 'transparent',
|
|
263
|
+
fontSize: '0.85rem',
|
|
264
|
+
fontWeight: subitem.isActive ? 600 : 500,
|
|
265
|
+
letterSpacing: '0.01em',
|
|
266
|
+
marginLeft: subitemConnector === '|' ? '-1px' : '0',
|
|
267
|
+
}}
|
|
268
|
+
onMouseEnter={(e) => {
|
|
269
|
+
if (!subitem.isActive) {
|
|
270
|
+
e.currentTarget.style.backgroundColor = "var(--sidebar-hover-bg, #f1f5f9)";
|
|
271
|
+
e.currentTarget.style.transform = 'translateX(3px)';
|
|
272
|
+
}
|
|
273
|
+
}}
|
|
274
|
+
onMouseLeave={(e) => {
|
|
275
|
+
if (!subitem.isActive) {
|
|
276
|
+
e.currentTarget.style.backgroundColor = 'transparent';
|
|
277
|
+
e.currentTarget.style.transform = 'translateX(0)';
|
|
278
|
+
}
|
|
279
|
+
}}
|
|
280
|
+
>
|
|
281
|
+
{subitem.isActive && subitemConnector === '|' && (
|
|
282
|
+
<div
|
|
283
|
+
className="absolute left-0 top-1/3 bottom-1/3 w-[2.5px] rounded-r-full transition-all"
|
|
284
|
+
style={{
|
|
285
|
+
backgroundColor: "var(--sidebar-active-icon, #10b981)",
|
|
286
|
+
boxShadow: "0 0 6px color-mix(in srgb, var(--sidebar-active-icon, #10b981) 25%, transparent)",
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
)}
|
|
290
|
+
{subitemConnector === 'dot' && (
|
|
291
|
+
<span
|
|
292
|
+
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 transition-all duration-300 ${subitem.isActive ? 'scale-125' : ''}`}
|
|
293
|
+
style={{
|
|
294
|
+
backgroundColor: subitem.isActive
|
|
295
|
+
? "var(--sidebar-active-icon, #10b981)"
|
|
296
|
+
: "var(--sidebar-icon-color, #94a3b8)"
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
)}
|
|
300
|
+
<ITText as="span" className="truncate">{subitem.label}</ITText>
|
|
301
|
+
</button>
|
|
302
|
+
</li>
|
|
303
|
+
))}
|
|
304
|
+
</ul>
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
</li>
|
|
308
|
+
))}
|
|
309
|
+
</ul>
|
|
310
|
+
</nav>
|
|
311
|
+
</aside>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CSSProperties } from "react";
|
|
2
|
+
|
|
3
|
+
export type SkeletonVariant = "text" | "circular" | "rectangular";
|
|
4
|
+
|
|
5
|
+
export interface ITSkeletonProps {
|
|
6
|
+
variant?: SkeletonVariant;
|
|
7
|
+
width?: string | number;
|
|
8
|
+
height?: string | number;
|
|
9
|
+
count?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: CSSProperties;
|
|
12
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import ITSkeleton from "./skeleton";
|
|
3
|
+
import ITStack from "../stack/stack";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ITSkeleton> = {
|
|
6
|
+
title: "Components/Feedback/ITSkeleton",
|
|
7
|
+
component: ITSkeleton,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof ITSkeleton>;
|
|
13
|
+
|
|
14
|
+
export const Text: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<ITStack spacing={2}>
|
|
17
|
+
<ITSkeleton variant="text" width="60%" />
|
|
18
|
+
<ITSkeleton variant="text" />
|
|
19
|
+
<ITSkeleton variant="text" width="80%" />
|
|
20
|
+
</ITStack>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Circular: Story = {
|
|
25
|
+
args: { variant: "circular", width: 48, height: 48 },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Rectangular: Story = {
|
|
29
|
+
args: { variant: "rectangular", width: "100%", height: 160 },
|
|
30
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { ITSkeletonProps, SkeletonVariant } from "./skeleton.props";
|
|
3
|
+
|
|
4
|
+
const variantClasses: Record<SkeletonVariant, string> = {
|
|
5
|
+
text: "rounded-md h-4 w-full",
|
|
6
|
+
circular: "rounded-full",
|
|
7
|
+
rectangular: "rounded-lg",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function ITSkeleton({
|
|
11
|
+
variant = "text",
|
|
12
|
+
width,
|
|
13
|
+
height,
|
|
14
|
+
count = 1,
|
|
15
|
+
className,
|
|
16
|
+
style,
|
|
17
|
+
}: ITSkeletonProps) {
|
|
18
|
+
const baseStyle: React.CSSProperties = {
|
|
19
|
+
...(width ? { width } : variant === "text" ? {} : width ? { width } : {}),
|
|
20
|
+
...(height ? { height } : {}),
|
|
21
|
+
...style,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const items = Array.from({ length: count }, (_, i) => i);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
{items.map((i) => (
|
|
29
|
+
<div
|
|
30
|
+
key={i}
|
|
31
|
+
className={clsx(
|
|
32
|
+
"animate-pulse bg-slate-200 dark:bg-slate-700",
|
|
33
|
+
variantClasses[variant],
|
|
34
|
+
variant === "text" && count > 1 && i < count - 1 && "mb-2",
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
style={{
|
|
38
|
+
...baseStyle,
|
|
39
|
+
width: variant === "text" && width === undefined ? `${Math.random() * 30 + 60}%` : width,
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
))}
|
|
43
|
+
</>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface ITSlideToggleProps {
|
|
2
|
+
/**
|
|
3
|
+
* Callback executed when the switch is toggled. Receives the new state.
|
|
4
|
+
*/
|
|
5
|
+
onToggle?: (value: boolean) => void;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Controlled state. Use this to completely control the component externally.
|
|
9
|
+
*/
|
|
10
|
+
isOn?: boolean;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Initial state for uncontrolled usage.
|
|
14
|
+
* Default: false
|
|
15
|
+
*/
|
|
16
|
+
initialState?: boolean;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Semantic theme color when activated (e.g., 'primary', 'success').
|
|
20
|
+
* Default: 'success'
|
|
21
|
+
*/
|
|
22
|
+
activeColor?: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Semantic theme color or hex when deactivated.
|
|
26
|
+
* Default: '#9ca3af' (gray-400)
|
|
27
|
+
*/
|
|
28
|
+
inactiveColor?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether the switch is disabled.
|
|
32
|
+
*/
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Size of the switch: sm, md, lg.
|
|
37
|
+
* Default: md
|
|
38
|
+
*/
|
|
39
|
+
size?: "sm" | "md" | "lg";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Additional container classes.
|
|
43
|
+
*/
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import ITSlideToggle from "./slide";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof ITSlideToggle> = {
|
|
6
|
+
title: "Components/Form Elements/ITSlideToggle",
|
|
7
|
+
component: ITSlideToggle,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "centered",
|
|
10
|
+
},
|
|
11
|
+
tags: ["autodocs"],
|
|
12
|
+
argTypes: {
|
|
13
|
+
activeColor: {
|
|
14
|
+
control: "select",
|
|
15
|
+
options: ["primary", "secondary", "success", "danger", "warning", "info", "purple"],
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
control: "select",
|
|
19
|
+
options: ["sm", "md", "lg"],
|
|
20
|
+
},
|
|
21
|
+
disabled: {
|
|
22
|
+
control: "boolean",
|
|
23
|
+
},
|
|
24
|
+
isOn: {
|
|
25
|
+
control: "boolean",
|
|
26
|
+
},
|
|
27
|
+
initialState: {
|
|
28
|
+
control: "boolean",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof ITSlideToggle>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {
|
|
37
|
+
args: {
|
|
38
|
+
activeColor: "success",
|
|
39
|
+
size: "md",
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const CheckedUncontrolled: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
initialState: true,
|
|
46
|
+
activeColor: "primary",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const ControlledSlideWrapper = (args: any) => {
|
|
51
|
+
const [isOn, setIsOn] = useState(false);
|
|
52
|
+
return (
|
|
53
|
+
<div className="flex flex-col gap-4 items-center">
|
|
54
|
+
<ITSlideToggle {...args} isOn={isOn} onToggle={setIsOn} />
|
|
55
|
+
<span className="text-sm font-medium text-gray-500">
|
|
56
|
+
External State: {isOn ? "ON" : "OFF"}
|
|
57
|
+
</span>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Controlled: Story = {
|
|
63
|
+
render: (args) => <ControlledSlideWrapper {...args} />,
|
|
64
|
+
args: {
|
|
65
|
+
activeColor: "info",
|
|
66
|
+
size: "lg",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const Sizes: Story = {
|
|
71
|
+
render: (args) => (
|
|
72
|
+
<div className="flex flex-col gap-6 items-center">
|
|
73
|
+
<div className="flex items-center gap-4">
|
|
74
|
+
<span className="w-16 text-sm text-gray-500 font-bold">Small</span>
|
|
75
|
+
<ITSlideToggle {...args} size="sm" initialState={true} />
|
|
76
|
+
</div>
|
|
77
|
+
<div className="flex items-center gap-4">
|
|
78
|
+
<span className="w-16 text-sm text-gray-500 font-bold">Medium</span>
|
|
79
|
+
<ITSlideToggle {...args} size="md" initialState={true} />
|
|
80
|
+
</div>
|
|
81
|
+
<div className="flex items-center gap-4">
|
|
82
|
+
<span className="w-16 text-sm text-gray-500 font-bold">Large</span>
|
|
83
|
+
<ITSlideToggle {...args} size="lg" initialState={true} />
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
),
|
|
87
|
+
args: {
|
|
88
|
+
activeColor: "primary"
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const Disabled: Story = {
|
|
93
|
+
render: (args) => (
|
|
94
|
+
<div className="flex gap-8">
|
|
95
|
+
<div className="flex flex-col gap-2 items-center">
|
|
96
|
+
<span className="text-sm text-gray-500">Off</span>
|
|
97
|
+
<ITSlideToggle {...args} initialState={false} />
|
|
98
|
+
</div>
|
|
99
|
+
<div className="flex flex-col gap-2 items-center">
|
|
100
|
+
<span className="text-sm text-gray-500">On</span>
|
|
101
|
+
<ITSlideToggle {...args} initialState={true} />
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
),
|
|
105
|
+
args: {
|
|
106
|
+
disabled: true,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const Colors: Story = {
|
|
111
|
+
render: (args) => (
|
|
112
|
+
<div className="flex flex-col gap-4">
|
|
113
|
+
{(['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'purple'] as const).map((col) => (
|
|
114
|
+
<div key={col} className="flex items-center gap-4">
|
|
115
|
+
<span className="w-24 text-sm text-gray-500 font-bold capitalize">{col}</span>
|
|
116
|
+
<ITSlideToggle {...args} activeColor={col} initialState={true} />
|
|
117
|
+
</div>
|
|
118
|
+
))}
|
|
119
|
+
</div>
|
|
120
|
+
),
|
|
121
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { ITSlideToggleProps } from "./slide.props";
|
|
4
|
+
import { theme } from "@/theme/theme";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Slide toggle switch component.
|
|
8
|
+
* Supports fully controlled (`isOn`) or uncontrolled (`initialState`) modes.
|
|
9
|
+
*/
|
|
10
|
+
export default function ITSlideToggle({
|
|
11
|
+
onToggle,
|
|
12
|
+
isOn: controlledIsOn,
|
|
13
|
+
initialState = false,
|
|
14
|
+
activeColor = "success",
|
|
15
|
+
inactiveColor = "#9ca3af", // default gray-400
|
|
16
|
+
disabled = false,
|
|
17
|
+
size = "md",
|
|
18
|
+
className = "",
|
|
19
|
+
}: ITSlideToggleProps) {
|
|
20
|
+
// Handle internal state if not controlled
|
|
21
|
+
const isControlled = controlledIsOn !== undefined;
|
|
22
|
+
const [internalIsOn, setInternalIsOn] = useState(initialState);
|
|
23
|
+
|
|
24
|
+
// Sync internal state with controlled state if it changes externally
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (isControlled) {
|
|
27
|
+
setInternalIsOn(controlledIsOn);
|
|
28
|
+
}
|
|
29
|
+
}, [controlledIsOn, isControlled]);
|
|
30
|
+
|
|
31
|
+
const isOn = isControlled ? controlledIsOn : internalIsOn;
|
|
32
|
+
|
|
33
|
+
const toggleSwitch = () => {
|
|
34
|
+
if (disabled) return;
|
|
35
|
+
|
|
36
|
+
const newState = !isOn;
|
|
37
|
+
if (!isControlled) {
|
|
38
|
+
setInternalIsOn(newState);
|
|
39
|
+
}
|
|
40
|
+
if (onToggle) {
|
|
41
|
+
onToggle(newState);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Resolve active theme color
|
|
46
|
+
const isThemeColor = activeColor in theme.colors;
|
|
47
|
+
const resolvedActiveColor = isThemeColor
|
|
48
|
+
? theme.colors[activeColor as keyof typeof theme.colors][500]
|
|
49
|
+
: activeColor;
|
|
50
|
+
|
|
51
|
+
// Resolve inactive color (could also be theme color, but defaulting to hex)
|
|
52
|
+
const isInactiveThemeColor = inactiveColor in theme.colors;
|
|
53
|
+
const resolvedInactiveColor = isInactiveThemeColor
|
|
54
|
+
? theme.colors[inactiveColor as keyof typeof theme.colors][400]
|
|
55
|
+
: inactiveColor;
|
|
56
|
+
|
|
57
|
+
const backgroundColor = isOn ? resolvedActiveColor : resolvedInactiveColor;
|
|
58
|
+
|
|
59
|
+
// Sizing definitions
|
|
60
|
+
const sizeClasses = {
|
|
61
|
+
sm: {
|
|
62
|
+
container: "w-10 h-5",
|
|
63
|
+
knob: "w-3.5 h-3.5",
|
|
64
|
+
translate: "translate-x-5",
|
|
65
|
+
},
|
|
66
|
+
md: {
|
|
67
|
+
container: "w-14 h-7",
|
|
68
|
+
knob: "w-5 h-5",
|
|
69
|
+
translate: "translate-x-7",
|
|
70
|
+
},
|
|
71
|
+
lg: {
|
|
72
|
+
container: "w-16 h-8",
|
|
73
|
+
knob: "w-6 h-6",
|
|
74
|
+
translate: "translate-x-8",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const { container, knob, translate } = sizeClasses[size];
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
onClick={toggleSwitch}
|
|
83
|
+
className={clsx(
|
|
84
|
+
"flex items-center rounded-full p-1 transition-colors duration-300",
|
|
85
|
+
container,
|
|
86
|
+
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
style={{ backgroundColor }}
|
|
90
|
+
role="switch"
|
|
91
|
+
aria-checked={isOn}
|
|
92
|
+
tabIndex={disabled ? -1 : 0}
|
|
93
|
+
onKeyDown={(e) => {
|
|
94
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
toggleSwitch();
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
className={clsx(
|
|
102
|
+
"bg-white rounded-full shadow-md transform transition-transform duration-300 pointer-events-none",
|
|
103
|
+
knob,
|
|
104
|
+
isOn ? translate : "translate-x-0"
|
|
105
|
+
)}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
}
|