oihana-next-ui 0.2.3 → 0.2.5
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 +16 -16
- package/src/@configs/navigation.js +6 -0
- package/src/@locale/navigation.js +6 -0
- package/src/app/lab/@tabs/effects/page.js +31 -0
- package/src/app/lab/@tabs/megamenu/page.js +29 -0
- package/src/app/lab/@tabs/tooltips/page.js +29 -0
- package/src/components/Aura.jsx +110 -0
- package/src/components/Tooltip.jsx +3 -1
- package/src/components/menus/Megamenu.jsx +165 -0
- package/src/components/menus/MenuLink.jsx +1 -1
- package/src/components/ranges/Range.jsx +11 -6
- package/src/components/rating/Rating.jsx +1 -1
- package/src/contexts/navigation/context.js +3 -0
- package/src/contexts/navigation/helpers/containsActivePath.js +4 -3
- package/src/contexts/navigation/helpers/findActiveLinkPath.js +69 -0
- package/src/contexts/navigation/helpers/isPathMatch.js +51 -0
- package/src/contexts/navigation/provider.js +28 -12
- package/src/demo/effects/AuraDemo.jsx +125 -0
- package/src/demo/menus/MegamenuDemo.jsx +125 -0
- package/src/demo/ranges/RangeDemo.jsx +28 -0
- package/src/demo/rating/RatingDemo.jsx +17 -0
- package/src/demo/tooltips/TooltipDemo.jsx +100 -0
- package/src/display/ui/navigation/Link.jsx +12 -2
- package/src/themes/components/range.js +21 -4
- package/src/themes/components/rating.js +39 -12
- package/src/themes/components/tooltip.js +28 -2
- package/src/themes/effects/aura.js +162 -0
- package/src/themes/navigation/megamenu.js +128 -0
- package/src/version.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oihana-next-ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Oihana Next.js UI component library — reusable components, hooks and utilities built with React 19, Next.js, Tailwind CSS and DaisyUI",
|
|
6
6
|
"author": {
|
|
@@ -57,48 +57,48 @@
|
|
|
57
57
|
"daisyui": "^5.0.0"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@maskito/core": "^5.
|
|
61
|
-
"@maskito/kit": "^5.
|
|
62
|
-
"@maskito/react": "^5.
|
|
60
|
+
"@maskito/core": "^5.3.1",
|
|
61
|
+
"@maskito/kit": "^5.3.1",
|
|
62
|
+
"@maskito/react": "^5.3.1",
|
|
63
63
|
"@use-gesture/react": "^10.3.1",
|
|
64
64
|
"chroma-js": "^3.2.0",
|
|
65
65
|
"clsx": "^2.1.1",
|
|
66
|
-
"dayjs": "^1.11.
|
|
66
|
+
"dayjs": "^1.11.21",
|
|
67
67
|
"flag-icons": "^7.5.0",
|
|
68
68
|
"html-react-parser": "^5.2.17",
|
|
69
69
|
"ky": "^1.14.3",
|
|
70
|
-
"motion": "^12.
|
|
70
|
+
"motion": "^12.42.0",
|
|
71
71
|
"next": "16.2.3",
|
|
72
72
|
"react": "19.2.3",
|
|
73
73
|
"react-dom": "19.2.3",
|
|
74
74
|
"react-icons": "^5.6.0",
|
|
75
|
-
"react-is": "^19.2.
|
|
75
|
+
"react-is": "^19.2.7",
|
|
76
76
|
"react-markdown": "^10.1.0",
|
|
77
77
|
"react-syntax-highlighter": "^16.1.1",
|
|
78
|
-
"react-use": "^17.6.
|
|
78
|
+
"react-use": "^17.6.1",
|
|
79
79
|
"rehype-external-links": "^3.0.0",
|
|
80
80
|
"rehype-raw": "^7.0.0",
|
|
81
81
|
"remark-breaks": "^4.0.0",
|
|
82
82
|
"remark-gfm": "^4.0.1",
|
|
83
|
-
"sanitize-html": "^2.17.
|
|
84
|
-
"tailwind-merge": "^3.
|
|
83
|
+
"sanitize-html": "^2.17.5",
|
|
84
|
+
"tailwind-merge": "^3.6.0",
|
|
85
85
|
"validator": "^13.15.35",
|
|
86
86
|
"vegas-js-core": "^1.0.47"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@biomejs/biome": "2.2.0",
|
|
90
|
-
"@tailwindcss/postcss": "^4.
|
|
91
|
-
"@tailwindcss/typography": "^0.5.
|
|
92
|
-
"@types/node": "^25.
|
|
90
|
+
"@tailwindcss/postcss": "^4.3.1",
|
|
91
|
+
"@tailwindcss/typography": "^0.5.20",
|
|
92
|
+
"@types/node": "^25.9.4",
|
|
93
93
|
"@types/react-syntax-highlighter": "^15.5.13",
|
|
94
94
|
"@types/sanitize-html": "^2.16.1",
|
|
95
95
|
"babel-plugin-react-compiler": "1.0.0",
|
|
96
|
-
"daisyui": "^5.
|
|
96
|
+
"daisyui": "^5.6.2",
|
|
97
97
|
"raw-loader": "^4.0.2",
|
|
98
98
|
"sharp": "^0.34.5",
|
|
99
99
|
"tailwind-scrollbar": "^4.0.2",
|
|
100
|
-
"tailwindcss": "^4.
|
|
101
|
-
"tailwindcss-animated": "^2.
|
|
100
|
+
"tailwindcss": "^4.3.1",
|
|
101
|
+
"tailwindcss-animated": "^2.1.0",
|
|
102
102
|
"tailwindcss-opentype": "^1.2.0"
|
|
103
103
|
},
|
|
104
104
|
"resolutions": {
|
|
@@ -18,6 +18,7 @@ import { RxAvatar as AvatarIcon } from "react-icons/rx";
|
|
|
18
18
|
import { LuBadgeAlert as BadgeIcon } from "react-icons/lu";
|
|
19
19
|
import { RiCheckboxFill as CheckBoxIcon } from "react-icons/ri";
|
|
20
20
|
import { LuListCollapse as CollapseIcon } from "react-icons/lu";
|
|
21
|
+
import { LuSparkles as EffectIcon } from "react-icons/lu";
|
|
21
22
|
import { CiGrid31 as FlexIcon } from "react-icons/ci";
|
|
22
23
|
import { IoMdGrid as GridIcon } from "react-icons/io";
|
|
23
24
|
import { FaPager as HeaderIcon } from "react-icons/fa6";
|
|
@@ -31,6 +32,7 @@ import { BsFillMarkdownFill as MarkdownIcon } from 'react-icons/bs' ;
|
|
|
31
32
|
import { GiDominoMask as MaskIcon } from "react-icons/gi";
|
|
32
33
|
import { RiLayoutMasonryFill as MasonryIcon } from "react-icons/ri";
|
|
33
34
|
import { TfiLayoutMenuSeparated as MenuIcon } from "react-icons/tfi" ;
|
|
35
|
+
import { LuLayoutPanelTop as MegamenuIcon } from "react-icons/lu";
|
|
34
36
|
import { SiDialogflow as ModalIcon } from "react-icons/si";
|
|
35
37
|
import { MdAnimation as MotionIcon } from "react-icons/md";
|
|
36
38
|
import { BiFoodMenu as NavigationIcon } from "react-icons/bi";
|
|
@@ -46,6 +48,7 @@ import { FaSpinner as SpinnerIcon } from "react-icons/fa";
|
|
|
46
48
|
import { GrStatusPlaceholder as StatusIcon } from "react-icons/gr";
|
|
47
49
|
import { BsTextareaResize as TextAreaIcon } from "react-icons/bs" ;
|
|
48
50
|
import { TiMessages as ToastIcon } from "react-icons/ti" ;
|
|
51
|
+
import { LuMessageSquareText as TooltipIcon } from "react-icons/lu";
|
|
49
52
|
import { FaToggleOn as ToggleIcon } from "react-icons/fa";
|
|
50
53
|
|
|
51
54
|
const navigation =
|
|
@@ -91,6 +94,7 @@ const navigation =
|
|
|
91
94
|
[
|
|
92
95
|
{ id : 'avatars' , type : LINK , Icon : AvatarIcon , path : '/lab/avatars' } ,
|
|
93
96
|
{ id : 'badges' , type : LINK , Icon : BadgeIcon , path : '/lab/badges' } ,
|
|
97
|
+
{ id : 'effects' , type : LINK , Icon : EffectIcon , path : '/lab/effects' } ,
|
|
94
98
|
{ id : 'images' , type : LINK , Icon : ImageIcon , path : '/lab/images' } ,
|
|
95
99
|
{ id : 'lists' , type : LINK , Icon : ListIcon , path : '/lab/lists' } ,
|
|
96
100
|
{ id : 'masks' , type : LINK , Icon : MaskIcon , path : '/lab/masks' } ,
|
|
@@ -110,6 +114,7 @@ const navigation =
|
|
|
110
114
|
[
|
|
111
115
|
{ id : 'pagination' , type : LINK , Icon : PaginationIcon , path : '/lab/pagination' } ,
|
|
112
116
|
{ id : 'menus' , type : LINK , Icon : MenuIcon , path : '/lab/menus' } ,
|
|
117
|
+
{ id : 'megamenu' , type : LINK , Icon : MegamenuIcon , path : '/lab/megamenu' } ,
|
|
113
118
|
]
|
|
114
119
|
} ,
|
|
115
120
|
{
|
|
@@ -123,6 +128,7 @@ const navigation =
|
|
|
123
128
|
{ id : 'radialProgress' , type : LINK , Icon : RadialProgressIcon , path : '/lab/radialProgress' } ,
|
|
124
129
|
{ id : 'spinners' , type : LINK , Icon : SpinnerIcon , path : '/lab/spinners' } ,
|
|
125
130
|
{ id : 'toasts' , type : LINK , Icon : ToastIcon , path : '/lab/toasts' } ,
|
|
131
|
+
{ id : 'tooltips' , type : LINK , Icon : TooltipIcon , path : '/lab/tooltips' } ,
|
|
126
132
|
]
|
|
127
133
|
} ,
|
|
128
134
|
{
|
|
@@ -10,6 +10,7 @@ const navigation =
|
|
|
10
10
|
display : 'Display' ,
|
|
11
11
|
avatars : 'Avatars' ,
|
|
12
12
|
badges : 'Badges' ,
|
|
13
|
+
effects : 'Effets' ,
|
|
13
14
|
headers : 'Entêtes' ,
|
|
14
15
|
images : 'Images' ,
|
|
15
16
|
markdown : 'Markdown' ,
|
|
@@ -33,6 +34,7 @@ const navigation =
|
|
|
33
34
|
radialProgress : 'Radial Progress' ,
|
|
34
35
|
spinners : 'Spinners' ,
|
|
35
36
|
toasts : 'Toasts' ,
|
|
37
|
+
tooltips : 'Tooltips' ,
|
|
36
38
|
form : 'Formulaire' ,
|
|
37
39
|
checkboxes : 'Cases à cocher' ,
|
|
38
40
|
inputs : 'Champs de saisie' ,
|
|
@@ -45,6 +47,7 @@ const navigation =
|
|
|
45
47
|
navigation : 'Navigation' ,
|
|
46
48
|
lists : 'Listes' ,
|
|
47
49
|
menus : 'Menus' ,
|
|
50
|
+
megamenu : 'Megamenu' ,
|
|
48
51
|
pagination : 'Pagination' ,
|
|
49
52
|
tests : 'Tests' ,
|
|
50
53
|
|
|
@@ -59,6 +62,7 @@ const navigation =
|
|
|
59
62
|
display : 'Display' ,
|
|
60
63
|
avatars : 'Avatars' ,
|
|
61
64
|
badges : 'Badges' ,
|
|
65
|
+
effects : 'Effects' ,
|
|
62
66
|
headers : 'Headers' ,
|
|
63
67
|
images : 'Images' ,
|
|
64
68
|
markdown : 'Markdown' ,
|
|
@@ -82,6 +86,7 @@ const navigation =
|
|
|
82
86
|
radialProgress : 'Radial Progress' ,
|
|
83
87
|
spinners : 'Spinners' ,
|
|
84
88
|
toasts : 'Toasts' ,
|
|
89
|
+
tooltips : 'Tooltips' ,
|
|
85
90
|
form : 'Form' ,
|
|
86
91
|
checkboxes : 'Checkboxes' ,
|
|
87
92
|
inputs : 'Inputs' ,
|
|
@@ -94,6 +99,7 @@ const navigation =
|
|
|
94
99
|
navigation : 'Navigation' ,
|
|
95
100
|
lists : 'Lists' ,
|
|
96
101
|
menus : 'Menus' ,
|
|
102
|
+
megamenu : 'Megamenu' ,
|
|
97
103
|
pagination : 'Pagination' ,
|
|
98
104
|
tests : 'Tests' ,
|
|
99
105
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client' ;
|
|
2
|
+
|
|
3
|
+
import AuraDemo from '@/demo/effects/AuraDemo' ;
|
|
4
|
+
import Container from '@/display/Container' ;
|
|
5
|
+
import Page from '@/display/Page' ;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Effects showcase page.
|
|
9
|
+
*
|
|
10
|
+
* Displays the visual effect components (Aura, …).
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} props
|
|
13
|
+
*/
|
|
14
|
+
const EffectsShowcase = () =>
|
|
15
|
+
{
|
|
16
|
+
return (
|
|
17
|
+
<Page full className='gap-8'>
|
|
18
|
+
|
|
19
|
+
<Container className="text-center" maxWidth="max-w-4xl">
|
|
20
|
+
<h1 className="text-4xl font-bold bg-linear-to-r from-secondary to-primary inline-block text-transparent bg-clip-text">
|
|
21
|
+
Effect Components
|
|
22
|
+
</h1>
|
|
23
|
+
</Container>
|
|
24
|
+
|
|
25
|
+
<AuraDemo />
|
|
26
|
+
|
|
27
|
+
</Page>
|
|
28
|
+
) ;
|
|
29
|
+
} ;
|
|
30
|
+
|
|
31
|
+
export default EffectsShowcase ;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client' ;
|
|
2
|
+
|
|
3
|
+
import MegamenuDemo from '@/demo/menus/MegamenuDemo' ;
|
|
4
|
+
import Container from '@/display/Container' ;
|
|
5
|
+
import Page from '@/display/Page' ;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Megamenu showcase page.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
*/
|
|
12
|
+
const MegamenuShowcase = () =>
|
|
13
|
+
{
|
|
14
|
+
return (
|
|
15
|
+
<Page full className='gap-8'>
|
|
16
|
+
|
|
17
|
+
<Container className="text-center" maxWidth="max-w-4xl">
|
|
18
|
+
<h1 className="text-4xl font-bold bg-linear-to-r from-secondary to-primary inline-block text-transparent bg-clip-text">
|
|
19
|
+
Megamenu Component
|
|
20
|
+
</h1>
|
|
21
|
+
</Container>
|
|
22
|
+
|
|
23
|
+
<MegamenuDemo />
|
|
24
|
+
|
|
25
|
+
</Page>
|
|
26
|
+
) ;
|
|
27
|
+
} ;
|
|
28
|
+
|
|
29
|
+
export default MegamenuShowcase ;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client' ;
|
|
2
|
+
|
|
3
|
+
import TooltipDemo from '@/demo/tooltips/TooltipDemo' ;
|
|
4
|
+
import Container from '@/display/Container' ;
|
|
5
|
+
import Page from '@/display/Page' ;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tooltip showcase page.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
*/
|
|
12
|
+
const TooltipsShowcase = () =>
|
|
13
|
+
{
|
|
14
|
+
return (
|
|
15
|
+
<Page full className='gap-8'>
|
|
16
|
+
|
|
17
|
+
<Container className="text-center" maxWidth="max-w-4xl">
|
|
18
|
+
<h1 className="text-4xl font-bold bg-linear-to-r from-secondary to-primary inline-block text-transparent bg-clip-text">
|
|
19
|
+
Tooltip Component
|
|
20
|
+
</h1>
|
|
21
|
+
</Container>
|
|
22
|
+
|
|
23
|
+
<TooltipDemo />
|
|
24
|
+
|
|
25
|
+
</Page>
|
|
26
|
+
) ;
|
|
27
|
+
} ;
|
|
28
|
+
|
|
29
|
+
export default TooltipsShowcase ;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use client' ;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Aura component for DaisyUI 5.6.
|
|
5
|
+
*
|
|
6
|
+
* Wraps any content with a border light effect. By default the aura is always
|
|
7
|
+
* animating ; set `trigger="hover"` to only reveal and animate it on hover.
|
|
8
|
+
*
|
|
9
|
+
* The light effect is built on `currentColor`, so in `hover` mode the wrapper
|
|
10
|
+
* colour is forced transparent at rest. Make sure the wrapped content sets its
|
|
11
|
+
* own text colour (e.g. a `card` with `text-base-content`) so it stays visible.
|
|
12
|
+
*
|
|
13
|
+
* @module components/Aura
|
|
14
|
+
* @see https://daisyui.com/components/aura
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```jsx
|
|
18
|
+
* // Always-on rainbow aura around a card
|
|
19
|
+
* <Aura variant="rainbow">
|
|
20
|
+
* <div className="card bg-base-100"><div className="card-body">Hi</div></div>
|
|
21
|
+
* </Aura>
|
|
22
|
+
*
|
|
23
|
+
* // Reveal on hover only, custom colour and duration
|
|
24
|
+
* <Aura trigger="hover" color="primary" duration={2000}>
|
|
25
|
+
* <button className="btn">Hover me</button>
|
|
26
|
+
* </Aura>
|
|
27
|
+
*
|
|
28
|
+
* // Custom colour + background tint (daisyUI custom-colour example)
|
|
29
|
+
* <Aura color="warning" background="warning">
|
|
30
|
+
* <div className="card bg-base-100 text-base-content"><div className="card-body">Hi</div></div>
|
|
31
|
+
* </Aura>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import cn from '../themes/helpers/cn' ;
|
|
36
|
+
|
|
37
|
+
import getAuraClasses, { ALWAYS, HOVER } from '../themes/effects/aura' ;
|
|
38
|
+
|
|
39
|
+
import getTextColor from '../themes/colors/textColor' ;
|
|
40
|
+
import getBackgroundColor from '../themes/colors/backgroundColor' ;
|
|
41
|
+
import { TRANSPARENT } from '../themes/colors/tailwindcss' ;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Object} props
|
|
45
|
+
* @param {React.ElementType} [props.as='div'] - Root element type.
|
|
46
|
+
* @param {string} [props.background] - Background tint colour constant (→ `bg-*`).
|
|
47
|
+
* @param {React.ReactNode} [props.children] - Wrapped content.
|
|
48
|
+
* @param {string} [props.className] - Additional class name.
|
|
49
|
+
* @param {string} [props.color] - Light colour constant (→ `text-*`).
|
|
50
|
+
* @param {number} [props.duration] - Animation duration in milliseconds.
|
|
51
|
+
* @param {React.Ref} [props.ref] - Forwarded ref.
|
|
52
|
+
* @param {import('../themes/effects/aura').AuraSize} [props.size='md'] - Aura size.
|
|
53
|
+
* @param {import('../themes/effects/aura').AuraTrigger} [props.trigger='always'] - 'always' or 'hover'.
|
|
54
|
+
* @param {import('../themes/effects/aura').AuraVariant} [props.variant] - Aura style variant.
|
|
55
|
+
*/
|
|
56
|
+
const Aura =
|
|
57
|
+
({
|
|
58
|
+
as ,
|
|
59
|
+
background ,
|
|
60
|
+
children ,
|
|
61
|
+
className ,
|
|
62
|
+
color ,
|
|
63
|
+
duration ,
|
|
64
|
+
ref ,
|
|
65
|
+
size ,
|
|
66
|
+
style ,
|
|
67
|
+
trigger = ALWAYS ,
|
|
68
|
+
variant ,
|
|
69
|
+
...rest
|
|
70
|
+
}) =>
|
|
71
|
+
{
|
|
72
|
+
const Component = as || 'div' ;
|
|
73
|
+
|
|
74
|
+
// Resolve the light colour class (e.g. 'text-primary') from the constant.
|
|
75
|
+
const colorClass = color ? Object.keys( getTextColor( color ) )[ 0 ] : null ;
|
|
76
|
+
|
|
77
|
+
// In `hover` mode the wrapper colour is transparent at rest and restored on
|
|
78
|
+
// hover, so the aura (built on currentColor) only shows while hovered.
|
|
79
|
+
const colorClasses = trigger === HOVER
|
|
80
|
+
? cn(
|
|
81
|
+
getTextColor( TRANSPARENT ) ,
|
|
82
|
+
colorClass ? `hover:${ colorClass }` : 'hover:text-inherit' ,
|
|
83
|
+
)
|
|
84
|
+
: colorClass ;
|
|
85
|
+
|
|
86
|
+
const classNames = cn
|
|
87
|
+
(
|
|
88
|
+
getAuraClasses({ size , trigger , variant }) ,
|
|
89
|
+
colorClasses ,
|
|
90
|
+
background && getBackgroundColor( background ) ,
|
|
91
|
+
className ,
|
|
92
|
+
) ;
|
|
93
|
+
|
|
94
|
+
// Duration drives the DaisyUI aura animation through the `--tw-duration` CSS
|
|
95
|
+
// variable. Setting it inline is JIT-proof (a `duration-[Nms]` class built at
|
|
96
|
+
// runtime would never be emitted by Tailwind).
|
|
97
|
+
const mergedStyle = duration != null
|
|
98
|
+
? { '--tw-duration' : `${ duration }ms` , ...style }
|
|
99
|
+
: style ;
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<Component className={ classNames } ref={ ref } style={ mergedStyle } { ...rest }>
|
|
103
|
+
{ children }
|
|
104
|
+
</Component>
|
|
105
|
+
) ;
|
|
106
|
+
} ;
|
|
107
|
+
|
|
108
|
+
Aura.displayName = 'Aura' ;
|
|
109
|
+
|
|
110
|
+
export default Aura ;
|
|
@@ -45,6 +45,7 @@ import getTooltipClassNames from '../themes/components/tooltip' ;
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* @param {Object} props
|
|
48
|
+
* @param {import('../themes/components/tooltip').TooltipAlignment} [props.align] - Tooltip alignment ('start' | 'center' | 'end').
|
|
48
49
|
* @param {React.ElementType} [props.as] - Root element type.
|
|
49
50
|
* @param {React.ReactNode} [props.children] - Tooltip trigger content.
|
|
50
51
|
* @param {string} [props.className] - Additional class name.
|
|
@@ -57,6 +58,7 @@ import getTooltipClassNames from '../themes/components/tooltip' ;
|
|
|
57
58
|
*/
|
|
58
59
|
const Tooltip =
|
|
59
60
|
({
|
|
61
|
+
align ,
|
|
60
62
|
as ,
|
|
61
63
|
children ,
|
|
62
64
|
className ,
|
|
@@ -73,7 +75,7 @@ const Tooltip =
|
|
|
73
75
|
|
|
74
76
|
const Component = as || 'div' ;
|
|
75
77
|
|
|
76
|
-
const classNames = getTooltipClassNames({ className , color , open , position }) ;
|
|
78
|
+
const classNames = getTooltipClassNames({ align , className , color , open , position }) ;
|
|
77
79
|
|
|
78
80
|
return (
|
|
79
81
|
<Component
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client' ;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Megamenu component for DaisyUI 5.6.
|
|
5
|
+
*
|
|
6
|
+
* A data-driven megamenu : each item renders a trigger button + a native popover
|
|
7
|
+
* holding its content. Unique HTML ids (`popovertarget` / `popover`) are derived
|
|
8
|
+
* from `useId()`, so no manual id wiring is needed. Anchoring is handled by
|
|
9
|
+
* DaisyUI's CSS (native popover API + CSS anchor positioning).
|
|
10
|
+
*
|
|
11
|
+
* Several megamenus can coexist on a page : basic ones anchor each popover to its
|
|
12
|
+
* own preceding trigger (CSS anchor resolution is DOM-order based), and `wide` /
|
|
13
|
+
* `full` get a unique per-instance anchor name (see below) instead of daisyUI's
|
|
14
|
+
* shared `--megamenu`, so they never collide.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ At most **10** popovers per megamenu (DaisyUI limit). Requires a browser with
|
|
17
|
+
* popover + anchor positioning support (progressive enhancement — no polyfill).
|
|
18
|
+
*
|
|
19
|
+
* @module components/menus/Megamenu
|
|
20
|
+
* @see https://daisyui.com/components/megamenu
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```jsx
|
|
24
|
+
* <Megamenu
|
|
25
|
+
* responsive
|
|
26
|
+
* width="wide"
|
|
27
|
+
* items={[
|
|
28
|
+
* { label: 'Services', links: [ { label: 'Enterprise', href: '#' }, { label: 'Security', href: '#' } ] },
|
|
29
|
+
* { label: 'AI', content: <div className="p-4">Custom content</div> },
|
|
30
|
+
* ]}
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { Fragment, useId } from 'react' ;
|
|
36
|
+
|
|
37
|
+
import cn from '../../themes/helpers/cn' ;
|
|
38
|
+
|
|
39
|
+
import getMegamenuClasses, { FULL, MEGAMENU_ACTIVE, WIDE } from '../../themes/navigation/megamenu' ;
|
|
40
|
+
import getMenuClasses from '../../themes/navigation/menu' ;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* DaisyUI supports at most 10 popovers inside a megamenu.
|
|
44
|
+
* @type {number}
|
|
45
|
+
*/
|
|
46
|
+
export const MAX_POPOVERS = 10 ;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} MegamenuItem
|
|
50
|
+
* @property {string} label - Trigger button label.
|
|
51
|
+
* @property {React.ReactNode} [content] - Popover content (takes precedence over `links`).
|
|
52
|
+
* @property {Array<{ label: string, href?: string }>} [links] - Convenience : renders a `menu` of links.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {Object} props
|
|
57
|
+
* @param {string} [props.className] - Extra classes for the megamenu container.
|
|
58
|
+
* @param {MegamenuItem[]} [props.items=[]] - Menu items (max 10).
|
|
59
|
+
* @param {string} [props.popoverClassName] - Extra classes for each popover panel.
|
|
60
|
+
* @param {React.Ref} [props.ref] - Forwarded ref on the container.
|
|
61
|
+
* @param {boolean} [props.responsive=false] - Add a mobile trigger button + vertical layout on small screens.
|
|
62
|
+
* @param {import('../../themes/navigation/megamenu').MegamenuSize} [props.size] - Megamenu size.
|
|
63
|
+
* @param {string} [props.triggerClassName] - Extra classes for the mobile trigger button.
|
|
64
|
+
* @param {string} [props.triggerLabel='Menu'] - Mobile trigger button label.
|
|
65
|
+
* @param {boolean} [props.vertical=false] - Always vertical layout.
|
|
66
|
+
* @param {import('../../themes/navigation/megamenu').MegamenuWidth} [props.width] - Width modifier ('wide' | 'full').
|
|
67
|
+
*/
|
|
68
|
+
const Megamenu =
|
|
69
|
+
({
|
|
70
|
+
className ,
|
|
71
|
+
items = [] ,
|
|
72
|
+
popoverClassName ,
|
|
73
|
+
ref ,
|
|
74
|
+
responsive = false ,
|
|
75
|
+
size ,
|
|
76
|
+
style ,
|
|
77
|
+
triggerClassName ,
|
|
78
|
+
triggerLabel = 'Menu' ,
|
|
79
|
+
vertical = false ,
|
|
80
|
+
width ,
|
|
81
|
+
...rest
|
|
82
|
+
}) =>
|
|
83
|
+
{
|
|
84
|
+
const uid = useId() ;
|
|
85
|
+
|
|
86
|
+
// useId() can contain ':' — strip to a clean base usable as an HTML id.
|
|
87
|
+
const base = `megamenu-${ uid.replace( /[^a-z0-9]/gi , '' ) }` ;
|
|
88
|
+
const containerId = base ;
|
|
89
|
+
|
|
90
|
+
// wide / full anchor every popover to the container via a shared CSS anchor.
|
|
91
|
+
// DaisyUI hard-codes `--megamenu`, which collides when several coexist ; we
|
|
92
|
+
// give each instance a unique anchor name (inline style, so JIT-proof) — this
|
|
93
|
+
// mirrors daisyUI's manual `[anchor-name:--megamenu-x]` pattern automatically.
|
|
94
|
+
const anchored = width === WIDE || width === FULL ;
|
|
95
|
+
const anchorName = `--${ base }` ;
|
|
96
|
+
|
|
97
|
+
const list = Array.isArray( items ) ? items.slice( 0 , MAX_POPOVERS ) : [] ;
|
|
98
|
+
|
|
99
|
+
if ( process.env.NODE_ENV !== 'production' && Array.isArray( items ) && items.length > MAX_POPOVERS )
|
|
100
|
+
{
|
|
101
|
+
console.warn( `[Megamenu] DaisyUI supports at most ${ MAX_POPOVERS } popovers ; ${ items.length } provided, extra items are ignored.` ) ;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const classes = getMegamenuClasses({ className , responsive , size , vertical , width }) ;
|
|
105
|
+
|
|
106
|
+
// The container is only a popover when responsive (the mobile trigger toggles it).
|
|
107
|
+
const containerPopover = responsive ? { id : containerId , popover : 'auto' } : {} ;
|
|
108
|
+
|
|
109
|
+
const containerStyle = anchored ? { anchorName , ...style } : style ;
|
|
110
|
+
const popoverStyle = anchored ? { positionAnchor : anchorName } : undefined ;
|
|
111
|
+
|
|
112
|
+
const renderContent = ( item ) =>
|
|
113
|
+
{
|
|
114
|
+
if ( item?.content )
|
|
115
|
+
{
|
|
116
|
+
return item.content ;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if ( Array.isArray( item?.links ) )
|
|
120
|
+
{
|
|
121
|
+
return (
|
|
122
|
+
<ul className={ getMenuClasses() }>
|
|
123
|
+
{ item.links.map( ( link , i ) => (
|
|
124
|
+
<li key={ i }>
|
|
125
|
+
<a href={ link?.href }>{ link?.label }</a>
|
|
126
|
+
</li>
|
|
127
|
+
) ) }
|
|
128
|
+
</ul>
|
|
129
|
+
) ;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return null ;
|
|
133
|
+
} ;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
{ responsive && (
|
|
138
|
+
<button className={ cn( 'btn sm:hidden' , triggerClassName ) } popoverTarget={ containerId }>
|
|
139
|
+
{ triggerLabel }
|
|
140
|
+
</button>
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
<div className={ classes } ref={ ref } style={ containerStyle } { ...containerPopover } { ...rest }>
|
|
144
|
+
<span className={ MEGAMENU_ACTIVE } />
|
|
145
|
+
|
|
146
|
+
{ list.map( ( item , index ) =>
|
|
147
|
+
{
|
|
148
|
+
const popoverId = `${ base }-p${ index }` ;
|
|
149
|
+
return (
|
|
150
|
+
<Fragment key={ popoverId }>
|
|
151
|
+
<button popoverTarget={ popoverId }>{ item?.label }</button>
|
|
152
|
+
<div id={ popoverId } popover="auto" className={ popoverClassName } style={ popoverStyle }>
|
|
153
|
+
{ renderContent( item ) }
|
|
154
|
+
</div>
|
|
155
|
+
</Fragment>
|
|
156
|
+
) ;
|
|
157
|
+
} )}
|
|
158
|
+
</div>
|
|
159
|
+
</>
|
|
160
|
+
) ;
|
|
161
|
+
} ;
|
|
162
|
+
|
|
163
|
+
Megamenu.displayName = 'Megamenu' ;
|
|
164
|
+
|
|
165
|
+
export default Megamenu ;
|
|
@@ -13,6 +13,8 @@ const Range =
|
|
|
13
13
|
error,
|
|
14
14
|
color,
|
|
15
15
|
size = 'md',
|
|
16
|
+
orientation = 'horizontal',
|
|
17
|
+
height = 'h-64',
|
|
16
18
|
min = 0,
|
|
17
19
|
max = 100,
|
|
18
20
|
step = 1,
|
|
@@ -33,6 +35,8 @@ const Range =
|
|
|
33
35
|
...rangeProps
|
|
34
36
|
}) =>
|
|
35
37
|
{
|
|
38
|
+
const isVertical = orientation === 'vertical' ;
|
|
39
|
+
|
|
36
40
|
const isControlled = controlledValue !== undefined ;
|
|
37
41
|
const [ internalValue, setInternalValue ] = useState( defaultValue ?? min ) ;
|
|
38
42
|
|
|
@@ -54,12 +58,13 @@ const Range =
|
|
|
54
58
|
|
|
55
59
|
const rangeClasses = getRangeClasses({
|
|
56
60
|
color: hasError ? 'error' : color,
|
|
61
|
+
orientation,
|
|
57
62
|
size,
|
|
58
63
|
className: rangeClassName,
|
|
59
64
|
}) ;
|
|
60
65
|
|
|
61
|
-
// Generate markers
|
|
62
|
-
const markers = showMarkers ? (() =>
|
|
66
|
+
// Generate markers (horizontal only — vertical markers are not supported yet)
|
|
67
|
+
const markers = ( showMarkers && !isVertical ) ? (() =>
|
|
63
68
|
{
|
|
64
69
|
const count = Math.floor(( max - min ) / step ) + 1 ;
|
|
65
70
|
const items = [] ;
|
|
@@ -74,8 +79,8 @@ const Range =
|
|
|
74
79
|
return items ;
|
|
75
80
|
})() : null ;
|
|
76
81
|
|
|
77
|
-
// Generate marker labels
|
|
78
|
-
const labels = markerLabels ? markerLabels.map(( label, i ) => (
|
|
82
|
+
// Generate marker labels (horizontal only)
|
|
83
|
+
const labels = ( markerLabels && !isVertical ) ? markerLabels.map(( label, i ) => (
|
|
79
84
|
<span key={ i } className="text-xs">{ label }</span>
|
|
80
85
|
)) : null ;
|
|
81
86
|
|
|
@@ -119,12 +124,12 @@ const Range =
|
|
|
119
124
|
</span>
|
|
120
125
|
)}
|
|
121
126
|
|
|
122
|
-
<div className=
|
|
127
|
+
<div className={ cn( isVertical ? height : 'flex-1 max-w-full' ) }>
|
|
123
128
|
<input
|
|
124
129
|
type="range"
|
|
125
130
|
id={ id }
|
|
126
131
|
name={ name }
|
|
127
|
-
className={ cn( rangeClasses, 'w-full' ) }
|
|
132
|
+
className={ cn( rangeClasses, !isVertical && 'w-full' ) }
|
|
128
133
|
min={ min }
|
|
129
134
|
max={ max }
|
|
130
135
|
step={ step }
|
|
@@ -41,7 +41,7 @@ import cn from '../../themes/helpers/cn' ;
|
|
|
41
41
|
* @param {number} [props.defaultValue] - Default value for uncontrolled mode
|
|
42
42
|
* @param {Function} [props.onChange] - Change handler (value) => void
|
|
43
43
|
* @param {number} [props.count=5] - Number of stars/items
|
|
44
|
-
* @param {import('../../themes/components/rating').RatingSize} [props.size='md'] - Size: 'xs'
|
|
44
|
+
* @param {import('../../themes/components/rating').RatingSize | import('../../themes/components/rating').ResponsiveRatingSize} [props.size='md'] - Size: scalar ('xs'…'xl') or responsive object (e.g. `{ xs: 'sm', md: 'lg' }`)
|
|
45
45
|
* @param {import('../../themes/components/mask').MaskShape} [props.shape='star-2'] - Mask shape
|
|
46
46
|
* @param {import('../../themes/colors/backgroundColor').BackgroundColorValue} [props.color] - Background color
|
|
47
47
|
* @param {boolean} [props.half=false] - Enable half-star ratings
|
|
@@ -17,6 +17,9 @@ import { createContext } from 'react' ;
|
|
|
17
17
|
* priority chain: persisted → auto(pathname) → item.defaultOpen → defaultMode.
|
|
18
18
|
* @property {string | null} pathname - Current pathname, captured by the
|
|
19
19
|
* provider so consumers (e.g. `Collapse`) don't have to read it again.
|
|
20
|
+
* @property {string | null} activePath - Longest LINK path matching the
|
|
21
|
+
* current route (the single "winning" link). `null` when nothing
|
|
22
|
+
* matches. Consumed by `Link` to decide its active state.
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* @module contexts/navigation/helpers/containsActivePath
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import notEmpty
|
|
10
|
-
|
|
9
|
+
import notEmpty from 'vegas-js-core/src/strings/notEmpty' ;
|
|
10
|
+
|
|
11
|
+
import isPathMatch from './isPathMatch' ;
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Walks the `items` tree under the given navigation node and returns
|
|
@@ -38,7 +39,7 @@ const containsActivePath = ( item , pathname ) =>
|
|
|
38
39
|
return false ;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
if ( notEmpty( item.path ) &&
|
|
42
|
+
if ( notEmpty( item.path ) && isPathMatch( pathname , item.path ) )
|
|
42
43
|
{
|
|
43
44
|
return true ;
|
|
44
45
|
}
|