oihana-next-ui 0.2.3 → 0.2.4

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.4",
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";
@@ -91,6 +93,7 @@ const navigation =
91
93
  [
92
94
  { id : 'avatars' , type : LINK , Icon : AvatarIcon , path : '/lab/avatars' } ,
93
95
  { id : 'badges' , type : LINK , Icon : BadgeIcon , path : '/lab/badges' } ,
96
+ { id : 'effects' , type : LINK , Icon : EffectIcon , path : '/lab/effects' } ,
94
97
  { id : 'images' , type : LINK , Icon : ImageIcon , path : '/lab/images' } ,
95
98
  { id : 'lists' , type : LINK , Icon : ListIcon , path : '/lab/lists' } ,
96
99
  { id : 'masks' , type : LINK , Icon : MaskIcon , path : '/lab/masks' } ,
@@ -110,6 +113,7 @@ const navigation =
110
113
  [
111
114
  { id : 'pagination' , type : LINK , Icon : PaginationIcon , path : '/lab/pagination' } ,
112
115
  { id : 'menus' , type : LINK , Icon : MenuIcon , path : '/lab/menus' } ,
116
+ { id : 'megamenu' , type : LINK , Icon : MegamenuIcon , path : '/lab/megamenu' } ,
113
117
  ]
114
118
  } ,
115
119
  {
@@ -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' ,
@@ -45,6 +46,7 @@ const navigation =
45
46
  navigation : 'Navigation' ,
46
47
  lists : 'Listes' ,
47
48
  menus : 'Menus' ,
49
+ megamenu : 'Megamenu' ,
48
50
  pagination : 'Pagination' ,
49
51
  tests : 'Tests' ,
50
52
 
@@ -59,6 +61,7 @@ const navigation =
59
61
  display : 'Display' ,
60
62
  avatars : 'Avatars' ,
61
63
  badges : 'Badges' ,
64
+ effects : 'Effects' ,
62
65
  headers : 'Headers' ,
63
66
  images : 'Images' ,
64
67
  markdown : 'Markdown' ,
@@ -94,6 +97,7 @@ const navigation =
94
97
  navigation : 'Navigation' ,
95
98
  lists : 'Lists' ,
96
99
  menus : 'Menus' ,
100
+ megamenu : 'Megamenu' ,
97
101
  pagination : 'Pagination' ,
98
102
  tests : 'Tests' ,
99
103
 
@@ -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,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 ;
@@ -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 ;
@@ -0,0 +1,125 @@
1
+ 'use client' ;
2
+
3
+ import Container from '@/display/Container' ;
4
+ import Divider from '@/components/Divider' ;
5
+ import Aura from '@/components/Aura' ;
6
+
7
+ /**
8
+ * Aura showcase — variants, sizes, custom colours, hover trigger and duration.
9
+ */
10
+ const AuraDemo = () =>
11
+ {
12
+ const variants = [ 'dual' , 'rainbow' , 'holo' , 'gold' , 'silver' , 'glow' ] ;
13
+ const auraSizes = [ 'xs' , 'sm' , 'md' , 'lg' , 'xl' ] ;
14
+
15
+ return (
16
+ <Container className="flex flex-col gap-8 bg-base-200/60 p-8 rounded-box" maxWidth="max-w-7xl">
17
+
18
+ <h2 className="text-3xl font-bold">Aura Examples</h2>
19
+
20
+ {/* Default */}
21
+ <div className="flex flex-col gap-4">
22
+ <h3 className="text-xl font-semibold">Default</h3>
23
+ <div className="flex flex-wrap items-center gap-8">
24
+ <Aura>
25
+ <div className="card bg-base-100">
26
+ <div className="card-body"><p>This card has aura</p></div>
27
+ </div>
28
+ </Aura>
29
+ <Aura>
30
+ <button className="btn">button with aura</button>
31
+ </Aura>
32
+ </div>
33
+ </div>
34
+
35
+ <Divider />
36
+
37
+ {/* Variants */}
38
+ <div className="flex flex-col gap-4">
39
+ <h3 className="text-xl font-semibold">Variants</h3>
40
+ <div className="flex flex-wrap items-center gap-8">
41
+ { variants.map( ( variant ) => (
42
+ <Aura key={ variant } variant={ variant }>
43
+ <div className="card bg-base-100">
44
+ <div className="card-body"><p>{ variant }</p></div>
45
+ </div>
46
+ </Aura>
47
+ ) ) }
48
+ </div>
49
+ </div>
50
+
51
+ <Divider />
52
+
53
+ {/* Custom colour */}
54
+ <div className="flex flex-col gap-4">
55
+ <h3 className="text-xl font-semibold">Custom colour & background</h3>
56
+ <div className="flex flex-wrap items-center gap-8">
57
+ <Aura color="warning">
58
+ <div className="card bg-base-100 text-base-content">
59
+ <div className="card-body"><p>Custom colour</p></div>
60
+ </div>
61
+ </Aura>
62
+ <Aura color="primary" background="primary">
63
+ <div className="card bg-base-100 text-base-content">
64
+ <div className="card-body"><p>Colour + background</p></div>
65
+ </div>
66
+ </Aura>
67
+ </div>
68
+ </div>
69
+
70
+ <Divider />
71
+
72
+ {/* Hover trigger */}
73
+ <div className="flex flex-col gap-4">
74
+ <h3 className="text-xl font-semibold">Trigger: hover</h3>
75
+ <p className="text-sm text-base-content/70">The aura only lights up while hovering.</p>
76
+ <div className="flex flex-wrap items-center gap-8">
77
+ <Aura trigger="hover" variant="rainbow">
78
+ <div className="card bg-base-100 text-base-content">
79
+ <div className="card-body"><p>Hover me (rainbow)</p></div>
80
+ </div>
81
+ </Aura>
82
+ <Aura trigger="hover" color="primary">
83
+ <button className="btn">Hover me</button>
84
+ </Aura>
85
+ </div>
86
+ </div>
87
+
88
+ <Divider />
89
+
90
+ {/* Sizes */}
91
+ <div className="flex flex-col gap-4">
92
+ <h3 className="text-xl font-semibold">Sizes</h3>
93
+ <div className="flex flex-wrap items-center gap-6">
94
+ { auraSizes.map( ( size ) => (
95
+ <Aura key={ size } size={ size }>
96
+ <button className="btn">{ size.toUpperCase() }</button>
97
+ </Aura>
98
+ ) ) }
99
+ </div>
100
+ </div>
101
+
102
+ <Divider />
103
+
104
+ {/* Custom duration */}
105
+ <div className="flex flex-col gap-4">
106
+ <h3 className="text-xl font-semibold">Custom duration</h3>
107
+ <div className="flex flex-wrap items-center gap-8">
108
+ <Aura variant="rainbow" duration={ 2000 }>
109
+ <div className="card bg-base-100">
110
+ <div className="card-body"><p>2000ms duration</p></div>
111
+ </div>
112
+ </Aura>
113
+ <Aura variant="rainbow" duration={ 10000 }>
114
+ <div className="card bg-base-100">
115
+ <div className="card-body"><p>10000ms duration</p></div>
116
+ </div>
117
+ </Aura>
118
+ </div>
119
+ </div>
120
+
121
+ </Container>
122
+ ) ;
123
+ } ;
124
+
125
+ export default AuraDemo ;
@@ -0,0 +1,125 @@
1
+ 'use client' ;
2
+
3
+ import Container from '@/display/Container' ;
4
+ import Divider from '@/components/Divider' ;
5
+ import Megamenu from '@/components/menus/Megamenu' ;
6
+
7
+ /**
8
+ * Megamenu showcase.
9
+ *
10
+ * Several megamenus coexist on this page : basic ones anchor each popover to its
11
+ * own trigger (DOM-order anchor resolution), and wide/full get a unique anchor
12
+ * name per instance so they never collide.
13
+ */
14
+ const MegamenuDemo = () =>
15
+ {
16
+ const items =
17
+ [
18
+ {
19
+ label : 'Services' ,
20
+ links :
21
+ [
22
+ { label : 'Enterprise' , href : '#' } ,
23
+ { label : 'CRM software' , href : '#' } ,
24
+ { label : 'Security' , href : '#' } ,
25
+ { label : 'Consulting' , href : '#' } ,
26
+ ] ,
27
+ } ,
28
+ {
29
+ label : 'AI' ,
30
+ links :
31
+ [
32
+ { label : 'AI infrastructure' , href : '#' } ,
33
+ { label : 'Image generation' , href : '#' } ,
34
+ { label : 'MCP servers' , href : '#' } ,
35
+ ] ,
36
+ } ,
37
+ {
38
+ label : 'Cloud Solutions' ,
39
+ links :
40
+ [
41
+ { label : 'Cloud computing' , href : '#' } ,
42
+ { label : 'Storage solutions' , href : '#' } ,
43
+ { label : 'Database services' , href : '#' } ,
44
+ { label : 'CDN performance' , href : '#' } ,
45
+ ] ,
46
+ } ,
47
+ ] ;
48
+
49
+ const plainItems =
50
+ [
51
+ { label : 'One' , content : <div className="p-4">Content for the first item</div> } ,
52
+ { label : 'Two' , content : <div className="p-4">Content for the second item</div> } ,
53
+ { label : 'Three' , content : <div className="p-4">Content for the third item</div> } ,
54
+ ] ;
55
+
56
+ return (
57
+ <Container className="flex flex-col gap-8 bg-base-200/60 p-8 rounded-box" maxWidth="max-w-7xl">
58
+
59
+ <h2 className="text-3xl font-bold">Megamenu</h2>
60
+ <p className="text-sm text-base-content/70">
61
+ Built on the native popover API + CSS anchor positioning (recent browsers).
62
+ Multiple megamenus coexist on this page.
63
+ </p>
64
+
65
+ {/* Responsive small menus */}
66
+ <div className="flex flex-col gap-4">
67
+ <h3 className="text-xl font-semibold">Responsive small menus</h3>
68
+ <Megamenu
69
+ responsive
70
+ items = { items }
71
+ className = "w-full p-2 border border-base-300"
72
+ />
73
+ </div>
74
+
75
+ <Divider />
76
+
77
+ {/* Wide */}
78
+ <div className="flex flex-col gap-4">
79
+ <h3 className="text-xl font-semibold">Wide popovers</h3>
80
+ <Megamenu
81
+ responsive
82
+ width = "wide"
83
+ items = { items }
84
+ className = "w-full p-2 border border-base-300"
85
+ />
86
+ </div>
87
+
88
+ <Divider />
89
+
90
+ {/* Full width inside a navbar */}
91
+ <div className="flex flex-col gap-4">
92
+ <h3 className="text-xl font-semibold">Full width inside a navbar</h3>
93
+ <div className="navbar bg-base-100 shadow-sm">
94
+ <div className="navbar-start">
95
+ <a className="btn btn-ghost text-xl">daisyUI</a>
96
+ </div>
97
+ <div className="navbar-center">
98
+ <Megamenu responsive width="full" items={ items } />
99
+ </div>
100
+ <div className="navbar-end">
101
+ <button className="btn">Login</button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <Divider />
107
+
108
+ {/* Sizes — five basic megamenus coexisting */}
109
+ <div className="flex flex-col gap-4">
110
+ <h3 className="text-xl font-semibold">Sizes</h3>
111
+ { [ 'xs' , 'sm' , 'md' , 'lg' , 'xl' ].map( ( size ) => (
112
+ <Megamenu
113
+ key={ size }
114
+ size={ size }
115
+ items={ plainItems }
116
+ className="w-full p-2 border border-base-300"
117
+ />
118
+ ) ) }
119
+ </div>
120
+
121
+ </Container>
122
+ ) ;
123
+ } ;
124
+
125
+ export default MegamenuDemo ;
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Aura class name generator for DaisyUI 5.6.
3
+ *
4
+ * Aura is a border light effect that wraps around any component. The effect is
5
+ * always animating by default ; pass `trigger: 'hover'` to only light it up on
6
+ * hover (the gate is built on top of the DaisyUI classes, see the component).
7
+ *
8
+ * @module themes/effects/aura
9
+ * @see https://daisyui.com/components/aura
10
+ */
11
+
12
+ import cn from '../helpers/cn' ;
13
+
14
+ import { LG, MD, SM, XL, XS } from '../sizing/sizes' ;
15
+
16
+ // Variants (styles)
17
+
18
+ /**
19
+ * @typedef {'dual' | 'rainbow' | 'holo' | 'gold' | 'silver' | 'glow'} AuraVariant
20
+ */
21
+
22
+ export const DUAL = 'dual' ;
23
+ export const RAINBOW = 'rainbow' ;
24
+ export const HOLO = 'holo' ;
25
+ export const GOLD = 'gold' ;
26
+ export const SILVER = 'silver' ;
27
+ export const GLOW = 'glow' ;
28
+
29
+ /**
30
+ * Valid aura variants.
31
+ * @type {AuraVariant[]}
32
+ */
33
+ export const variants = [ DUAL, RAINBOW, HOLO, GOLD, SILVER, GLOW ] ;
34
+
35
+ const variantMap =
36
+ {
37
+ [ DUAL ] : 'aura-dual',
38
+ [ RAINBOW ] : 'aura-rainbow',
39
+ [ HOLO ] : 'aura-holo',
40
+ [ GOLD ] : 'aura-gold',
41
+ [ SILVER ] : 'aura-silver',
42
+ [ GLOW ] : 'aura-glow',
43
+ } ;
44
+
45
+ // Sizes
46
+
47
+ /**
48
+ * @typedef {'xs' | 'sm' | 'md' | 'lg' | 'xl'} AuraSize
49
+ */
50
+
51
+ /**
52
+ * Valid aura sizes.
53
+ * @type {AuraSize[]}
54
+ */
55
+ export const sizes = [ XS, SM, MD, LG, XL ] ;
56
+
57
+ const sizeMap =
58
+ {
59
+ [ XS ] : 'aura-xs',
60
+ [ SM ] : 'aura-sm',
61
+ [ MD ] : 'aura-md',
62
+ [ LG ] : 'aura-lg',
63
+ [ XL ] : 'aura-xl',
64
+ } ;
65
+
66
+ // Triggers
67
+
68
+ /**
69
+ * @typedef {'always' | 'hover'} AuraTrigger
70
+ */
71
+
72
+ export const ALWAYS = 'always' ;
73
+ export const HOVER = 'hover' ;
74
+
75
+ /**
76
+ * Valid aura triggers.
77
+ * @type {AuraTrigger[]}
78
+ */
79
+ export const triggers = [ ALWAYS, HOVER ] ;
80
+
81
+ export const AURA = 'aura' ;
82
+
83
+ /**
84
+ * Pauses the rotation animation (rest state of the `hover` trigger).
85
+ * @type {string}
86
+ */
87
+ export const AURA_PAUSED = '[animation-play-state:paused]' ;
88
+
89
+ /**
90
+ * Resumes the rotation animation on hover (`hover` trigger).
91
+ * @type {string}
92
+ */
93
+ export const AURA_RUNNING = 'hover:[animation-play-state:running]' ;
94
+
95
+ /**
96
+ * Generates aura class names for DaisyUI 5.6.
97
+ *
98
+ * Colour (`text-*`) and background (`bg-*`) tints are NOT handled here — they
99
+ * are resolved from the colour constants by the `<Aura>` component, because the
100
+ * `hover` trigger needs to gate the colour on `currentColor`.
101
+ *
102
+ * The animation duration is NOT a class : it is driven by the `--tw-duration`
103
+ * CSS variable, applied as an inline style by the `<Aura>` component (a dynamic
104
+ * `duration-[Nms]` class would never be generated by Tailwind's JIT scanner).
105
+ *
106
+ * @param {Object} [props]
107
+ * @param {Object} [props.after] - Class overrides applied after.
108
+ * @param {Object} [props.before] - Class definitions applied before.
109
+ * @param {string} [props.beforeClassName] - CSS string prepended.
110
+ * @param {string} [props.className] - CSS string appended.
111
+ * @param {AuraSize} [props.size='md'] - Aura size (xs, sm, md, lg, xl).
112
+ * @param {AuraTrigger} [props.trigger='always'] - 'always' (default) or 'hover'.
113
+ * @param {AuraVariant} [props.variant] - Aura style variant.
114
+ *
115
+ * @returns {string} Combined class names.
116
+ *
117
+ * @example
118
+ * ```js
119
+ * getAuraClasses({ variant: 'rainbow', size: 'lg' }) ;
120
+ * // → 'aura aura-rainbow aura-lg'
121
+ *
122
+ * getAuraClasses({ trigger: 'hover' }) ;
123
+ * // → 'aura aura-md [animation-play-state:paused] hover:[animation-play-state:running]'
124
+ * ```
125
+ */
126
+ export const getAuraClasses =
127
+ ({
128
+ after,
129
+ before,
130
+ beforeClassName,
131
+ className,
132
+ size = MD,
133
+ trigger = ALWAYS,
134
+ variant,
135
+ }
136
+ = {} ) => cn
137
+ (
138
+ beforeClassName,
139
+ {
140
+ ...before,
141
+
142
+ [ AURA ] : true,
143
+
144
+ ...!!variantMap[variant] && { [ variantMap[variant] ] : true },
145
+ ...!!sizeMap[size] && { [ sizeMap[size] ] : true },
146
+ ...trigger === HOVER && { [ AURA_PAUSED ] : true, [ AURA_RUNNING ] : true },
147
+
148
+ ...after,
149
+ },
150
+ className,
151
+ ) ;
152
+
153
+ export default getAuraClasses ;
154
+
155
+ /* Tailwind CSS safe list
156
+ `trigger="hover"` restores the light colour on hover via `hover:text-*`, which
157
+ is built at runtime — list the supported colours here so the JIT emits them.
158
+ | hover:text-primary | hover:text-secondary | hover:text-accent |
159
+ | hover:text-neutral | hover:text-info | hover:text-success |
160
+ | hover:text-warning | hover:text-error | hover:text-inherit |
161
+ | hover:text-base-content |
162
+ */
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Megamenu class name generator for DaisyUI 5.6.
3
+ *
4
+ * Megamenu is a large horizontal menu where each item opens a popover. It is
5
+ * built on the native HTML popover API and CSS anchor positioning, so the
6
+ * anchoring is handled entirely by DaisyUI's CSS — this generator only composes
7
+ * the class modifiers (width, size, vertical).
8
+ *
9
+ * @module themes/navigation/megamenu
10
+ * @see https://daisyui.com/components/megamenu
11
+ *
12
+ * @safelist
13
+ * ## Sizes
14
+ * - megamenu-xs | megamenu-sm | megamenu-md | megamenu-lg | megamenu-xl
15
+ * ## Width
16
+ * - megamenu-wide | megamenu-full
17
+ * ## Direction
18
+ * - megamenu-vertical | max-sm:megamenu-vertical
19
+ */
20
+
21
+ import cn from '../helpers/cn' ;
22
+
23
+ import { LG, MD, SM, XL, XS } from '../sizing/sizes' ;
24
+
25
+ export const MEGAMENU = 'megamenu' ;
26
+ export const MEGAMENU_ACTIVE = 'megamenu-active' ;
27
+
28
+ // Width
29
+
30
+ /**
31
+ * @typedef {'wide' | 'full'} MegamenuWidth
32
+ */
33
+
34
+ export const WIDE = 'wide' ;
35
+ export const FULL = 'full' ;
36
+
37
+ /**
38
+ * Valid megamenu width modifiers.
39
+ * @type {MegamenuWidth[]}
40
+ */
41
+ export const widths = [ WIDE, FULL ] ;
42
+
43
+ const widthMap =
44
+ {
45
+ [ WIDE ] : 'megamenu-wide',
46
+ [ FULL ] : 'megamenu-full',
47
+ } ;
48
+
49
+ // Sizes
50
+
51
+ /**
52
+ * @typedef {'xs' | 'sm' | 'md' | 'lg' | 'xl'} MegamenuSize
53
+ */
54
+
55
+ /**
56
+ * Valid megamenu sizes.
57
+ * @type {MegamenuSize[]}
58
+ */
59
+ export const sizes = [ XS, SM, MD, LG, XL ] ;
60
+
61
+ const sizeMap =
62
+ {
63
+ [ XS ] : 'megamenu-xs',
64
+ [ SM ] : 'megamenu-sm',
65
+ [ MD ] : 'megamenu-md',
66
+ [ LG ] : 'megamenu-lg',
67
+ [ XL ] : 'megamenu-xl',
68
+ } ;
69
+
70
+ // Direction
71
+
72
+ export const MEGAMENU_VERTICAL = 'megamenu-vertical' ;
73
+ export const MEGAMENU_VERTICAL_MAX_SM = 'max-sm:megamenu-vertical' ;
74
+
75
+ /**
76
+ * Generates megamenu container class names for DaisyUI 5.6.
77
+ *
78
+ * @param {Object} [props]
79
+ * @param {Object} [props.after] - Class overrides applied after.
80
+ * @param {Object} [props.before] - Class definitions applied before.
81
+ * @param {string} [props.beforeClassName] - CSS string prepended.
82
+ * @param {string} [props.className] - CSS string appended.
83
+ * @param {boolean} [props.responsive=false] - Show vertically on small screens (`max-sm:megamenu-vertical`).
84
+ * @param {MegamenuSize} [props.size='md'] - Megamenu size (xs, sm, md, lg, xl).
85
+ * @param {boolean} [props.vertical=false] - Always vertical (`megamenu-vertical`).
86
+ * @param {MegamenuWidth} [props.width] - Width modifier ('wide' | 'full').
87
+ *
88
+ * @returns {string} Combined class names.
89
+ *
90
+ * @example
91
+ * ```js
92
+ * getMegamenuClasses({ width: 'wide', size: 'lg' }) ;
93
+ * // → 'megamenu megamenu-wide megamenu-lg'
94
+ *
95
+ * getMegamenuClasses({ responsive: true }) ;
96
+ * // → 'megamenu megamenu-md max-sm:megamenu-vertical'
97
+ * ```
98
+ */
99
+ export const getMegamenuClasses =
100
+ ({
101
+ after,
102
+ before,
103
+ beforeClassName,
104
+ className,
105
+ responsive = false,
106
+ size = MD,
107
+ vertical = false,
108
+ width,
109
+ }
110
+ = {} ) => cn
111
+ (
112
+ beforeClassName,
113
+ MEGAMENU,
114
+ {
115
+ ...before,
116
+
117
+ ...!!widthMap[width] && { [ widthMap[width] ] : true },
118
+ ...!!sizeMap[size] && { [ sizeMap[size] ] : true },
119
+
120
+ [ MEGAMENU_VERTICAL ] : vertical === true,
121
+ [ MEGAMENU_VERTICAL_MAX_SM ] : responsive === true,
122
+
123
+ ...after,
124
+ },
125
+ className,
126
+ ) ;
127
+
128
+ export default getMegamenuClasses ;
package/src/version.js CHANGED
@@ -1,3 +1,3 @@
1
- const version = "0.2.3" ;
1
+ const version = "0.2.4" ;
2
2
 
3
3
  export default version ;