@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.
Files changed (30) hide show
  1. package/package.json +3 -2
  2. package/src/components/Asset/Asset.jsx +31 -81
  3. package/src/components/Media/Media.jsx +27 -125
  4. package/src/components/SocialIcon/index.jsx +146 -0
  5. package/src/hooks/index.js +6 -0
  6. package/src/hooks/useAccordion.js +143 -0
  7. package/src/hooks/useActiveRoute.js +97 -0
  8. package/src/hooks/useGridLayout.js +71 -0
  9. package/src/hooks/useMobileMenu.js +58 -0
  10. package/src/hooks/useScrolled.js +48 -0
  11. package/src/hooks/useTheme.js +205 -0
  12. package/src/index.js +29 -10
  13. package/src/styled/Asset/Asset.jsx +161 -0
  14. package/src/styled/Asset/index.js +1 -0
  15. package/src/{components → styled}/Disclaimer/Disclaimer.jsx +1 -1
  16. package/src/styled/Media/Media.jsx +322 -0
  17. package/src/styled/Media/index.js +1 -0
  18. package/src/{components → styled}/Section/Render.jsx +4 -4
  19. package/src/{components → styled}/Section/index.js +6 -0
  20. package/src/{components → styled}/Section/renderers/Alert.jsx +1 -1
  21. package/src/{components → styled}/Section/renderers/Details.jsx +1 -1
  22. package/src/{components → styled}/Section/renderers/Table.jsx +1 -1
  23. package/src/{components → styled}/Section/renderers/index.js +1 -1
  24. package/src/styled/SidebarLayout/SidebarLayout.jsx +310 -0
  25. package/src/styled/SidebarLayout/index.js +1 -0
  26. package/src/styled/index.js +40 -0
  27. /package/src/{components → styled}/Disclaimer/index.js +0 -0
  28. /package/src/{components → styled}/Section/Section.jsx +0 -0
  29. /package/src/{components → styled}/Section/renderers/Code.jsx +0 -0
  30. /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