@uniweb/kit 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/components/Asset/Asset.jsx +31 -81
- package/src/components/Media/Media.jsx +27 -125
- package/src/components/SocialIcon/index.jsx +146 -0
- package/src/hooks/index.js +6 -0
- package/src/hooks/useAccordion.js +143 -0
- package/src/hooks/useActiveRoute.js +97 -0
- package/src/hooks/useGridLayout.js +71 -0
- package/src/hooks/useMobileMenu.js +58 -0
- package/src/hooks/useScrolled.js +48 -0
- package/src/hooks/useTheme.js +205 -0
- package/src/index.js +29 -10
- package/src/styled/Asset/Asset.jsx +161 -0
- package/src/styled/Asset/index.js +1 -0
- package/src/{components → styled}/Disclaimer/Disclaimer.jsx +1 -1
- package/src/styled/Media/Media.jsx +322 -0
- package/src/styled/Media/index.js +1 -0
- package/src/{components → styled}/Section/Render.jsx +4 -4
- package/src/{components → styled}/Section/index.js +6 -0
- package/src/{components → styled}/Section/renderers/Alert.jsx +1 -1
- package/src/{components → styled}/Section/renderers/Details.jsx +1 -1
- package/src/{components → styled}/Section/renderers/Table.jsx +1 -1
- package/src/{components → styled}/Section/renderers/index.js +1 -1
- package/src/styled/SidebarLayout/SidebarLayout.jsx +310 -0
- package/src/styled/SidebarLayout/index.js +1 -0
- package/src/styled/index.js +40 -0
- /package/src/{components → styled}/Disclaimer/index.js +0 -0
- /package/src/{components → styled}/Section/Section.jsx +0 -0
- /package/src/{components → styled}/Section/renderers/Code.jsx +0 -0
- /package/src/{components → styled}/Section/renderers/Divider.jsx +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { cn } from '../../utils/index.js'
|
|
3
|
+
import { useMobileMenu } from '../../hooks/useMobileMenu.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SidebarLayout Component
|
|
7
|
+
*
|
|
8
|
+
* A flexible layout with optional left and/or right sidebars.
|
|
9
|
+
* On desktop, sidebars appear inline at their configured breakpoints.
|
|
10
|
+
* On mobile, the left panel is accessible via a slide-out drawer with FAB toggle.
|
|
11
|
+
* The right panel is hidden on mobile (common pattern: nav essential, TOC optional).
|
|
12
|
+
*
|
|
13
|
+
* The layout is "sandwiched" - header and footer span the full width,
|
|
14
|
+
* with the sidebars and content area between them.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* // In foundation's src/exports.js - use as-is
|
|
18
|
+
* import { SidebarLayout } from '@uniweb/kit'
|
|
19
|
+
*
|
|
20
|
+
* export default {
|
|
21
|
+
* Layout: SidebarLayout,
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* // With custom configuration
|
|
26
|
+
* import { SidebarLayout } from '@uniweb/kit'
|
|
27
|
+
*
|
|
28
|
+
* function CustomLayout(props) {
|
|
29
|
+
* return (
|
|
30
|
+
* <SidebarLayout
|
|
31
|
+
* {...props}
|
|
32
|
+
* leftBreakpoint="lg"
|
|
33
|
+
* rightBreakpoint="xl"
|
|
34
|
+
* leftWidth="w-72"
|
|
35
|
+
* />
|
|
36
|
+
* )
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* export default { Layout: CustomLayout }
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Hamburger menu icon
|
|
44
|
+
*/
|
|
45
|
+
function MenuIcon({ className }) {
|
|
46
|
+
return (
|
|
47
|
+
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
48
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
49
|
+
</svg>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Close (X) icon
|
|
55
|
+
*/
|
|
56
|
+
function CloseIcon({ className }) {
|
|
57
|
+
return (
|
|
58
|
+
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
59
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
60
|
+
</svg>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Mobile drawer component (always slides from left)
|
|
66
|
+
*/
|
|
67
|
+
function MobileDrawer({ isOpen, onClose, width, stickyHeader, children }) {
|
|
68
|
+
// Prevent body scroll when drawer is open
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (isOpen) {
|
|
71
|
+
document.body.style.overflow = 'hidden'
|
|
72
|
+
return () => {
|
|
73
|
+
document.body.style.overflow = ''
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}, [isOpen])
|
|
77
|
+
|
|
78
|
+
// Position below header if sticky, otherwise from top
|
|
79
|
+
const topOffset = stickyHeader ? 'top-16' : 'top-0'
|
|
80
|
+
const height = stickyHeader ? 'h-[calc(100vh-4rem)]' : 'h-screen'
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
{/* Backdrop */}
|
|
85
|
+
<div
|
|
86
|
+
className={cn(
|
|
87
|
+
'fixed inset-0 bg-black/50 z-40 transition-opacity duration-300',
|
|
88
|
+
isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
89
|
+
)}
|
|
90
|
+
onClick={onClose}
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
/>
|
|
93
|
+
|
|
94
|
+
{/* Drawer (always from left) */}
|
|
95
|
+
<div
|
|
96
|
+
className={cn(
|
|
97
|
+
'fixed left-0 bg-white z-50 shadow-xl',
|
|
98
|
+
topOffset,
|
|
99
|
+
height,
|
|
100
|
+
width,
|
|
101
|
+
'transform transition-transform duration-300 ease-in-out',
|
|
102
|
+
isOpen ? 'translate-x-0' : '-translate-x-full'
|
|
103
|
+
)}
|
|
104
|
+
role="dialog"
|
|
105
|
+
aria-modal="true"
|
|
106
|
+
aria-label="Sidebar navigation"
|
|
107
|
+
>
|
|
108
|
+
{/* Close button */}
|
|
109
|
+
<button
|
|
110
|
+
onClick={onClose}
|
|
111
|
+
className="absolute top-4 right-4 p-1.5 rounded-md hover:bg-gray-100 transition-colors"
|
|
112
|
+
aria-label="Close sidebar"
|
|
113
|
+
>
|
|
114
|
+
<CloseIcon className="w-5 h-5 text-gray-500" />
|
|
115
|
+
</button>
|
|
116
|
+
|
|
117
|
+
{/* Drawer content */}
|
|
118
|
+
<div className="h-full overflow-y-auto overscroll-contain">
|
|
119
|
+
{children}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Floating action button for mobile menu (always bottom-left)
|
|
128
|
+
*/
|
|
129
|
+
function FloatingMenuButton({ onClick }) {
|
|
130
|
+
return (
|
|
131
|
+
<button
|
|
132
|
+
onClick={onClick}
|
|
133
|
+
className={cn(
|
|
134
|
+
'fixed bottom-4 left-4 z-30',
|
|
135
|
+
'p-3 bg-primary text-white rounded-full shadow-lg',
|
|
136
|
+
'hover:bg-primary/90 active:scale-95 transition-all',
|
|
137
|
+
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2'
|
|
138
|
+
)}
|
|
139
|
+
aria-label="Open navigation menu"
|
|
140
|
+
>
|
|
141
|
+
<MenuIcon className="w-6 h-6" />
|
|
142
|
+
</button>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get responsive classes for showing/hiding at breakpoint
|
|
148
|
+
*/
|
|
149
|
+
function getBreakpointClasses(breakpoint) {
|
|
150
|
+
const showClass = {
|
|
151
|
+
sm: 'sm:block',
|
|
152
|
+
md: 'md:block',
|
|
153
|
+
lg: 'lg:block',
|
|
154
|
+
xl: 'xl:block',
|
|
155
|
+
}[breakpoint] || 'md:block'
|
|
156
|
+
|
|
157
|
+
const hideClass = {
|
|
158
|
+
sm: 'sm:hidden',
|
|
159
|
+
md: 'md:hidden',
|
|
160
|
+
lg: 'lg:hidden',
|
|
161
|
+
xl: 'xl:hidden',
|
|
162
|
+
}[breakpoint] || 'md:hidden'
|
|
163
|
+
|
|
164
|
+
return { showClass, hideClass }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* SidebarLayout main component
|
|
169
|
+
*
|
|
170
|
+
* @param {Object} props
|
|
171
|
+
* @param {React.ReactNode} props.header - Header content (from @header sections)
|
|
172
|
+
* @param {React.ReactNode} props.body - Main body content (page sections)
|
|
173
|
+
* @param {React.ReactNode} props.footer - Footer content (from @footer sections)
|
|
174
|
+
* @param {React.ReactNode} props.left - Left panel content (from @left sections)
|
|
175
|
+
* @param {React.ReactNode} props.right - Right panel content (from @right sections)
|
|
176
|
+
* @param {React.ReactNode} props.leftPanel - Alias for left (backwards compatibility)
|
|
177
|
+
* @param {React.ReactNode} props.rightPanel - Alias for right (backwards compatibility)
|
|
178
|
+
* @param {string} [props.leftWidth='w-64'] - Tailwind width class for left sidebar
|
|
179
|
+
* @param {string} [props.rightWidth='w-64'] - Tailwind width class for right sidebar
|
|
180
|
+
* @param {string} [props.drawerWidth='w-72'] - Tailwind width class for mobile drawer
|
|
181
|
+
* @param {string} [props.leftBreakpoint='md'] - Breakpoint for showing left sidebar inline
|
|
182
|
+
* @param {string} [props.rightBreakpoint='xl'] - Breakpoint for showing right sidebar inline
|
|
183
|
+
* @param {boolean} [props.stickyHeader=true] - Whether header sticks to top
|
|
184
|
+
* @param {boolean} [props.stickySidebar=true] - Whether sidebars stick below header
|
|
185
|
+
* @param {string} [props.maxWidth='max-w-7xl'] - Max width of content area
|
|
186
|
+
* @param {string} [props.contentPadding='px-4 py-8 sm:px-6 lg:px-8'] - Padding for main content
|
|
187
|
+
* @param {string} [props.className] - Additional classes for the root element
|
|
188
|
+
*/
|
|
189
|
+
export function SidebarLayout({
|
|
190
|
+
// Pre-rendered layout areas from runtime
|
|
191
|
+
header,
|
|
192
|
+
body,
|
|
193
|
+
footer,
|
|
194
|
+
left,
|
|
195
|
+
right,
|
|
196
|
+
leftPanel,
|
|
197
|
+
rightPanel,
|
|
198
|
+
// Configuration
|
|
199
|
+
leftWidth = 'w-64',
|
|
200
|
+
rightWidth = 'w-64',
|
|
201
|
+
drawerWidth = 'w-72',
|
|
202
|
+
leftBreakpoint = 'md',
|
|
203
|
+
rightBreakpoint = 'xl',
|
|
204
|
+
stickyHeader = true,
|
|
205
|
+
stickySidebar = true,
|
|
206
|
+
maxWidth = 'max-w-7xl',
|
|
207
|
+
contentPadding = 'px-4 py-8 sm:px-6 lg:px-8',
|
|
208
|
+
className,
|
|
209
|
+
}) {
|
|
210
|
+
const { isOpen, open, close } = useMobileMenu()
|
|
211
|
+
|
|
212
|
+
// Resolve panel content (support both naming conventions)
|
|
213
|
+
const leftContent = left || leftPanel
|
|
214
|
+
const rightContent = right || rightPanel
|
|
215
|
+
|
|
216
|
+
// Get breakpoint classes for each panel
|
|
217
|
+
const leftClasses = getBreakpointClasses(leftBreakpoint)
|
|
218
|
+
const rightClasses = getBreakpointClasses(rightBreakpoint)
|
|
219
|
+
|
|
220
|
+
// Sticky positioning
|
|
221
|
+
const headerClasses = stickyHeader
|
|
222
|
+
? 'sticky top-0 z-30'
|
|
223
|
+
: ''
|
|
224
|
+
|
|
225
|
+
const sidebarClasses = stickySidebar && stickyHeader
|
|
226
|
+
? 'sticky top-16 h-[calc(100vh-4rem)]'
|
|
227
|
+
: stickySidebar
|
|
228
|
+
? 'sticky top-0 h-screen'
|
|
229
|
+
: ''
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div className={cn('min-h-screen flex flex-col bg-white', className)}>
|
|
233
|
+
{/* Header */}
|
|
234
|
+
{header && (
|
|
235
|
+
<header className={cn(
|
|
236
|
+
'w-full border-b border-gray-200 bg-white/95 backdrop-blur supports-[backdrop-filter]:bg-white/80',
|
|
237
|
+
headerClasses
|
|
238
|
+
)}>
|
|
239
|
+
{header}
|
|
240
|
+
</header>
|
|
241
|
+
)}
|
|
242
|
+
|
|
243
|
+
{/* Mobile Drawer (left panel only) */}
|
|
244
|
+
{leftContent && (
|
|
245
|
+
<div className={leftClasses.hideClass}>
|
|
246
|
+
<MobileDrawer
|
|
247
|
+
isOpen={isOpen}
|
|
248
|
+
onClose={close}
|
|
249
|
+
width={drawerWidth}
|
|
250
|
+
stickyHeader={stickyHeader}
|
|
251
|
+
>
|
|
252
|
+
{leftContent}
|
|
253
|
+
</MobileDrawer>
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
256
|
+
|
|
257
|
+
{/* Main Content Area */}
|
|
258
|
+
<div className={cn('flex-1 w-full mx-auto', maxWidth)}>
|
|
259
|
+
<div className="flex">
|
|
260
|
+
{/* Left Sidebar (desktop) */}
|
|
261
|
+
{leftContent && (
|
|
262
|
+
<aside className={cn(
|
|
263
|
+
'hidden flex-shrink-0 overflow-y-auto border-r border-gray-200',
|
|
264
|
+
leftClasses.showClass,
|
|
265
|
+
leftWidth,
|
|
266
|
+
sidebarClasses
|
|
267
|
+
)}>
|
|
268
|
+
{leftContent}
|
|
269
|
+
</aside>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* Main Content */}
|
|
273
|
+
<main className="flex-1 min-w-0">
|
|
274
|
+
<div className={contentPadding}>
|
|
275
|
+
{body}
|
|
276
|
+
</div>
|
|
277
|
+
</main>
|
|
278
|
+
|
|
279
|
+
{/* Right Sidebar (desktop only, hidden on mobile) */}
|
|
280
|
+
{rightContent && (
|
|
281
|
+
<aside className={cn(
|
|
282
|
+
'hidden flex-shrink-0 overflow-y-auto border-l border-gray-200',
|
|
283
|
+
rightClasses.showClass,
|
|
284
|
+
rightWidth,
|
|
285
|
+
sidebarClasses
|
|
286
|
+
)}>
|
|
287
|
+
{rightContent}
|
|
288
|
+
</aside>
|
|
289
|
+
)}
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
|
|
293
|
+
{/* Footer */}
|
|
294
|
+
{footer && (
|
|
295
|
+
<footer className="w-full border-t border-gray-200">
|
|
296
|
+
{footer}
|
|
297
|
+
</footer>
|
|
298
|
+
)}
|
|
299
|
+
|
|
300
|
+
{/* Mobile FAB (only if left panel exists) */}
|
|
301
|
+
{leftContent && (
|
|
302
|
+
<div className={leftClasses.hideClass}>
|
|
303
|
+
<FloatingMenuButton onClick={open} />
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export default SidebarLayout
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SidebarLayout, default } from './SidebarLayout.jsx'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uniweb/kit/styled
|
|
3
|
+
*
|
|
4
|
+
* Pre-styled components from the kit.
|
|
5
|
+
*
|
|
6
|
+
* These components come with built-in styling (using Tailwind CSS).
|
|
7
|
+
* For unstyled primitives, use the main '@uniweb/kit' export.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { SidebarLayout, Section, Media } from '@uniweb/kit/styled'
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Layout
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export { SidebarLayout } from './SidebarLayout/index.js'
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Content Rendering
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
// Section - Rich content section renderer
|
|
24
|
+
export { Section, Render } from './Section/index.js'
|
|
25
|
+
|
|
26
|
+
// Renderers - Individual content type renderers
|
|
27
|
+
export { Code, Alert, Warning, Table, Details, Divider } from './Section/renderers/index.js'
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// UI Components
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
// Disclaimer - Modal dialog for legal disclaimers
|
|
34
|
+
export { Disclaimer } from './Disclaimer/index.js'
|
|
35
|
+
|
|
36
|
+
// Media - Video player with styled play button facade
|
|
37
|
+
export { Media } from './Media/index.js'
|
|
38
|
+
|
|
39
|
+
// Asset - File download card with preview and hover overlay
|
|
40
|
+
export { Asset } from './Asset/index.js'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|