@shohojdhara/atomix 0.4.4 → 0.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/atomix.css +50 -11
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +1 -1
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.js +184 -189
  6. package/dist/charts.js.map +1 -1
  7. package/dist/core.d.ts +4 -4
  8. package/dist/core.js +194 -199
  9. package/dist/core.js.map +1 -1
  10. package/dist/forms.js +184 -189
  11. package/dist/forms.js.map +1 -1
  12. package/dist/heavy.js +189 -194
  13. package/dist/heavy.js.map +1 -1
  14. package/dist/index.d.ts +44 -47
  15. package/dist/index.esm.js +496 -613
  16. package/dist/index.esm.js.map +1 -1
  17. package/dist/index.js +528 -631
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.min.js +1 -1
  20. package/dist/index.min.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/AtomixGlass/AtomixGlass.tsx +60 -39
  23. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +8 -42
  24. package/src/components/AtomixGlass/glass-utils.ts +27 -14
  25. package/src/components/AtomixGlass/stories/Overview.stories.tsx +19 -21
  26. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1162 -515
  27. package/src/components/AtomixGlass/stories/shared-components.tsx +11 -3
  28. package/src/components/Breadcrumb/Breadcrumb.tsx +5 -5
  29. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +2 -2
  30. package/src/components/Button/Button.tsx +6 -6
  31. package/src/components/Card/Card.tsx +3 -3
  32. package/src/components/Dropdown/Dropdown.tsx +5 -3
  33. package/src/components/Footer/Footer.tsx +124 -166
  34. package/src/components/Footer/FooterLink.tsx +16 -19
  35. package/src/components/Footer/FooterSection.tsx +40 -39
  36. package/src/components/Footer/FooterSocialLink.tsx +59 -58
  37. package/src/components/Footer/README.md +1 -1
  38. package/src/components/Hero/Hero.tsx +72 -142
  39. package/src/components/Navigation/Menu/MegaMenu.tsx +17 -12
  40. package/src/components/Navigation/Menu/Menu.tsx +49 -24
  41. package/src/components/Navigation/Nav/NavItem.tsx +5 -3
  42. package/src/components/Navigation/Navbar/Navbar.tsx +13 -5
  43. package/src/components/Navigation/SideMenu/SideMenu.tsx +2 -2
  44. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -4
  45. package/src/components/Slider/Slider.tsx +7 -4
  46. package/src/lib/composables/index.ts +1 -2
  47. package/src/lib/composables/useAtomixGlass.ts +246 -222
  48. package/src/lib/composables/useAtomixGlassStyles.ts +46 -23
  49. package/src/lib/composables/useFooter.ts +117 -20
  50. package/src/lib/composables/useSlider.ts +3 -1
  51. package/src/lib/constants/components.ts +3 -1
  52. package/src/lib/types/components.ts +44 -12
  53. package/src/styles/06-components/_components.atomix-glass.scss +72 -14
  54. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +0 -222
  55. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +0 -329
  56. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +0 -82
  57. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +0 -153
  58. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +0 -198
  59. package/src/lib/composables/atomix-glass/useGlassState.ts +0 -112
  60. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +0 -160
  61. package/src/lib/composables/glass-styles.ts +0 -302
  62. package/src/lib/composables/useGlassContainer.ts +0 -177
@@ -1,7 +1,6 @@
1
- import React, { forwardRef } from 'react';
1
+ import React, { forwardRef, useState } from 'react';
2
2
  import { FooterSectionProps } from '../../lib/types/components';
3
3
  import { GridCol } from '../../layouts';
4
- import { useFooter } from '../../lib/composables/useFooter';
5
4
 
6
5
  /**
7
6
  * FooterSection component provides a section within the footer for organizing links and content.
@@ -31,14 +30,16 @@ export const FooterSection = forwardRef<
31
30
  },
32
31
  ref
33
32
  ) => {
34
- const [isCollapsed, setIsCollapsed] = React.useState(defaultCollapsed);
33
+ const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
35
34
 
36
35
  const handleToggle = () => {
37
- if (collapsible) {
38
- setIsCollapsed(!isCollapsed);
39
- }
36
+ if (collapsible) setIsCollapsed(prev => !prev);
40
37
  };
41
38
 
39
+ const sectionId = title
40
+ ? `footer-section-${title.toString().toLowerCase().replace(/\s+/g, '-')}`
41
+ : undefined;
42
+
42
43
  const sectionClass = [
43
44
  'c-footer__section',
44
45
  collapsible && 'c-footer__section--collapsible',
@@ -48,42 +49,42 @@ export const FooterSection = forwardRef<
48
49
  .filter(Boolean)
49
50
  .join(' ');
50
51
 
52
+ const renderHeader = () => {
53
+ if (!title) return null;
54
+
55
+ if (collapsible) {
56
+ return (
57
+ <div className="c-footer__section-header">
58
+ <button
59
+ type="button"
60
+ className="c-footer__section-toggle"
61
+ onClick={handleToggle}
62
+ aria-expanded={!isCollapsed}
63
+ aria-controls={sectionId}
64
+ >
65
+ {icon && <span className="c-footer__section-icon">{icon}</span>}
66
+ <h4 className="c-footer__section-title">{title}</h4>
67
+ <span className="c-footer__section-chevron">{isCollapsed ? '▼' : '▲'}</span>
68
+ </button>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ return (
74
+ <div className="c-footer__section-header">
75
+ <div className="c-footer__section-header-content">
76
+ {icon && <span className="c-footer__section-icon">{icon}</span>}
77
+ <h4 className="c-footer__section-title">{title}</h4>
78
+ </div>
79
+ </div>
80
+ );
81
+ };
82
+
51
83
  return (
52
84
  <GridCol xs={12} md={showNewsletter ? 6 : 3} className="c-footer__section-col">
53
85
  <div ref={ref} className={sectionClass} {...props}>
54
- {title && (
55
- <div className="c-footer__section-header">
56
- {collapsible ? (
57
- <button
58
- type="button"
59
- className="c-footer__section-toggle"
60
- onClick={handleToggle}
61
- aria-expanded={!isCollapsed}
62
- aria-controls={`footer-section-${title.toString().toLowerCase().replace(/\s+/g, '-')}`}
63
- >
64
- {icon && <span className="c-footer__section-icon">{icon}</span>}
65
- <h4 className="c-footer__section-title">{title}</h4>
66
- <span className="c-footer__section-chevron">{isCollapsed ? '▼' : '▲'}</span>
67
- </button>
68
- ) : (
69
- <div className="c-footer__section-header-content">
70
- {icon && <span className="c-footer__section-icon">{icon}</span>}
71
- <h4 className="c-footer__section-title">{title}</h4>
72
- </div>
73
- )}
74
- </div>
75
- )}
76
- <div
77
- className="c-footer__section-content"
78
- id={
79
- title
80
- ? `footer-section-${title.toString().toLowerCase().replace(/\s+/g, '-')}`
81
- : undefined
82
- }
83
- style={{
84
- display: collapsible && isCollapsed ? 'none' : 'flex',
85
- }}
86
- >
86
+ {renderHeader()}
87
+ <div className="c-footer__section-content" id={sectionId}>
87
88
  {children}
88
89
  </div>
89
90
  </div>
@@ -2,6 +2,65 @@ import React, { forwardRef } from 'react';
2
2
  import { Icon } from '../Icon/Icon';
3
3
  import { FooterSocialLinkProps } from '../../lib/types/components';
4
4
 
5
+ // ─── Platform Maps ───────────────────────────────────────────────
6
+
7
+ const PLATFORM_ICON_MAP: Record<string, string> = {
8
+ facebook: 'FacebookLogo',
9
+ twitter: 'TwitterLogo',
10
+ instagram: 'InstagramLogo',
11
+ linkedin: 'LinkedinLogo',
12
+ youtube: 'YoutubeLogo',
13
+ github: 'GithubLogo',
14
+ discord: 'DiscordLogo',
15
+ tiktok: 'TiktokLogo',
16
+ pinterest: 'PinterestLogo',
17
+ snapchat: 'SnapchatLogo',
18
+ whatsapp: 'WhatsappLogo',
19
+ telegram: 'TelegramLogo',
20
+ reddit: 'RedditLogo',
21
+ twitch: 'TwitchLogo',
22
+ spotify: 'SpotifyLogo',
23
+ dribbble: 'DribbbleLogo',
24
+ behance: 'BehanceLogo',
25
+ medium: 'MediumLogo',
26
+ dev: 'DevToLogo',
27
+ codepen: 'CodepenLogo',
28
+ };
29
+
30
+ const PLATFORM_LABEL_MAP: Record<string, string> = {
31
+ facebook: 'Facebook',
32
+ twitter: 'Twitter',
33
+ instagram: 'Instagram',
34
+ linkedin: 'LinkedIn',
35
+ youtube: 'YouTube',
36
+ github: 'GitHub',
37
+ discord: 'Discord',
38
+ tiktok: 'TikTok',
39
+ pinterest: 'Pinterest',
40
+ snapchat: 'Snapchat',
41
+ whatsapp: 'WhatsApp',
42
+ telegram: 'Telegram',
43
+ reddit: 'Reddit',
44
+ twitch: 'Twitch',
45
+ spotify: 'Spotify',
46
+ dribbble: 'Dribbble',
47
+ behance: 'Behance',
48
+ medium: 'Medium',
49
+ dev: 'Dev.to',
50
+ codepen: 'CodePen',
51
+ };
52
+
53
+ // ─── Helpers ─────────────────────────────────────────────────────
54
+
55
+ const getPlatformIcon = (platform: string) => {
56
+ const iconName = PLATFORM_ICON_MAP[platform];
57
+ return iconName ? <Icon name={iconName as any} /> : <Icon name="Link" />;
58
+ };
59
+
60
+ const getPlatformLabel = (platform: string): string => PLATFORM_LABEL_MAP[platform] || platform;
61
+
62
+ // ─── Component ───────────────────────────────────────────────────
63
+
5
64
  /**
6
65
  * FooterSocialLink component provides styled social media links with platform-specific icons.
7
66
  *
@@ -26,64 +85,6 @@ export const FooterSocialLink = forwardRef<HTMLAnchorElement, FooterSocialLinkPr
26
85
  },
27
86
  ref
28
87
  ) => {
29
- const getPlatformIcon = (platform: string) => {
30
- const iconMap: Record<string, string> = {
31
- facebook: 'FacebookLogo',
32
- twitter: 'TwitterLogo',
33
- instagram: 'InstagramLogo',
34
- linkedin: 'LinkedinLogo',
35
- youtube: 'YoutubeLogo',
36
- github: 'GithubLogo',
37
- discord: 'DiscordLogo',
38
- tiktok: 'TiktokLogo',
39
- pinterest: 'PinterestLogo',
40
- snapchat: 'SnapchatLogo',
41
- whatsapp: 'WhatsappLogo',
42
- telegram: 'TelegramLogo',
43
- reddit: 'RedditLogo',
44
- twitch: 'TwitchLogo',
45
- spotify: 'SpotifyLogo',
46
- dribbble: 'DribbbleLogo',
47
- behance: 'BehanceLogo',
48
- medium: 'MediumLogo',
49
- dev: 'DevToLogo',
50
- codepen: 'CodepenLogo',
51
- };
52
-
53
- const iconName = iconMap[platform];
54
- if (iconName) {
55
- return <Icon name={iconName as any} />;
56
- }
57
-
58
- return <Icon name="Link" />;
59
- };
60
-
61
- const getPlatformLabel = (platform: string) => {
62
- const labels: Record<string, string> = {
63
- facebook: 'Facebook',
64
- twitter: 'Twitter',
65
- instagram: 'Instagram',
66
- linkedin: 'LinkedIn',
67
- youtube: 'YouTube',
68
- github: 'GitHub',
69
- discord: 'Discord',
70
- tiktok: 'TikTok',
71
- pinterest: 'Pinterest',
72
- snapchat: 'Snapchat',
73
- whatsapp: 'WhatsApp',
74
- telegram: 'Telegram',
75
- reddit: 'Reddit',
76
- twitch: 'Twitch',
77
- spotify: 'Spotify',
78
- dribbble: 'Dribbble',
79
- behance: 'Behance',
80
- medium: 'Medium',
81
- dev: 'Dev.to',
82
- codepen: 'CodePen',
83
- };
84
- return labels[platform] || platform;
85
- };
86
-
87
88
  const linkClass = [
88
89
  'c-footer__social-link',
89
90
  `c-footer__social-link--${platform}`,
@@ -110,7 +110,7 @@ import { Footer, FooterSection, FooterLink } from '@shohojdhara/atomix';
110
110
  | `active` | `boolean` | `false` | Whether link is active |
111
111
  | `disabled` | `boolean` | `false` | Whether link is disabled |
112
112
  | `onClick` | `(event: MouseEvent) => void` | - | Link click handler |
113
- | `LinkComponent` | `React.ElementType` | - | Custom link component (e.g., React Router Link) |
113
+ | `linkComponent` | `React.ElementType` | - | Custom link component (e.g., React Router Link) |
114
114
 
115
115
  ### Social Link Configuration
116
116
 
@@ -12,21 +12,35 @@ export interface HeroTitleProps extends React.HTMLAttributes<HTMLHeadingElement>
12
12
  const HeroTitle = ({ children, className, level = 'h1', ...props }: HeroTitleProps) => {
13
13
  const Tag = level as any;
14
14
  return (
15
- <Tag className={`${HERO.SELECTORS.TITLE.replace('.', '')} ${className || ''}`.trim()} {...props}>
15
+ <Tag
16
+ className={`${HERO.SELECTORS.TITLE.replace('.', '')} ${className || ''}`.trim()}
17
+ {...props}
18
+ >
16
19
  {children}
17
20
  </Tag>
18
21
  );
19
22
  };
20
23
 
21
- const HeroSubtitle = ({ children, className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => {
24
+ const HeroSubtitle = ({
25
+ children,
26
+ className,
27
+ ...props
28
+ }: React.HTMLAttributes<HTMLParagraphElement>) => {
22
29
  return (
23
- <p className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${className || ''}`.trim()} {...props}>
30
+ <p
31
+ className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${className || ''}`.trim()}
32
+ {...props}
33
+ >
24
34
  {children}
25
35
  </p>
26
36
  );
27
37
  };
28
38
 
29
- const HeroText = ({ children, className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => {
39
+ const HeroText = ({
40
+ children,
41
+ className,
42
+ ...props
43
+ }: React.HTMLAttributes<HTMLParagraphElement>) => {
30
44
  return (
31
45
  <p className={`${HERO.SELECTORS.TEXT.replace('.', '')} ${className || ''}`.trim()} {...props}>
32
46
  {children}
@@ -36,7 +50,10 @@ const HeroText = ({ children, className, ...props }: React.HTMLAttributes<HTMLPa
36
50
 
37
51
  const HeroActions = ({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
38
52
  return (
39
- <div className={`${HERO.SELECTORS.ACTIONS.replace('.', '')} ${className || ''}`.trim()} {...props}>
53
+ <div
54
+ className={`${HERO.SELECTORS.ACTIONS.replace('.', '')} ${className || ''}`.trim()}
55
+ {...props}
56
+ >
40
57
  {children}
41
58
  </div>
42
59
  );
@@ -46,34 +63,32 @@ export interface HeroContentProps extends React.HTMLAttributes<HTMLDivElement> {
46
63
  glass?: AtomixGlassProps | boolean;
47
64
  }
48
65
 
66
+ const HERO_DEFAULT_GLASS_PROPS = {
67
+ displacementScale: 60,
68
+ blurAmount: 3,
69
+ saturation: 180,
70
+ aberrationIntensity: 0,
71
+ borderRadius: 8,
72
+ overLight: false as const,
73
+ mode: 'standard' as const,
74
+ };
75
+
49
76
  const HeroContent = ({ children, className, style, glass, ...props }: HeroContentProps) => {
50
77
  const contentClass = `${HERO.SELECTORS.CONTENT.replace('.', '')} ${className || ''}`.trim();
51
78
 
52
- if (glass) {
53
- const glassProps = typeof glass === 'boolean' ? {
54
- displacementScale: 60,
55
- blurAmount: 3,
56
- saturation: 180,
57
- aberrationIntensity: 0,
58
- borderRadius: 8,
59
- overLight: false,
60
- mode: 'standard' as const,
61
- } : glass;
79
+ const innerContent = glass ? <div className="u-p-4">{children}</div> : children;
62
80
 
63
- return (
64
- <div className={contentClass} style={style} {...props}>
65
- <AtomixGlass {...glassProps}>
66
- <div className="u-p-4">
67
- {children}
68
- </div>
69
- </AtomixGlass>
70
- </div>
71
- );
72
- }
81
+ const wrappedContent = glass
82
+ ? (() => {
83
+ const glassProps =
84
+ glass === true ? HERO_DEFAULT_GLASS_PROPS : { ...HERO_DEFAULT_GLASS_PROPS, ...glass };
85
+ return <AtomixGlass {...glassProps}>{innerContent}</AtomixGlass>;
86
+ })()
87
+ : innerContent;
73
88
 
74
89
  return (
75
90
  <div className={contentClass} style={style} {...props}>
76
- {children}
91
+ {wrappedContent}
77
92
  </div>
78
93
  );
79
94
  };
@@ -106,7 +121,13 @@ const HeroImage = ({
106
121
  );
107
122
  };
108
123
 
109
- const HeroBackground = ({ className, style, src, children, ...props }: React.HTMLAttributes<HTMLDivElement> & { src?: string }) => {
124
+ const HeroBackground = ({
125
+ className,
126
+ style,
127
+ src,
128
+ children,
129
+ ...props
130
+ }: React.HTMLAttributes<HTMLDivElement> & { src?: string }) => {
110
131
  return (
111
132
  <div
112
133
  className={`${HERO.SELECTORS.BG.replace('.', '')} ${className || ''}`.trim()}
@@ -114,11 +135,7 @@ const HeroBackground = ({ className, style, src, children, ...props }: React.HTM
114
135
  {...props}
115
136
  >
116
137
  {src && (
117
- <img
118
- src={src}
119
- alt="Background"
120
- className={HERO.SELECTORS.BG_IMAGE.replace('.', '')}
121
- />
138
+ <img src={src} alt="Background" className={HERO.SELECTORS.BG_IMAGE.replace('.', '')} />
122
139
  )}
123
140
  {children}
124
141
  </div>
@@ -332,11 +349,9 @@ export const Hero: React.FC<HeroProps> & {
332
349
  };
333
350
 
334
351
  const renderContent = () => {
335
- const content = (
336
- <div
337
- className={`${HERO.SELECTORS.CONTENT.replace('.', '')} ${parts?.content?.className || ''}`.trim()}
338
- style={parts?.content?.style}
339
- >
352
+ // Build inner content elements ONCE — no duplication
353
+ const innerElements = (
354
+ <>
340
355
  {subtitle && (
341
356
  <p
342
357
  className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${parts?.subtitle?.className || ''}`.trim()}
@@ -367,115 +382,30 @@ export const Hero: React.FC<HeroProps> & {
367
382
  {actions}
368
383
  </div>
369
384
  )}
370
- </div>
385
+ </>
371
386
  );
372
387
 
373
- // If glass is explicitly set to false, don't apply glass effect
374
- if (glass === false) {
375
- return content;
376
- }
377
-
378
- // If glass is true or an object, apply glass effect
379
- if (glass) {
380
- // If glass is true, use default glass props
381
- if (glass === true) {
382
- return (
383
- <div
384
- className={`${HERO.SELECTORS.CONTENT.replace('.', '')} ${parts?.content?.className || ''}`.trim()}
385
- style={parts?.content?.style}
386
- >
387
- <AtomixGlass
388
- displacementScale={60}
389
- blurAmount={3}
390
- saturation={180}
391
- aberrationIntensity={0}
392
- borderRadius={8}
393
- overLight={false}
394
- mode="standard"
395
- >
396
- <div className="u-p-4">
397
- {subtitle && (
398
- <p
399
- className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${parts?.subtitle?.className || ''}`.trim()}
400
- style={parts?.subtitle?.style}
401
- >
402
- {subtitle}
403
- </p>
404
- )}
405
- <HeadingTag
406
- className={`${HERO.SELECTORS.TITLE.replace('.', '')} ${parts?.title?.className || ''}`.trim()}
407
- style={parts?.title?.style}
408
- >
409
- {title}
410
- </HeadingTag>
411
- {text && (
412
- <p
413
- className={`${HERO.SELECTORS.TEXT.replace('.', '')} ${parts?.text?.className || ''}`.trim()}
414
- style={parts?.text?.style}
415
- >
416
- {text}
417
- </p>
418
- )}
419
- {actions && (
420
- <div
421
- className={`${HERO.SELECTORS.ACTIONS.replace('.', '')} ${parts?.actions?.className || ''}`.trim()}
422
- style={parts?.actions?.style}
423
- >
424
- {actions}
425
- </div>
426
- )}
427
- </div>
388
+ // Conditionally wrap with AtomixGlass using the standard glass prop pattern
389
+ const contentBody = glass
390
+ ? (() => {
391
+ const glassProps =
392
+ glass === true ? HERO_DEFAULT_GLASS_PROPS : { ...HERO_DEFAULT_GLASS_PROPS, ...glass };
393
+ return (
394
+ <AtomixGlass {...glassProps}>
395
+ <div className="u-p-4">{innerElements}</div>
428
396
  </AtomixGlass>
429
- </div>
430
- );
431
- }
432
-
433
- // If glass is an object, use provided glass props
434
- return (
435
- <div
436
- className={`${HERO.SELECTORS.CONTENT.replace('.', '')} ${parts?.content?.className || ''}`.trim()}
437
- style={parts?.content?.style}
438
- >
439
- <AtomixGlass {...glass}>
440
- <div className="u-p-4">
441
- {subtitle && (
442
- <p
443
- className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${parts?.subtitle?.className || ''}`.trim()}
444
- style={parts?.subtitle?.style}
445
- >
446
- {subtitle}
447
- </p>
448
- )}
449
- <HeadingTag
450
- className={`${HERO.SELECTORS.TITLE.replace('.', '')} ${parts?.title?.className || ''}`.trim()}
451
- style={parts?.title?.style}
452
- >
453
- {title}
454
- </HeadingTag>
455
- {text && (
456
- <p
457
- className={`${HERO.SELECTORS.TEXT.replace('.', '')} ${parts?.text?.className || ''}`.trim()}
458
- style={parts?.text?.style}
459
- >
460
- {text}
461
- </p>
462
- )}
463
- {actions && (
464
- <div
465
- className={`${HERO.SELECTORS.ACTIONS.replace('.', '')} ${parts?.actions?.className || ''}`.trim()}
466
- style={parts?.actions?.style}
467
- >
468
- {actions}
469
- </div>
470
- )}
471
- </div>
472
- </AtomixGlass>
473
- </div>
474
- );
475
- }
397
+ );
398
+ })()
399
+ : innerElements;
476
400
 
477
- // Default behavior - no glass effect
478
- return content;
401
+ return (
402
+ <div
403
+ className={`${HERO.SELECTORS.CONTENT.replace('.', '')} ${parts?.content?.className || ''}`.trim()}
404
+ style={parts?.content?.style}
405
+ >
406
+ {contentBody}
407
+ </div>
408
+ );
479
409
  };
480
410
 
481
411
  const renderForegroundImage = () => {
@@ -86,7 +86,7 @@ export const MegaMenuColumn = forwardRef<HTMLDivElement, MegaMenuColumnProps>(
86
86
  MegaMenuColumn.displayName = 'MegaMenuColumn';
87
87
 
88
88
  export const MegaMenuLink = forwardRef<HTMLAnchorElement, MegaMenuLinkProps>(
89
- ({ href, children, className = '', disabled = false, onClick }, ref) => {
89
+ ({ href, target, linkComponent, children, className = '', disabled = false, onClick }, ref) => {
90
90
  const handleClick = (e: React.MouseEvent) => {
91
91
  if (disabled) {
92
92
  e.preventDefault();
@@ -98,17 +98,22 @@ export const MegaMenuLink = forwardRef<HTMLAnchorElement, MegaMenuLinkProps>(
98
98
  }
99
99
  };
100
100
 
101
- return (
102
- <a
103
- ref={ref}
104
- href={href}
105
- className={`c-menu__subitem-link ${disabled ? 'is-disabled' : ''} ${className}`}
106
- onClick={handleClick}
107
- aria-disabled={disabled}
108
- >
109
- {children}
110
- </a>
111
- );
101
+ const linkProps = {
102
+ ref,
103
+ href,
104
+ to: href,
105
+ target,
106
+ className: `c-menu__subitem-link ${disabled ? 'is-disabled' : ''} ${className}`,
107
+ onClick: handleClick,
108
+ 'aria-disabled': disabled,
109
+ };
110
+
111
+ if (linkComponent) {
112
+ const Component = linkComponent as React.ComponentType<any>;
113
+ return <Component {...linkProps}>{children}</Component>;
114
+ }
115
+
116
+ return <a {...linkProps}>{children}</a>;
112
117
  }
113
118
  );
114
119
 
@@ -39,7 +39,17 @@ interface MenuDividerProps {
39
39
 
40
40
  export const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>(
41
41
  (
42
- { children, href = '#', icon, active = false, disabled = false, onClick, className = '' },
42
+ {
43
+ children,
44
+ href = '#',
45
+ target,
46
+ linkComponent,
47
+ icon,
48
+ active = false,
49
+ disabled = false,
50
+ onClick,
51
+ className = '',
52
+ },
43
53
  ref
44
54
  ) => {
45
55
  const handleClick = (e: React.MouseEvent) => {
@@ -55,31 +65,46 @@ export const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>(
55
65
 
56
66
  const itemClass = `c-menu__item ${active ? 'is-active' : ''} ${disabled ? 'is-disabled' : ''} ${className}`;
57
67
 
68
+ const linkProps = {
69
+ href,
70
+ to: href,
71
+ target,
72
+ className: 'c-menu__link',
73
+ onClick: handleClick,
74
+ 'aria-disabled': disabled,
75
+ 'aria-current': (active ? 'page' : undefined) as React.AriaAttributes['aria-current'],
76
+ };
77
+
78
+ const content = (
79
+ <>
80
+ {icon &&
81
+ (typeof icon === 'string' ? (
82
+ icon.startsWith('c-icon-') ? (
83
+ <Icon
84
+ name={mapIconName(icon.replace('c-icon-', ''))}
85
+ size="sm"
86
+ className="c-menu__icon"
87
+ />
88
+ ) : (
89
+ <i className={`c-menu__icon ${icon}`}>{typeof icon !== 'string' && icon}</i>
90
+ )
91
+ ) : (
92
+ <span className="c-menu__icon">{icon}</span>
93
+ ))}
94
+ {children}
95
+ </>
96
+ );
97
+
58
98
  return (
59
99
  <li ref={ref} className={itemClass} role="menuitem">
60
- <a
61
- href={href}
62
- className="c-menu__link"
63
- onClick={handleClick}
64
- aria-disabled={disabled}
65
- aria-current={active ? 'page' : undefined}
66
- >
67
- {icon &&
68
- (typeof icon === 'string' ? (
69
- icon.startsWith('c-icon-') ? (
70
- <Icon
71
- name={mapIconName(icon.replace('c-icon-', ''))}
72
- size="sm"
73
- className="c-menu__icon"
74
- />
75
- ) : (
76
- <i className={`c-menu__icon ${icon}`}>{typeof icon !== 'string' && icon}</i>
77
- )
78
- ) : (
79
- <span className="c-menu__icon">{icon}</span>
80
- ))}
81
- {children}
82
- </a>
100
+ {linkComponent ? (
101
+ (() => {
102
+ const Component = linkComponent as React.ComponentType<any>;
103
+ return <Component {...linkProps}>{content}</Component>;
104
+ })()
105
+ ) : (
106
+ <a {...linkProps}>{content}</a>
107
+ )}
83
108
  </li>
84
109
  );
85
110
  }