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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oihana-next-ui",
3
- "version": "0.2.3",
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.2.2",
61
- "@maskito/kit": "^5.2.2",
62
- "@maskito/react": "^5.2.2",
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.20",
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.38.0",
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.5",
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.0",
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.3",
84
- "tailwind-merge": "^3.5.0",
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.2.4",
91
- "@tailwindcss/typography": "^0.5.19",
92
- "@types/node": "^25.6.0",
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.5.19",
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.2.4",
101
- "tailwindcss-animated": "^2.0.0",
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 ;
@@ -75,7 +75,7 @@ const MenuLink =
75
75
  <Tooltip
76
76
  className={ tooltipClassName }
77
77
  color={ tooltipColor }
78
- label={ tooltip }
78
+ tip={ tooltip }
79
79
  position={ tooltipPosition }
80
80
  show={ showTooltip && !disabled }
81
81
  >
@@ -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="flex-1 max-w-full">
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', 'sm', 'md', 'lg', 'xl'
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 from 'vegas-js-core/src/strings/notEmpty' ;
10
- import startsWith from 'vegas-js-core/src/strings/startsWith' ;
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 ) && startsWith( pathname , item.path ) )
42
+ if ( notEmpty( item.path ) && isPathMatch( pathname , item.path ) )
42
43
  {
43
44
  return true ;
44
45
  }