@uniweb/kit 0.1.5 → 0.1.7
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/README.md +24 -0
- 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,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAccordion Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages expand/collapse state for accordion-style UIs.
|
|
5
|
+
* Used in FAQ components and collapsible navigation (LeftPanel).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Single-select accordion (only one item open at a time)
|
|
9
|
+
* function FAQ({ items }) {
|
|
10
|
+
* const { isOpen, toggle } = useAccordion()
|
|
11
|
+
*
|
|
12
|
+
* return items.map((item, i) => (
|
|
13
|
+
* <div key={i}>
|
|
14
|
+
* <button onClick={() => toggle(i)}>{item.question}</button>
|
|
15
|
+
* {isOpen(i) && <p>{item.answer}</p>}
|
|
16
|
+
* </div>
|
|
17
|
+
* ))
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Multi-select with first item open
|
|
22
|
+
* const { isOpen, toggle } = useAccordion({
|
|
23
|
+
* multiple: true,
|
|
24
|
+
* defaultOpen: [0]
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // All items open by default
|
|
29
|
+
* const { isOpen, toggle, openAll, closeAll } = useAccordion({
|
|
30
|
+
* multiple: true,
|
|
31
|
+
* defaultOpen: 'all',
|
|
32
|
+
* items: faqItems // needed when using 'all'
|
|
33
|
+
* })
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { useState, useCallback } from 'react'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook to manage accordion expand/collapse state.
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options - Configuration options
|
|
42
|
+
* @param {boolean} [options.multiple=false] - Allow multiple items open at once
|
|
43
|
+
* @param {Array|string} [options.defaultOpen=[]] - Initially open items (indices or 'all')
|
|
44
|
+
* @param {Array} [options.items] - Items array (needed for defaultOpen: 'all')
|
|
45
|
+
* @returns {Object} Accordion state and controls
|
|
46
|
+
*/
|
|
47
|
+
export function useAccordion(options = {}) {
|
|
48
|
+
const {
|
|
49
|
+
multiple = false,
|
|
50
|
+
defaultOpen = [],
|
|
51
|
+
items = [],
|
|
52
|
+
} = options
|
|
53
|
+
|
|
54
|
+
// Compute initial open state
|
|
55
|
+
const getInitialOpen = () => {
|
|
56
|
+
if (defaultOpen === 'all' && items.length > 0) {
|
|
57
|
+
return items.map((_, i) => i)
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(defaultOpen)) {
|
|
60
|
+
return defaultOpen
|
|
61
|
+
}
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [openItems, setOpenItems] = useState(getInitialOpen)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if an item is open
|
|
69
|
+
* @param {number|string} id - Item identifier (index or key)
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
const isOpen = useCallback((id) => {
|
|
73
|
+
return openItems.includes(id)
|
|
74
|
+
}, [openItems])
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Toggle an item open/closed
|
|
78
|
+
* @param {number|string} id - Item identifier
|
|
79
|
+
*/
|
|
80
|
+
const toggle = useCallback((id) => {
|
|
81
|
+
setOpenItems(prev => {
|
|
82
|
+
if (prev.includes(id)) {
|
|
83
|
+
// Close this item
|
|
84
|
+
return prev.filter(item => item !== id)
|
|
85
|
+
}
|
|
86
|
+
if (multiple) {
|
|
87
|
+
// Add to open items
|
|
88
|
+
return [...prev, id]
|
|
89
|
+
}
|
|
90
|
+
// Single select: replace open item
|
|
91
|
+
return [id]
|
|
92
|
+
})
|
|
93
|
+
}, [multiple])
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Open a specific item
|
|
97
|
+
* @param {number|string} id - Item identifier
|
|
98
|
+
*/
|
|
99
|
+
const open = useCallback((id) => {
|
|
100
|
+
setOpenItems(prev => {
|
|
101
|
+
if (prev.includes(id)) return prev
|
|
102
|
+
if (multiple) return [...prev, id]
|
|
103
|
+
return [id]
|
|
104
|
+
})
|
|
105
|
+
}, [multiple])
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Close a specific item
|
|
109
|
+
* @param {number|string} id - Item identifier
|
|
110
|
+
*/
|
|
111
|
+
const close = useCallback((id) => {
|
|
112
|
+
setOpenItems(prev => prev.filter(item => item !== id))
|
|
113
|
+
}, [])
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Open all items (only works in multiple mode)
|
|
117
|
+
* @param {Array} allIds - All item identifiers
|
|
118
|
+
*/
|
|
119
|
+
const openAll = useCallback((allIds) => {
|
|
120
|
+
if (multiple && Array.isArray(allIds)) {
|
|
121
|
+
setOpenItems(allIds)
|
|
122
|
+
}
|
|
123
|
+
}, [multiple])
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Close all items
|
|
127
|
+
*/
|
|
128
|
+
const closeAll = useCallback(() => {
|
|
129
|
+
setOpenItems([])
|
|
130
|
+
}, [])
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
openItems,
|
|
134
|
+
isOpen,
|
|
135
|
+
toggle,
|
|
136
|
+
open,
|
|
137
|
+
close,
|
|
138
|
+
openAll,
|
|
139
|
+
closeAll,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default useAccordion
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useActiveRoute Hook
|
|
3
|
+
*
|
|
4
|
+
* SSG-safe hook for active route detection in navigation components.
|
|
5
|
+
* Provides utilities for checking if pages are active or ancestors of the current route.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* function NavItem({ page }) {
|
|
9
|
+
* const { isActiveOrAncestor } = useActiveRoute()
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <Link
|
|
13
|
+
* href={page.getNavigableRoute()}
|
|
14
|
+
* className={isActiveOrAncestor(page) ? 'active' : ''}
|
|
15
|
+
* >
|
|
16
|
+
* {page.label}
|
|
17
|
+
* </Link>
|
|
18
|
+
* )
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useRouting } from './useRouting.js'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a route by removing leading/trailing slashes
|
|
26
|
+
* @param {string} route
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function normalizeRoute(route) {
|
|
30
|
+
return (route || '').replace(/^\//, '').replace(/\/$/, '')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook for active route detection with SSG-safe fallbacks.
|
|
35
|
+
*
|
|
36
|
+
* @returns {Object} Route utilities
|
|
37
|
+
* @property {string} route - Current normalized route (e.g., 'docs/getting-started')
|
|
38
|
+
* @property {string} rootSegment - First segment of route (e.g., 'docs')
|
|
39
|
+
* @property {function} isActive - Check if a page is the current page
|
|
40
|
+
* @property {function} isActiveOrAncestor - Check if a page or its descendants are active
|
|
41
|
+
*/
|
|
42
|
+
export function useActiveRoute() {
|
|
43
|
+
const { useLocation } = useRouting()
|
|
44
|
+
const location = useLocation()
|
|
45
|
+
|
|
46
|
+
const route = normalizeRoute(location?.pathname)
|
|
47
|
+
const rootSegment = route.split('/')[0]
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
/**
|
|
51
|
+
* Current normalized route (no leading/trailing slashes)
|
|
52
|
+
* @type {string}
|
|
53
|
+
*/
|
|
54
|
+
route,
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* First segment of the current route
|
|
58
|
+
* Useful for root-level navigation highlighting
|
|
59
|
+
* @type {string}
|
|
60
|
+
*/
|
|
61
|
+
rootSegment,
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if a page is the current active page (exact match)
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} page - Page object with getNormalizedRoute() or route property
|
|
67
|
+
* @returns {boolean}
|
|
68
|
+
*/
|
|
69
|
+
isActive: (page) => {
|
|
70
|
+
if (typeof page.getNormalizedRoute === 'function') {
|
|
71
|
+
return page.getNormalizedRoute() === route
|
|
72
|
+
}
|
|
73
|
+
// Fallback for page info objects from getPageHierarchy
|
|
74
|
+
return normalizeRoute(page.route) === route
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if a page or any of its descendants is active
|
|
79
|
+
* Useful for highlighting parent nav items when child is active
|
|
80
|
+
*
|
|
81
|
+
* @param {Object} page - Page object with isActiveOrAncestor() or route property
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
isActiveOrAncestor: (page) => {
|
|
85
|
+
if (typeof page.isActiveOrAncestor === 'function') {
|
|
86
|
+
return page.isActiveOrAncestor(route)
|
|
87
|
+
}
|
|
88
|
+
// Fallback for page info objects from getPageHierarchy
|
|
89
|
+
const pageRoute = normalizeRoute(page.route)
|
|
90
|
+
if (pageRoute === route) return true
|
|
91
|
+
if (pageRoute === '') return true // Root is ancestor of all
|
|
92
|
+
return route.startsWith(pageRoute + '/')
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export default useActiveRoute
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useGridLayout Hook
|
|
3
|
+
*
|
|
4
|
+
* Returns Tailwind CSS classes for responsive grid layouts.
|
|
5
|
+
* Standardizes the grid column patterns used across components.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* function Features({ items, params }) {
|
|
9
|
+
* const gridClass = useGridLayout(params.columns)
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <div className={gridClass}>
|
|
13
|
+
* {items.map(item => <FeatureCard key={item.id} {...item} />)}
|
|
14
|
+
* </div>
|
|
15
|
+
* )
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // With custom gap
|
|
20
|
+
* const gridClass = useGridLayout(3, { gap: 12 })
|
|
21
|
+
* // Returns: "grid gap-12 sm:grid-cols-2 lg:grid-cols-3"
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Standard responsive grid column configurations.
|
|
26
|
+
* Keys are column counts, values are Tailwind classes.
|
|
27
|
+
*/
|
|
28
|
+
const GRID_CONFIGS = {
|
|
29
|
+
1: '',
|
|
30
|
+
2: 'sm:grid-cols-2',
|
|
31
|
+
3: 'sm:grid-cols-2 lg:grid-cols-3',
|
|
32
|
+
4: 'sm:grid-cols-2 lg:grid-cols-4',
|
|
33
|
+
5: 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5',
|
|
34
|
+
6: 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Hook to generate responsive grid layout classes.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} columns - Number of columns (1-6)
|
|
41
|
+
* @param {Object} options - Configuration options
|
|
42
|
+
* @param {number} [options.gap=8] - Gap size (Tailwind scale: 4, 6, 8, 10, 12)
|
|
43
|
+
* @param {string} [options.baseClass='grid'] - Base class to include
|
|
44
|
+
* @returns {string} Tailwind CSS classes for the grid
|
|
45
|
+
*/
|
|
46
|
+
export function useGridLayout(columns = 3, options = {}) {
|
|
47
|
+
const {
|
|
48
|
+
gap = 8,
|
|
49
|
+
baseClass = 'grid',
|
|
50
|
+
} = options
|
|
51
|
+
|
|
52
|
+
const colConfig = GRID_CONFIGS[columns] || GRID_CONFIGS[3]
|
|
53
|
+
const gapClass = `gap-${gap}`
|
|
54
|
+
|
|
55
|
+
return [baseClass, gapClass, colConfig].filter(Boolean).join(' ')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get grid classes without the hook (for non-React contexts)
|
|
60
|
+
* @param {number} columns
|
|
61
|
+
* @param {Object} options
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
export function getGridClasses(columns = 3, options = {}) {
|
|
65
|
+
const { gap = 8, baseClass = 'grid' } = options
|
|
66
|
+
const colConfig = GRID_CONFIGS[columns] || GRID_CONFIGS[3]
|
|
67
|
+
const gapClass = `gap-${gap}`
|
|
68
|
+
return [baseClass, gapClass, colConfig].filter(Boolean).join(' ')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default useGridLayout
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMobileMenu Hook
|
|
3
|
+
*
|
|
4
|
+
* Manages mobile menu state with automatic close on route change.
|
|
5
|
+
* Common pattern in Header/Navbar components across all templates.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* function Header() {
|
|
9
|
+
* const { isOpen, toggle, close } = useMobileMenu()
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <>
|
|
13
|
+
* <button onClick={toggle}>Menu</button>
|
|
14
|
+
* {isOpen && (
|
|
15
|
+
* <nav>
|
|
16
|
+
* <Link href="/about" onClick={close}>About</Link>
|
|
17
|
+
* </nav>
|
|
18
|
+
* )}
|
|
19
|
+
* </>
|
|
20
|
+
* )
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
25
|
+
import { useActiveRoute } from './useActiveRoute.js'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Hook to manage mobile menu state.
|
|
29
|
+
* Automatically closes menu on route change.
|
|
30
|
+
*
|
|
31
|
+
* @returns {Object} Menu state and controls
|
|
32
|
+
* @property {boolean} isOpen - Whether menu is open
|
|
33
|
+
* @property {function} open - Open the menu
|
|
34
|
+
* @property {function} close - Close the menu
|
|
35
|
+
* @property {function} toggle - Toggle menu open/closed
|
|
36
|
+
*/
|
|
37
|
+
export function useMobileMenu() {
|
|
38
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
39
|
+
const { route } = useActiveRoute()
|
|
40
|
+
|
|
41
|
+
// Close menu on route change
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setIsOpen(false)
|
|
44
|
+
}, [route])
|
|
45
|
+
|
|
46
|
+
const open = useCallback(() => setIsOpen(true), [])
|
|
47
|
+
const close = useCallback(() => setIsOpen(false), [])
|
|
48
|
+
const toggle = useCallback(() => setIsOpen(prev => !prev), [])
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isOpen,
|
|
52
|
+
open,
|
|
53
|
+
close,
|
|
54
|
+
toggle,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default useMobileMenu
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScrolled Hook
|
|
3
|
+
*
|
|
4
|
+
* Detects scroll position for sticky header effects.
|
|
5
|
+
* Common pattern in Header components across all templates.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* function Header() {
|
|
9
|
+
* const scrolled = useScrolled()
|
|
10
|
+
* return (
|
|
11
|
+
* <header className={scrolled ? 'bg-white shadow' : 'bg-transparent'}>
|
|
12
|
+
* ...
|
|
13
|
+
* </header>
|
|
14
|
+
* )
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // With custom threshold
|
|
19
|
+
* const scrolled = useScrolled(50) // triggers after 50px scroll
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { useState, useEffect } from 'react'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Hook to detect if page has scrolled past a threshold.
|
|
26
|
+
*
|
|
27
|
+
* @param {number} threshold - Scroll position threshold in pixels (default: 0)
|
|
28
|
+
* @returns {boolean} Whether scroll position is past threshold
|
|
29
|
+
*/
|
|
30
|
+
export function useScrolled(threshold = 0) {
|
|
31
|
+
const [scrolled, setScrolled] = useState(false)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const handleScroll = () => {
|
|
35
|
+
setScrolled(window.scrollY > threshold)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check initial state
|
|
39
|
+
handleScroll()
|
|
40
|
+
|
|
41
|
+
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
42
|
+
return () => window.removeEventListener('scroll', handleScroll)
|
|
43
|
+
}, [threshold])
|
|
44
|
+
|
|
45
|
+
return scrolled
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default useScrolled
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useTheme Hook
|
|
3
|
+
*
|
|
4
|
+
* Provides standardized theme classes for components.
|
|
5
|
+
* Eliminates duplicated theme objects across 25+ components.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* function Hero({ params }) {
|
|
9
|
+
* const t = useTheme(params.theme)
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <section className={t.section}>
|
|
13
|
+
* <h1 className={t.title}>...</h1>
|
|
14
|
+
* <p className={t.body}>...</p>
|
|
15
|
+
* </section>
|
|
16
|
+
* )
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // With custom overrides
|
|
21
|
+
* const t = useTheme('dark', {
|
|
22
|
+
* card: 'bg-gray-800 rounded-xl',
|
|
23
|
+
* cardHover: 'hover:bg-gray-700'
|
|
24
|
+
* })
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Standard theme definitions.
|
|
29
|
+
* Keys are standardized across all components.
|
|
30
|
+
*/
|
|
31
|
+
const THEMES = {
|
|
32
|
+
light: {
|
|
33
|
+
// Section backgrounds
|
|
34
|
+
section: 'bg-white',
|
|
35
|
+
sectionAlt: 'bg-gray-50',
|
|
36
|
+
|
|
37
|
+
// Typography
|
|
38
|
+
title: 'text-gray-900',
|
|
39
|
+
subtitle: 'text-gray-700',
|
|
40
|
+
pretitle: 'text-primary',
|
|
41
|
+
body: 'text-gray-600',
|
|
42
|
+
muted: 'text-gray-500',
|
|
43
|
+
|
|
44
|
+
// Cards
|
|
45
|
+
card: 'bg-gray-50',
|
|
46
|
+
cardHover: 'hover:shadow-lg',
|
|
47
|
+
cardBorder: 'border border-gray-200',
|
|
48
|
+
|
|
49
|
+
// Interactive
|
|
50
|
+
link: 'text-primary hover:text-primary-dark',
|
|
51
|
+
linkMuted: 'text-gray-600 hover:text-gray-900',
|
|
52
|
+
|
|
53
|
+
// Borders & Dividers
|
|
54
|
+
border: 'border-gray-200',
|
|
55
|
+
divider: 'bg-gray-200',
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
gray: {
|
|
59
|
+
section: 'bg-gray-50',
|
|
60
|
+
sectionAlt: 'bg-gray-100',
|
|
61
|
+
|
|
62
|
+
title: 'text-gray-900',
|
|
63
|
+
subtitle: 'text-gray-700',
|
|
64
|
+
pretitle: 'text-primary',
|
|
65
|
+
body: 'text-gray-600',
|
|
66
|
+
muted: 'text-gray-500',
|
|
67
|
+
|
|
68
|
+
card: 'bg-white shadow-sm',
|
|
69
|
+
cardHover: 'hover:shadow-lg',
|
|
70
|
+
cardBorder: 'border border-gray-200',
|
|
71
|
+
|
|
72
|
+
link: 'text-primary hover:text-primary-dark',
|
|
73
|
+
linkMuted: 'text-gray-600 hover:text-gray-900',
|
|
74
|
+
|
|
75
|
+
border: 'border-gray-200',
|
|
76
|
+
divider: 'bg-gray-300',
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
dark: {
|
|
80
|
+
section: 'bg-gray-900',
|
|
81
|
+
sectionAlt: 'bg-gray-800',
|
|
82
|
+
|
|
83
|
+
title: 'text-white',
|
|
84
|
+
subtitle: 'text-gray-300',
|
|
85
|
+
pretitle: 'text-primary',
|
|
86
|
+
body: 'text-gray-400',
|
|
87
|
+
muted: 'text-gray-500',
|
|
88
|
+
|
|
89
|
+
card: 'bg-gray-800',
|
|
90
|
+
cardHover: 'hover:bg-gray-750',
|
|
91
|
+
cardBorder: 'border border-gray-700',
|
|
92
|
+
|
|
93
|
+
link: 'text-primary hover:text-primary-light',
|
|
94
|
+
linkMuted: 'text-gray-400 hover:text-white',
|
|
95
|
+
|
|
96
|
+
border: 'border-gray-700',
|
|
97
|
+
divider: 'bg-gray-700',
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
primary: {
|
|
101
|
+
section: 'bg-primary',
|
|
102
|
+
sectionAlt: 'bg-primary-dark',
|
|
103
|
+
|
|
104
|
+
title: 'text-white',
|
|
105
|
+
subtitle: 'text-white/90',
|
|
106
|
+
pretitle: 'text-white/80',
|
|
107
|
+
body: 'text-white/80',
|
|
108
|
+
muted: 'text-white/60',
|
|
109
|
+
|
|
110
|
+
card: 'bg-white/10',
|
|
111
|
+
cardHover: 'hover:bg-white/20',
|
|
112
|
+
cardBorder: 'border border-white/20',
|
|
113
|
+
|
|
114
|
+
link: 'text-white hover:text-white/80',
|
|
115
|
+
linkMuted: 'text-white/70 hover:text-white',
|
|
116
|
+
|
|
117
|
+
border: 'border-white/20',
|
|
118
|
+
divider: 'bg-white/20',
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
gradient: {
|
|
122
|
+
section: 'bg-gradient-to-br from-primary to-indigo-600',
|
|
123
|
+
sectionAlt: 'bg-gradient-to-br from-primary-dark to-indigo-700',
|
|
124
|
+
|
|
125
|
+
title: 'text-white',
|
|
126
|
+
subtitle: 'text-white/90',
|
|
127
|
+
pretitle: 'text-white/80',
|
|
128
|
+
body: 'text-white/80',
|
|
129
|
+
muted: 'text-white/60',
|
|
130
|
+
|
|
131
|
+
card: 'bg-white/10 backdrop-blur-sm',
|
|
132
|
+
cardHover: 'hover:bg-white/20',
|
|
133
|
+
cardBorder: 'border border-white/20',
|
|
134
|
+
|
|
135
|
+
link: 'text-white hover:text-white/80',
|
|
136
|
+
linkMuted: 'text-white/70 hover:text-white',
|
|
137
|
+
|
|
138
|
+
border: 'border-white/20',
|
|
139
|
+
divider: 'bg-white/20',
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
glass: {
|
|
143
|
+
section: 'bg-white/10 backdrop-blur-lg',
|
|
144
|
+
sectionAlt: 'bg-white/5 backdrop-blur-lg',
|
|
145
|
+
|
|
146
|
+
title: 'text-white',
|
|
147
|
+
subtitle: 'text-white/90',
|
|
148
|
+
pretitle: 'text-primary',
|
|
149
|
+
body: 'text-white/80',
|
|
150
|
+
muted: 'text-white/60',
|
|
151
|
+
|
|
152
|
+
card: 'bg-white/10 backdrop-blur-sm',
|
|
153
|
+
cardHover: 'hover:bg-white/20',
|
|
154
|
+
cardBorder: 'border border-white/20',
|
|
155
|
+
|
|
156
|
+
link: 'text-white hover:text-primary',
|
|
157
|
+
linkMuted: 'text-white/70 hover:text-white',
|
|
158
|
+
|
|
159
|
+
border: 'border-white/20',
|
|
160
|
+
divider: 'bg-white/20',
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Hook to get theme classes by name.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} themeName - Theme name (light, gray, dark, primary, gradient, glass)
|
|
168
|
+
* @param {Object} overrides - Custom class overrides
|
|
169
|
+
* @returns {Object} Theme classes object
|
|
170
|
+
*/
|
|
171
|
+
export function useTheme(themeName = 'light', overrides = {}) {
|
|
172
|
+
const baseTheme = THEMES[themeName] || THEMES.light
|
|
173
|
+
|
|
174
|
+
if (Object.keys(overrides).length === 0) {
|
|
175
|
+
return baseTheme
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
...baseTheme,
|
|
180
|
+
...overrides,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get theme classes without the hook (for non-React contexts)
|
|
186
|
+
* @param {string} themeName
|
|
187
|
+
* @param {Object} overrides
|
|
188
|
+
* @returns {Object}
|
|
189
|
+
*/
|
|
190
|
+
export function getThemeClasses(themeName = 'light', overrides = {}) {
|
|
191
|
+
const baseTheme = THEMES[themeName] || THEMES.light
|
|
192
|
+
return { ...baseTheme, ...overrides }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* All available theme names
|
|
197
|
+
*/
|
|
198
|
+
export const THEME_NAMES = Object.keys(THEMES)
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Export theme definitions for customization
|
|
202
|
+
*/
|
|
203
|
+
export { THEMES }
|
|
204
|
+
|
|
205
|
+
export default useTheme
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Standard component library for Uniweb foundations.
|
|
5
5
|
*
|
|
6
|
+
* For pre-styled components (Section, SidebarLayout, Disclaimer, etc.),
|
|
7
|
+
* import from '@uniweb/kit/styled'.
|
|
8
|
+
*
|
|
6
9
|
* @example
|
|
7
10
|
* import { Link, Image, useWebsite } from '@uniweb/kit'
|
|
8
11
|
*
|
|
@@ -18,10 +21,10 @@
|
|
|
18
21
|
*/
|
|
19
22
|
|
|
20
23
|
// ============================================================================
|
|
21
|
-
// Components
|
|
24
|
+
// Components (Primitives - no Tailwind dependency)
|
|
22
25
|
// ============================================================================
|
|
23
26
|
|
|
24
|
-
//
|
|
27
|
+
// Navigation
|
|
25
28
|
export { Link } from './components/Link/index.js'
|
|
26
29
|
export { Image } from './components/Image/index.js'
|
|
27
30
|
export { SafeHtml } from './components/SafeHtml/index.js'
|
|
@@ -35,24 +38,40 @@ export {
|
|
|
35
38
|
PlainText
|
|
36
39
|
} from './components/Text/index.js'
|
|
37
40
|
|
|
38
|
-
// Media
|
|
41
|
+
// Media (plain version - for styled facade, use @uniweb/kit/tailwind)
|
|
39
42
|
export { Media } from './components/Media/index.js'
|
|
40
43
|
export { FileLogo } from './components/FileLogo/index.js'
|
|
41
44
|
export { MediaIcon } from './components/MediaIcon/index.js'
|
|
42
45
|
|
|
43
|
-
//
|
|
44
|
-
export { Section, Render } from './components/Section/index.js'
|
|
45
|
-
export { Code, Alert, Warning, Table, Details, Divider } from './components/Section/renderers/index.js'
|
|
46
|
-
|
|
47
|
-
// Utilities
|
|
46
|
+
// Files (plain version - for styled card, use @uniweb/kit/tailwind)
|
|
48
47
|
export { Asset } from './components/Asset/index.js'
|
|
49
|
-
|
|
48
|
+
|
|
49
|
+
// Social
|
|
50
|
+
export {
|
|
51
|
+
SocialIcon,
|
|
52
|
+
getSocialPlatform,
|
|
53
|
+
isSocialLink,
|
|
54
|
+
filterSocialLinks
|
|
55
|
+
} from './components/SocialIcon/index.jsx'
|
|
50
56
|
|
|
51
57
|
// ============================================================================
|
|
52
58
|
// Hooks
|
|
53
59
|
// ============================================================================
|
|
54
60
|
|
|
55
|
-
export {
|
|
61
|
+
export {
|
|
62
|
+
useWebsite,
|
|
63
|
+
useRouting,
|
|
64
|
+
useActiveRoute,
|
|
65
|
+
useScrolled,
|
|
66
|
+
useMobileMenu,
|
|
67
|
+
useAccordion,
|
|
68
|
+
useGridLayout,
|
|
69
|
+
getGridClasses,
|
|
70
|
+
useTheme,
|
|
71
|
+
getThemeClasses,
|
|
72
|
+
THEMES,
|
|
73
|
+
THEME_NAMES
|
|
74
|
+
} from './hooks/index.js'
|
|
56
75
|
|
|
57
76
|
// ============================================================================
|
|
58
77
|
// Utilities
|