@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
@@ -75,16 +75,18 @@ export const BackgroundWrapper: React.FC<BackgroundWrapperProps> = ({
75
75
 
76
76
  const bgStyle = {
77
77
  backgroundImage: bgImage ? `url(${bgImage})` : undefined,
78
+ backgroundSize: 'cover',
79
+ backgroundPosition: 'center',
80
+ backgroundRepeat: 'no-repeat',
78
81
  height,
79
82
  width,
80
83
  borderRadius,
81
- padding,
82
84
  ...style,
83
85
  };
84
86
 
85
87
  return (
86
88
  <div
87
- className={`u-relative u-overflow-hidden ${className}`}
89
+ className={`u-relative u-overflow-hidden u-flex u-items-center u-justify-center ${className}`}
88
90
  style={bgStyle}
89
91
  aria-hidden={ariaHidden}
90
92
  >
@@ -94,10 +96,16 @@ export const BackgroundWrapper: React.FC<BackgroundWrapperProps> = ({
94
96
  style={{
95
97
  backgroundColor: overlayColor,
96
98
  opacity: overlayOpacity,
99
+ padding,
97
100
  }}
98
101
  />
99
102
  )}
100
- <div className="u-relative u-z-10">{children}</div>
103
+ <div
104
+ className="u-relative u-z-10 u-w-100 u-h-100 u-flex u-items-center u-justify-center"
105
+ style={{ padding }}
106
+ >
107
+ {children}
108
+ </div>
101
109
  </div>
102
110
  );
103
111
  };
@@ -78,7 +78,7 @@ export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLLIElement>
78
78
  linkAs?: React.ElementType<any>;
79
79
 
80
80
  /**
81
- * Link props to pass to the underlying anchor or LinkComponent
81
+ * Link props to pass to the underlying anchor or linkComponent
82
82
  */
83
83
  linkProps?: Record<string, any>;
84
84
  }
@@ -169,7 +169,7 @@ export interface BreadcrumbProps {
169
169
  /**
170
170
  * Optional custom link component
171
171
  */
172
- LinkComponent?: React.ElementType;
172
+ linkComponent?: React.ElementType;
173
173
 
174
174
  /**
175
175
  * Custom style for the breadcrumb
@@ -187,7 +187,7 @@ const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbB
187
187
  divider,
188
188
  className = '',
189
189
  'aria-label': ariaLabel = 'Breadcrumb',
190
- LinkComponent,
190
+ linkComponent,
191
191
  style,
192
192
  children,
193
193
  }: BreadcrumbProps) {
@@ -209,7 +209,7 @@ const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbB
209
209
  onClick={item.onClick as any}
210
210
  className={item.className}
211
211
  style={item.style}
212
- linkAs={LinkComponent}
212
+ linkAs={linkComponent}
213
213
  >
214
214
  {item.label}
215
215
  </BreadcrumbItem>
@@ -228,7 +228,7 @@ const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbB
228
228
 
229
229
  const newProps = {
230
230
  active: active ?? (isLast ? true : undefined),
231
- linkAs: linkAs ?? LinkComponent,
231
+ linkAs: linkAs ?? linkComponent,
232
232
  };
233
233
 
234
234
  return cloneElement(child, newProps as any);
@@ -46,7 +46,7 @@ describe('Breadcrumb Component', () => {
46
46
  expect(handleClick).toHaveBeenCalledTimes(1);
47
47
  });
48
48
 
49
- it('supports custom LinkComponent in compound components', () => {
49
+ it('supports custom linkComponent in compound components', () => {
50
50
  const CustomLink = ({ href, children, ...props }: any) => (
51
51
  <a href={href} data-testid="custom-link" {...props}>
52
52
  {children} (Custom)
@@ -54,7 +54,7 @@ describe('Breadcrumb Component', () => {
54
54
  );
55
55
 
56
56
  render(
57
- <Breadcrumb LinkComponent={CustomLink}>
57
+ <Breadcrumb linkComponent={CustomLink}>
58
58
  <Breadcrumb.Item href="/custom">Link</Breadcrumb.Item>
59
59
  <Breadcrumb.Item>Current</Breadcrumb.Item>
60
60
  </Breadcrumb>
@@ -10,7 +10,7 @@ export type ButtonAsProp = {
10
10
  as?: ElementType;
11
11
  to?: string;
12
12
  href?: string;
13
- LinkComponent?: React.ElementType;
13
+ linkComponent?: React.ElementType;
14
14
  [key: string]: any;
15
15
  };
16
16
 
@@ -51,7 +51,7 @@ export const Button = React.memo(
51
51
  'aria-controls': ariaControls,
52
52
  tabIndex,
53
53
  style,
54
- LinkComponent,
54
+ linkComponent,
55
55
  ...props
56
56
  },
57
57
  ref
@@ -220,12 +220,12 @@ export const Button = React.memo(
220
220
 
221
221
  // Render as anchor if href is provided
222
222
  if (shouldRenderAsLink) {
223
- // Use custom LinkComponent if provided (e.g., Next.js Link)
224
- if (LinkComponent) {
225
- const LinkComp = LinkComponent as React.ComponentType<any>;
223
+ // Use custom linkComponent if provided (e.g., Next.js Link)
224
+ if (linkComponent) {
225
+ const LinkComp = linkComponent as React.ComponentType<any>;
226
226
  const linkProps = {
227
227
  ...buttonProps,
228
- ref: ref as any, // LinkComponent usually forwards ref to anchor
228
+ ref: ref as any, // linkComponent usually forwards ref to anchor
229
229
  href,
230
230
  to: href,
231
231
  target,
@@ -40,7 +40,7 @@ export const Card = React.memo(
40
40
  href,
41
41
  target,
42
42
  // Custom Link
43
- LinkComponent,
43
+ linkComponent,
44
44
  // Glass
45
45
  glass,
46
46
  // Accessibility
@@ -246,8 +246,8 @@ export const Card = React.memo(
246
246
  if (href && !isDisabled) {
247
247
  let anchorElement: React.ReactElement;
248
248
 
249
- if (LinkComponent) {
250
- const LinkComp = LinkComponent as React.ComponentType<any>;
249
+ if (linkComponent) {
250
+ const LinkComp = linkComponent as React.ComponentType<any>;
251
251
  anchorElement = (
252
252
  <LinkComp
253
253
  {...commonProps}
@@ -90,12 +90,13 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
90
90
  ({
91
91
  children,
92
92
  href,
93
+ target,
93
94
  active = false,
94
95
  disabled = false,
95
96
  icon,
96
97
  onClick,
97
98
  className = '',
98
- LinkComponent,
99
+ linkComponent,
99
100
  ...props
100
101
  }) => {
101
102
  const { close } = useContext(DropdownContext);
@@ -126,6 +127,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
126
127
  const linkProps = {
127
128
  href,
128
129
  to: href,
130
+ target,
129
131
  className: itemClasses,
130
132
  onClick: handleClick,
131
133
  role: 'menuitem',
@@ -136,9 +138,9 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
136
138
  if (href && !disabled) {
137
139
  return (
138
140
  <li>
139
- {LinkComponent ? (
141
+ {linkComponent ? (
140
142
  (() => {
141
- const Component = LinkComponent as React.ComponentType<any>;
143
+ const Component = linkComponent as React.ComponentType<any>;
142
144
  return (
143
145
  <Component {...linkProps}>
144
146
  {icon && <span className="c-dropdown__menu-item-icon">{icon}</span>}
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef } from 'react';
2
- import { FooterProps } from '../../lib/types/components';
2
+ import { FooterProps, AtomixGlassProps } from '../../lib/types/components';
3
3
  import { useFooter } from '../../lib/composables/useFooter';
4
4
  import { Button } from '../Button';
5
5
  import { Input, Form } from '../Form';
@@ -7,7 +7,6 @@ import { FooterSocialLink } from './FooterSocialLink';
7
7
  import { Grid, GridCol } from '../../layouts/Grid';
8
8
  import { FooterSection } from './FooterSection';
9
9
  import AtomixGlass from '../AtomixGlass/AtomixGlass';
10
- import { AtomixGlassProps } from '../../lib/types/components';
11
10
 
12
11
  /**
13
12
  * Footer component provides a comprehensive footer section with multiple layout options,
@@ -68,9 +67,9 @@ export const Footer = forwardRef<HTMLElement, FooterProps>(
68
67
  brandClass,
69
68
  sectionsClass,
70
69
  bottomClass,
70
+ getResponsiveColumnProps,
71
71
  handleNewsletterSubmit,
72
72
  handleBackToTop,
73
- socialLinks: footerSocialLinks,
74
73
  } = useFooter({
75
74
  layout,
76
75
  variant,
@@ -78,179 +77,118 @@ export const Footer = forwardRef<HTMLElement, FooterProps>(
78
77
  sticky,
79
78
  showNewsletter,
80
79
  showBackToTop,
80
+ showDivider,
81
81
  socialLinks,
82
82
  onNewsletterSubmit,
83
83
  onBackToTop,
84
+ glass: Boolean(glass),
84
85
  className,
85
86
  });
86
87
 
87
- // Calculate grid column sizes based on layout
88
- const getGridColumnSizes = () => {
89
- switch (layout) {
90
- case 'columns':
91
- // For columns layout, we have 3 columns (brand, content, newsletter)
92
- return {
93
- brand: 4,
94
- content: !showNewsletter ? 8 : 4,
95
- newsletter: !showNewsletter ? 0 : 4,
96
- };
97
- case 'centered':
98
- // For centered layout, brand takes full width, content and newsletter are centered
99
- return { brand: 12, content: 12, newsletter: !showNewsletter ? 0 : 12 };
100
- case 'minimal':
101
- // For minimal layout, everything takes full width
102
- return { brand: 12, content: 12, newsletter: !showNewsletter ? 0 : 12 };
103
- case 'stacked':
104
- // For stacked layout, everything takes full width but stacked vertically
105
- return { brand: 12, content: 12, newsletter: !showNewsletter ? 0 : 12 };
106
- case 'flexible':
107
- // For flexible layout, adjust based on content
108
- return { brand: 'auto', content: 'auto', newsletter: 'auto' };
109
- case 'sidebar':
110
- // For sidebar layout, brand on left, content and newsletter on right
111
- return {
112
- brand: 3,
113
- content: !showNewsletter ? 9 : 9,
114
- newsletter: !showNewsletter ? 0 : 9,
115
- };
116
- case 'wide':
117
- // For wide layout, content takes more space
118
- return {
119
- brand: 3,
120
- content: !showNewsletter ? 6 : 6,
121
- newsletter: !showNewsletter ? 0 : 3,
122
- };
123
- default:
124
- return {
125
- brand: 4,
126
- content: !showNewsletter ? 8 : 4,
127
- newsletter: !showNewsletter ? 0 : 4,
128
- };
129
- }
130
- };
88
+ // ──────────────────────────────────────────
89
+ // Render helpers
90
+ // ──────────────────────────────────────────
131
91
 
132
- const columnSizes = getGridColumnSizes();
92
+ const renderBrandSection = () => {
93
+ if (!brand && !brandLogo && !brandDescription) return null;
133
94
 
134
- // Calculate responsive column sizes
135
- const getResponsiveColumnProps = (columnType: 'brand' | 'content' | 'newsletter') => {
136
- const baseMd =
137
- layout === 'columns' || layout === 'sidebar' || layout === 'wide'
138
- ? columnSizes[columnType]
139
- : 12;
95
+ return (
96
+ <GridCol {...(getResponsiveColumnProps('brand') as any)} className={brandClass}>
97
+ {brandLogo && (
98
+ <div className="c-footer__brand-logo">
99
+ {typeof brandLogo === 'string' ? <img src={brandLogo} alt="Brand Logo" /> : brandLogo}
100
+ </div>
101
+ )}
102
+ {brand && (
103
+ <div className="c-footer__brand-name">
104
+ {typeof brand === 'string' ? <h3>{brand}</h3> : brand}
105
+ </div>
106
+ )}
107
+ {brandDescription && (
108
+ <div className="c-footer__brand-description">{brandDescription}</div>
109
+ )}
110
+ {socialLinks.length > 0 && (
111
+ <div className="c-footer__social" data-testid="footer-social-links">
112
+ {socialLinks.map((link, index) => (
113
+ <FooterSocialLink
114
+ key={`${link.platform}-${index}`}
115
+ platform={link.platform}
116
+ url={link.url}
117
+ icon={link.icon}
118
+ label={link.label}
119
+ size={size}
120
+ />
121
+ ))}
122
+ </div>
123
+ )}
124
+ </GridCol>
125
+ );
126
+ };
140
127
 
141
- // For flexible layout, we want auto-sizing
142
- if (layout === 'flexible' && columnSizes[columnType] === 'auto') {
143
- return { xs: 12, sm: true, md: true };
144
- }
128
+ const renderSections = () => {
129
+ if (!children) return null;
145
130
 
146
- // For other layouts, we use specific sizes
147
- return { xs: 12, md: baseMd };
131
+ return (
132
+ <GridCol {...(getResponsiveColumnProps('content') as any)} className="c-footer__content">
133
+ <Grid
134
+ className="c-footer__sections"
135
+ alignItems={layout === 'centered' || layout === 'stacked' ? 'center' : undefined}
136
+ >
137
+ {React.Children.map(children, child => {
138
+ if (React.isValidElement(child)) {
139
+ return React.cloneElement(child, { showNewsletter } as any);
140
+ }
141
+ return child;
142
+ })}
143
+ </Grid>
144
+ </GridCol>
145
+ );
148
146
  };
149
147
 
150
- const footerContent = (
151
- <div className={containerClass}>
152
- {/* Main Footer Content */}
153
- <Grid
154
- className={sectionsClass}
155
- alignItems="start"
156
- justifyContent={layout === 'centered' ? 'center' : undefined}
148
+ const renderNewsletter = () => {
149
+ if (!showNewsletter) return null;
150
+
151
+ return (
152
+ <GridCol
153
+ {...(getResponsiveColumnProps('newsletter') as any)}
154
+ className="c-footer__newsletter"
157
155
  >
158
- {/* Brand Section */}
159
- {(brand || brandLogo || brandDescription) && (
160
- <GridCol {...(getResponsiveColumnProps('brand') as any)} className={brandClass}>
161
- {brandLogo && (
162
- <div className="c-footer__brand-logo">
163
- {typeof brandLogo === 'string' ? (
164
- <img src={brandLogo} alt={'Brand Logo'} />
165
- ) : (
166
- brandLogo
167
- )}
168
- </div>
169
- )}
170
- {brand && (
171
- <div className="c-footer__brand-name">
172
- {typeof brand === 'string' ? <h3>{brand}</h3> : brand}
173
- </div>
174
- )}
175
- {brandDescription && (
176
- <div className="c-footer__brand-description">{brandDescription}</div>
177
- )}
178
- {socialLinks.length > 0 && (
179
- <div className="c-footer__social" data-testid="footer-social-links">
180
- {socialLinks.map((link, index) => (
181
- <FooterSocialLink
182
- key={`${link.platform}-${index}`}
183
- platform={link.platform}
184
- url={link.url}
185
- icon={link.icon}
186
- label={link.label}
187
- size={size}
188
- />
189
- ))}
190
- </div>
191
- )}
192
- </GridCol>
156
+ <h4 className="c-footer__newsletter-title">{newsletterTitle}</h4>
157
+ {newsletterDescription && (
158
+ <p className="c-footer__newsletter-description">{newsletterDescription}</p>
193
159
  )}
160
+ <Form
161
+ className="c-footer__newsletter-form"
162
+ onSubmit={e => {
163
+ e.preventDefault();
164
+ const formData = new FormData(e.currentTarget);
165
+ const email = formData.get('email') as string;
166
+ if (email) handleNewsletterSubmit(email);
167
+ }}
168
+ >
169
+ <div className="c-footer__newsletter-input-group">
170
+ <Input
171
+ type="email"
172
+ name="email"
173
+ className="c-footer__newsletter-input"
174
+ placeholder={newsletterPlaceholder}
175
+ required
176
+ />
177
+ <Button type="submit" className="c-footer__newsletter-button">
178
+ {newsletterButtonText}
179
+ </Button>
180
+ </div>
181
+ </Form>
182
+ </GridCol>
183
+ );
184
+ };
194
185
 
195
- {/* Footer Sections */}
196
- {children && (
197
- <GridCol
198
- {...(getResponsiveColumnProps('content') as any)}
199
- className="c-footer__content"
200
- >
201
- <Grid
202
- className="c-footer__sections"
203
- alignItems={layout === 'centered' || layout === 'stacked' ? 'center' : undefined}
204
- >
205
- {React.Children.map(children, child => {
206
- // Check if the child is a valid React element
207
- if (React.isValidElement(child)) {
208
- // Clone the element and pass the showNewsletter prop
209
- return React.cloneElement(child, { showNewsletter } as any);
210
- }
211
- return child;
212
- })}
213
- </Grid>
214
- </GridCol>
215
- )}
186
+ const renderBottom = () => {
187
+ if (!copyright && !showBackToTop) return null;
216
188
 
217
- {/* Newsletter Section */}
218
- {showNewsletter && (
219
- <GridCol
220
- {...(getResponsiveColumnProps('newsletter') as any)}
221
- className="c-footer__newsletter"
222
- >
223
- <h4 className="c-footer__newsletter-title">{newsletterTitle}</h4>
224
- {newsletterDescription && (
225
- <p className="c-footer__newsletter-description">{newsletterDescription}</p>
226
- )}
227
- <Form
228
- className="c-footer__newsletter-form"
229
- onSubmit={e => {
230
- e.preventDefault();
231
- const formData = new FormData(e.currentTarget);
232
- const email = formData.get('email') as string;
233
- if (email) handleNewsletterSubmit(email);
234
- }}
235
- >
236
- <div className="c-footer__newsletter-input-group">
237
- <Input
238
- type="email"
239
- name="email"
240
- className="c-footer__newsletter-input"
241
- placeholder={newsletterPlaceholder}
242
- required
243
- />
244
- <Button type="submit" className="c-footer__newsletter-button">
245
- {newsletterButtonText}
246
- </Button>
247
- </div>
248
- </Form>
249
- </GridCol>
250
- )}
251
- </Grid>
252
-
253
- {(copyright || showBackToTop) && (
189
+ return (
190
+ <>
191
+ {showDivider && <hr className="c-footer__divider" />}
254
192
  <div className={bottomClass}>
255
193
  {copyright && <div className="c-footer__copyright">{copyright}</div>}
256
194
  {showBackToTop && (
@@ -266,16 +204,36 @@ export const Footer = forwardRef<HTMLElement, FooterProps>(
266
204
  </Button>
267
205
  )}
268
206
  </div>
269
- )}
207
+ </>
208
+ );
209
+ };
210
+
211
+ // ──────────────────────────────────────────
212
+ // Main content
213
+ // ──────────────────────────────────────────
214
+
215
+ const footerContent = (
216
+ <div className={containerClass}>
217
+ <Grid
218
+ className={sectionsClass}
219
+ alignItems="start"
220
+ justifyContent={layout === 'centered' ? 'center' : undefined}
221
+ >
222
+ {renderBrandSection()}
223
+ {renderSections()}
224
+ {renderNewsletter()}
225
+ </Grid>
226
+
227
+ {renderBottom()}
270
228
  </div>
271
229
  );
272
230
 
231
+ // ──────────────────────────────────────────
232
+ // Root element
233
+ // ──────────────────────────────────────────
234
+
273
235
  return (
274
- <footer
275
- ref={ref}
276
- className={footerClass + ` c-footer ${glass ? 'c-footer--glass' : ''}`}
277
- {...props}
278
- >
236
+ <footer ref={ref} className={footerClass} {...props}>
279
237
  {glass ? (
280
238
  <AtomixGlass {...(glass as unknown as AtomixGlassProps)} elasticity={0}>
281
239
  <div className="c-footer__glass">{footerContent}</div>
@@ -22,7 +22,7 @@ export const FooterLink = forwardRef<HTMLAnchorElement, FooterLinkProps>(
22
22
  onClick,
23
23
  children,
24
24
  className = '',
25
- LinkComponent,
25
+ linkComponent,
26
26
  ...props
27
27
  },
28
28
  ref
@@ -36,40 +36,37 @@ export const FooterLink = forwardRef<HTMLAnchorElement, FooterLinkProps>(
36
36
  .filter(Boolean)
37
37
  .join(' ');
38
38
 
39
- const linkProps = {
39
+ const sharedProps = {
40
40
  className: linkClass,
41
41
  onClick: disabled ? undefined : onClick,
42
42
  'aria-disabled': disabled,
43
43
  ...(external && {
44
- target: '_blank',
44
+ target: '_blank' as const,
45
45
  rel: 'noopener noreferrer',
46
46
  }),
47
47
  ...props,
48
48
  };
49
49
 
50
- if (LinkComponent) {
51
- const Component = LinkComponent as React.ComponentType<any>;
52
- // Only pass href/to if the link is not disabled and href exists
53
- const componentProps = {
54
- ref,
55
- ...(href && !disabled ? { href, to: href } : {}),
56
- ...linkProps,
57
- };
50
+ const linkContent = (
51
+ <>
52
+ {icon && <span className="c-footer__link-icon">{icon}</span>}
53
+ <span className="c-footer__link-text">{children}</span>
54
+ {external && <span className="c-footer__link-external">↗</span>}
55
+ </>
56
+ );
58
57
 
58
+ if (linkComponent) {
59
+ const Component = linkComponent as React.ComponentType<any>;
59
60
  return (
60
- <Component {...componentProps}>
61
- {icon && <span className="c-footer__link-icon">{icon}</span>}
62
- <span className="c-footer__link-text">{children}</span>
63
- {external && <span className="c-footer__link-external">↗</span>}
61
+ <Component ref={ref} {...(href && !disabled ? { href, to: href } : {})} {...sharedProps}>
62
+ {linkContent}
64
63
  </Component>
65
64
  );
66
65
  }
67
66
 
68
67
  return (
69
- <a ref={ref} href={disabled ? undefined : href} {...linkProps}>
70
- {icon && <span className="c-footer__link-icon">{icon}</span>}
71
- <span className="c-footer__link-text">{children}</span>
72
- {external && <span className="c-footer__link-external">↗</span>}
68
+ <a ref={ref} href={disabled ? undefined : href} {...sharedProps}>
69
+ {linkContent}
73
70
  </a>
74
71
  );
75
72
  }