@shohojdhara/atomix 0.5.5 → 0.5.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 (37) hide show
  1. package/README.md +43 -21
  2. package/dist/atomix.css +647 -1395
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +5 -5
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/core.d.ts +100 -8
  7. package/dist/core.js +89 -79
  8. package/dist/core.js.map +1 -1
  9. package/dist/forms.js +1 -7
  10. package/dist/forms.js.map +1 -1
  11. package/dist/heavy.js +7 -3
  12. package/dist/heavy.js.map +1 -1
  13. package/dist/index.d.ts +179 -54
  14. package/dist/index.esm.js +112 -99
  15. package/dist/index.esm.js.map +1 -1
  16. package/dist/index.js +112 -99
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.min.js +1 -1
  19. package/dist/index.min.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/Accordion/Accordion.tsx +40 -25
  22. package/src/components/Breadcrumb/Breadcrumb.tsx +22 -13
  23. package/src/components/Button/Button.tsx +4 -5
  24. package/src/components/Callout/Callout.tsx +98 -96
  25. package/src/components/Card/Card.tsx +117 -103
  26. package/src/components/Card/index.ts +7 -5
  27. package/src/components/Dropdown/Dropdown.tsx +27 -8
  28. package/src/components/EdgePanel/EdgePanel.tsx +7 -2
  29. package/src/components/Modal/Modal.tsx +27 -8
  30. package/src/components/Spinner/Spinner.tsx +60 -43
  31. package/src/components/Tabs/Tabs.tsx +163 -149
  32. package/src/lib/composables/useInput.ts +11 -9
  33. package/src/lib/types/components.ts +84 -0
  34. package/src/styles/01-settings/_settings.background.scss +2 -1
  35. package/src/styles/02-tools/_tools.background.scss +100 -294
  36. package/src/styles/06-components/_components.card.scss +2 -2
  37. package/src/styles/99-utilities/_utilities.link.scss +4 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shohojdhara/atomix",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Atomix Design System - A modern component library for web applications",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,4 +1,4 @@
1
- import React, { ReactNode, useId, memo, forwardRef } from 'react';
1
+ import React, { ReactNode, useId, memo, forwardRef, ComponentType } from 'react';
2
2
  import { ACCORDION } from '../../lib/constants/components';
3
3
  import { useAccordion } from '../../lib/composables/useAccordion';
4
4
  import type {
@@ -9,6 +9,19 @@ import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
9
9
 
10
10
  export type AccordionProps = AccordionPropsType;
11
11
 
12
+ // Type aliases for compound component detection
13
+ type ExtendedComponentType<P = {}> = ComponentType<P> & {
14
+ displayName?: string;
15
+ }
16
+
17
+ // Props interface for type-safe props access
18
+ interface AccordionChildProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onClick'> {
19
+ iconPosition?: 'left' | 'right';
20
+ panelRef?: React.RefObject<HTMLDivElement>;
21
+ contentRef?: React.RefObject<HTMLDivElement>;
22
+ onClick?: (e: React.MouseEvent) => void;
23
+ }
24
+
12
25
  // Default icon
13
26
  const DefaultIcon = () => (
14
27
  <i className="c-accordion__icon" aria-hidden="true">
@@ -30,7 +43,8 @@ const DefaultIcon = () => (
30
43
  </i>
31
44
  );
32
45
 
33
- export interface AccordionHeaderProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
46
+ export interface AccordionHeaderProps
47
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
34
48
  title?: ReactNode;
35
49
  icon?: ReactNode;
36
50
  iconPosition?: 'left' | 'right';
@@ -38,10 +52,7 @@ export interface AccordionHeaderProps extends Omit<React.ButtonHTMLAttributes<HT
38
52
  }
39
53
 
40
54
  export const AccordionHeader = forwardRef<HTMLButtonElement, AccordionHeaderProps>(
41
- (
42
- { title, icon, iconPosition = 'right', isOpen, children, className = '', ...props },
43
- ref
44
- ) => {
55
+ ({ title, icon, iconPosition = 'right', isOpen, children, className = '', ...props }, ref) => {
45
56
  // Determine icon to render. Explicit check for undefined to allow null/false to hide icon.
46
57
  const iconElement = icon === undefined ? <DefaultIcon /> : icon;
47
58
 
@@ -67,9 +78,11 @@ export interface AccordionBodyProps extends React.HTMLAttributes<HTMLDivElement>
67
78
  }
68
79
 
69
80
  // Helper to merge refs
70
- function mergeRefs<T = any>(...refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null)[]) {
81
+ function mergeRefs<T = any>(
82
+ ...refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null)[]
83
+ ) {
71
84
  return (node: T) => {
72
- refs.forEach((ref) => {
85
+ refs.forEach(ref => {
73
86
  if (typeof ref === 'function') {
74
87
  ref(node);
75
88
  } else if (ref != null) {
@@ -88,12 +101,9 @@ export const AccordionBody = forwardRef<HTMLDivElement, AccordionBodyProps>(
88
101
  ref={mergedPanelRef}
89
102
  className={className} // Parent injects class names
90
103
  role="region"
91
- {...props}
104
+ {...props} // Spreads id, aria-labelledby, etc.
92
105
  >
93
- <div
94
- className={ACCORDION.SELECTORS.BODY.replace('.', '')}
95
- ref={contentRef}
96
- >
106
+ <div className={ACCORDION.SELECTORS.BODY.replace('.', '')} ref={contentRef}>
97
107
  {children}
98
108
  </div>
99
109
  </div>
@@ -147,9 +157,12 @@ const AccordionImpl = memo(
147
157
  const panelClassNames = ACCORDION.SELECTORS.PANEL.replace('.', '');
148
158
 
149
159
  // Check for compound usage
150
- const hasCompoundComponents = React.Children.toArray(children).some((child) =>
151
- React.isValidElement(child) &&
152
- ['AccordionHeader', 'AccordionBody'].includes((child.type as any).displayName)
160
+ const hasCompoundComponents = React.Children.toArray(children).some(
161
+ child =>
162
+ React.isValidElement(child) &&
163
+ ['AccordionHeader', 'AccordionBody'].includes(
164
+ (child.type as ExtendedComponentType).displayName || ''
165
+ )
153
166
  );
154
167
 
155
168
  const content = (
@@ -160,29 +173,31 @@ const AccordionImpl = memo(
160
173
  {hasCompoundComponents ? (
161
174
  React.Children.map(children, child => {
162
175
  if (React.isValidElement(child)) {
163
- if ((child.type as any).displayName === 'AccordionHeader') {
176
+ const childProps = child.props as AccordionChildProps;
177
+
178
+ if ((child.type as ExtendedComponentType).displayName === 'AccordionHeader') {
164
179
  return React.cloneElement(child, {
165
180
  id: buttonId,
166
- className: `${headerClassNames} ${(child.props as any).className || ''}`.trim(),
167
- onClick: (e: React.MouseEvent) => {
181
+ className: `${headerClassNames} ${childProps.className || ''}`.trim(),
182
+ onClick: (e: React.MouseEvent<HTMLElement>) => {
168
183
  toggle();
169
- (child.props as any).onClick?.(e);
184
+ childProps?.onClick?.(e);
170
185
  },
171
186
  'aria-expanded': state.isOpen,
172
187
  'aria-controls': panelId,
173
188
  'aria-disabled': disabled,
174
189
  disabled: disabled,
175
- iconPosition: (child.props as any).iconPosition || iconPosition,
176
- } as any);
190
+ iconPosition: childProps.iconPosition || iconPosition,
191
+ } as Partial<AccordionHeaderProps>);
177
192
  }
178
- if ((child.type as any).displayName === 'AccordionBody') {
193
+ if ((child.type as ExtendedComponentType).displayName === 'AccordionBody') {
179
194
  return React.cloneElement(child, {
180
195
  id: panelId,
181
- className: `${panelClassNames} ${(child.props as any).className || ''}`.trim(),
196
+ className: `${panelClassNames} ${childProps.className || ''}`.trim(),
182
197
  'aria-labelledby': buttonId,
183
198
  panelRef: panelRef,
184
199
  contentRef: contentRef,
185
- } as any);
200
+ } as Partial<AccordionBodyProps>);
186
201
  }
187
202
  }
188
203
  return child;
@@ -6,6 +6,9 @@ import React, {
6
6
  cloneElement,
7
7
  isValidElement,
8
8
  ElementType,
9
+ ComponentType,
10
+ MouseEvent,
11
+ AnchorHTMLAttributes,
9
12
  } from 'react';
10
13
  import { BREADCRUMB } from '../../lib/constants/components';
11
14
 
@@ -50,6 +53,13 @@ export interface BreadcrumbItemData {
50
53
  // Rename exported type to avoid conflict with the component constant
51
54
  export type BreadcrumbItemType = BreadcrumbItemData;
52
55
 
56
+ // Link component props interface
57
+ interface LinkComponentProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
58
+ href?: string;
59
+ to?: string;
60
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
61
+ }
62
+
53
63
  // Compound Component Props
54
64
  export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLLIElement> {
55
65
  /**
@@ -70,17 +80,17 @@ export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLLIElement>
70
80
  /**
71
81
  * Optional click handler for the link
72
82
  */
73
- onClick?: (event: React.MouseEvent<any>) => void;
83
+ onClick?: (event: MouseEvent<HTMLAnchorElement>) => void;
74
84
 
75
85
  /**
76
86
  * Optional custom link component
77
87
  */
78
- linkAs?: React.ElementType<any>;
88
+ linkAs?: ComponentType<LinkComponentProps>;
79
89
 
80
90
  /**
81
91
  * Link props to pass to the underlying anchor or linkComponent
82
92
  */
83
- linkProps?: Record<string, any>;
93
+ linkProps?: Partial<LinkComponentProps>;
84
94
  }
85
95
 
86
96
  export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
@@ -114,14 +124,14 @@ export const BreadcrumbItem = forwardRef<HTMLLIElement, BreadcrumbItemProps>(
114
124
  </>
115
125
  );
116
126
 
117
- const commonLinkProps = {
127
+ const commonLinkProps: LinkComponentProps = {
118
128
  className: BREADCRUMB.CLASSES.LINK,
119
- onClick: onClick as any,
129
+ onClick: onClick,
120
130
  style, // Apply style to the link as per legacy behavior
121
131
  ...linkProps,
122
132
  };
123
133
 
124
- const LinkComponent = linkAs as React.ComponentType<any>;
134
+ const LinkComponent = linkAs;
125
135
 
126
136
  return (
127
137
  <li ref={ref} className={itemClasses} style={style} {...props}>
@@ -206,7 +216,7 @@ const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbB
206
216
  href={item.href}
207
217
  active={item.active || isLast}
208
218
  icon={item.icon}
209
- onClick={item.onClick as any}
219
+ onClick={item.onClick as (event: MouseEvent<HTMLAnchorElement>) => void}
210
220
  className={item.className}
211
221
  style={item.style}
212
222
  linkAs={linkComponent}
@@ -219,19 +229,18 @@ const BreadcrumbComponent: React.FC<BreadcrumbProps> = memo(function BreadcrumbB
219
229
  // Compound rendering
220
230
  const childrenCount = Children.count(children);
221
231
  content = Children.map(children, (child, index) => {
222
- if (isValidElement(child)) {
232
+ if (isValidElement<BreadcrumbItemProps>(child)) {
223
233
  const isLast = index === childrenCount - 1;
224
- const childProps = child.props as any;
225
234
 
226
- // Extract props from the child element
227
- const { active, linkAs, ...otherProps } = childProps;
235
+ // Extract props from the child element with proper typing
236
+ const { active, linkAs, ...otherProps } = child.props;
228
237
 
229
- const newProps = {
238
+ const newProps: Partial<BreadcrumbItemProps> = {
230
239
  active: active ?? (isLast ? true : undefined),
231
240
  linkAs: linkAs ?? linkComponent,
232
241
  };
233
242
 
234
- return cloneElement(child, newProps as any);
243
+ return cloneElement(child, newProps);
235
244
  }
236
245
  return child;
237
246
  });
@@ -12,7 +12,6 @@ export type ButtonAsProp = {
12
12
  to?: string;
13
13
  href?: string;
14
14
  linkComponent?: React.ElementType;
15
- [key: string]: any;
16
15
  };
17
16
 
18
17
  export const Button = React.memo(
@@ -177,8 +176,8 @@ export const Button = React.memo(
177
176
  ? 'primary'
178
177
  : variant === 'danger'
179
178
  ? 'error'
180
- : (variant as any),
181
- } as any,
179
+ : variant,
180
+ },
182
181
  <Spinner
183
182
  size={spinnerSize}
184
183
  variant={
@@ -187,7 +186,7 @@ export const Button = React.memo(
187
186
  ? 'primary'
188
187
  : variant === 'danger'
189
188
  ? 'error'
190
- : (variant as any)
189
+ : variant
191
190
  }
192
191
  />
193
192
  )}
@@ -339,4 +338,4 @@ Button.displayName = 'Button';
339
338
 
340
339
  export type { ButtonProps };
341
340
 
342
- export default Button;
341
+ export default Button;
@@ -1,9 +1,14 @@
1
- import React, { memo, forwardRef } from 'react';
1
+ import React, { memo, forwardRef, ComponentType } from 'react';
2
2
  import { CalloutProps } from '../../lib/types/components';
3
3
  import { useCallout } from '../../lib/composables/useCallout';
4
4
  import { Icon } from '../Icon/Icon';
5
5
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
6
6
 
7
+ // Type-safe interface for compound component detection
8
+ type ExtendedComponentType<P = {}> = ComponentType<P> & {
9
+ displayName?: string;
10
+ };
11
+
7
12
  // Subcomponents
8
13
  export const CalloutIcon = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
9
14
  ({ children, className = '', ...props }, ref) => (
@@ -90,51 +95,52 @@ type CalloutComponent = React.FC<CalloutProps> & {
90
95
  };
91
96
 
92
97
  const CalloutComponentBase = ({
93
- title,
94
- children,
95
- icon,
96
- variant = 'primary',
97
- onClose,
98
- actions,
99
- compact = false,
100
- isToast = false,
98
+ title,
99
+ children,
100
+ icon,
101
+ variant = 'primary',
102
+ onClose,
103
+ actions,
104
+ compact = false,
105
+ isToast = false,
106
+ glass,
107
+ className,
108
+ style,
109
+ ...props
110
+ }: CalloutProps) => {
111
+ const { generateCalloutClass, handleClose } = useCallout({
112
+ variant,
113
+ compact,
114
+ isToast,
101
115
  glass,
102
116
  className,
103
117
  style,
104
- ...props
105
- }: CalloutProps) => {
106
- const { generateCalloutClass, handleClose } = useCallout({
107
- variant,
108
- compact,
109
- isToast,
110
- glass,
111
- className,
112
- style,
113
- });
114
-
115
- // Determine appropriate ARIA attributes based on variant
116
- const getAriaAttributes = () => {
117
- const baseAttributes: Record<string, string> = {
118
- role: 'region',
119
- };
120
-
121
- // For toast notifications or alerts, use appropriate role and live region
122
- if (isToast) {
123
- baseAttributes.role = 'alert';
124
- baseAttributes['aria-live'] = 'polite';
125
- } else if (['warning', 'error'].includes(variant)) {
126
- baseAttributes.role = 'alert';
127
- baseAttributes['aria-live'] = 'assertive';
128
- } else if (['info', 'success'].includes(variant)) {
129
- baseAttributes.role = 'status';
130
- baseAttributes['aria-live'] = 'polite';
131
- }
132
-
133
- return baseAttributes;
118
+ });
119
+
120
+ // Determine appropriate ARIA attributes based on variant
121
+ const getAriaAttributes = () => {
122
+ const baseAttributes: Record<string, string> = {
123
+ role: 'region',
134
124
  };
135
125
 
136
- // Check for compound usage
137
- const hasCompoundComponents = React.Children.toArray(children).some((child) =>
126
+ // For toast notifications or alerts, use appropriate role and live region
127
+ if (isToast) {
128
+ baseAttributes.role = 'alert';
129
+ baseAttributes['aria-live'] = 'polite';
130
+ } else if (['warning', 'error'].includes(variant)) {
131
+ baseAttributes.role = 'alert';
132
+ baseAttributes['aria-live'] = 'assertive';
133
+ } else if (['info', 'success'].includes(variant)) {
134
+ baseAttributes.role = 'status';
135
+ baseAttributes['aria-live'] = 'polite';
136
+ }
137
+
138
+ return baseAttributes;
139
+ };
140
+
141
+ // Check for compound usage
142
+ const hasCompoundComponents = React.Children.toArray(children).some(
143
+ child =>
138
144
  React.isValidElement(child) &&
139
145
  [
140
146
  'CalloutIcon',
@@ -143,63 +149,40 @@ const CalloutComponentBase = ({
143
149
  'CalloutText',
144
150
  'CalloutActions',
145
151
  'CalloutContent',
146
- ].includes((child.type as any).displayName)
147
- );
148
-
149
- const calloutContent = hasCompoundComponents ? (
150
- children
151
- ) : (
152
- <>
153
- <div className="c-callout__content">
154
- {icon && <div className="c-callout__icon">{icon}</div>}
155
- <div className="c-callout__message">
156
- {title && <div className="c-callout__title">{title}</div>}
157
- {children && <div className="c-callout__text">{children}</div>}
158
- </div>
152
+ ].includes((child.type as ExtendedComponentType).displayName || '')
153
+ );
154
+
155
+ const calloutContent = hasCompoundComponents ? (
156
+ children
157
+ ) : (
158
+ <>
159
+ <div className="c-callout__content">
160
+ {icon && <div className="c-callout__icon">{icon}</div>}
161
+ <div className="c-callout__message">
162
+ {title && <div className="c-callout__title">{title}</div>}
163
+ {children && <div className="c-callout__text">{children}</div>}
159
164
  </div>
165
+ </div>
160
166
 
161
- {actions && <div className="c-callout__actions">{actions}</div>}
162
-
163
- {onClose && (
164
- <button
165
- className="c-callout__close-btn"
166
- onClick={handleClose(onClose)}
167
- aria-label="Close"
168
- >
169
- <Icon name="X" size="md" />
170
- </button>
171
- )}
172
- </>
173
- );
167
+ {actions && <div className="c-callout__actions">{actions}</div>}
168
+
169
+ {onClose && (
170
+ <button className="c-callout__close-btn" onClick={handleClose(onClose)} aria-label="Close">
171
+ <Icon name="X" size="md" />
172
+ </button>
173
+ )}
174
+ </>
175
+ );
176
+
177
+ if (glass) {
178
+ // Default glass settings for callouts
179
+ const defaultGlassProps = {
180
+ displacementScale: 30,
181
+ borderRadius: 8,
182
+ elasticity: 0,
183
+ };
174
184
 
175
- if (glass) {
176
- // Default glass settings for callouts
177
- const defaultGlassProps = {
178
- displacementScale: 30,
179
- borderRadius: 8,
180
- elasticity: 0,
181
- };
182
-
183
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
184
-
185
- return (
186
- <div
187
- className={generateCalloutClass({ variant, compact, isToast, glass, className })}
188
- {...getAriaAttributes()}
189
- {...props}
190
- style={style}
191
- >
192
- <AtomixGlass {...glassProps}>
193
- <div
194
- className="c-callout__glass-content"
195
- style={{ borderRadius: glassProps.borderRadius }}
196
- >
197
- {calloutContent}
198
- </div>
199
- </AtomixGlass>
200
- </div>
201
- );
202
- }
185
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
203
186
 
204
187
  return (
205
188
  <div
@@ -208,9 +191,28 @@ const CalloutComponentBase = ({
208
191
  {...props}
209
192
  style={style}
210
193
  >
211
- {calloutContent}
194
+ <AtomixGlass {...glassProps}>
195
+ <div
196
+ className="c-callout__glass-content"
197
+ style={{ borderRadius: glassProps.borderRadius }}
198
+ >
199
+ {calloutContent}
200
+ </div>
201
+ </AtomixGlass>
212
202
  </div>
213
203
  );
204
+ }
205
+
206
+ return (
207
+ <div
208
+ className={generateCalloutClass({ variant, compact, isToast, glass, className })}
209
+ {...getAriaAttributes()}
210
+ {...props}
211
+ style={style}
212
+ >
213
+ {calloutContent}
214
+ </div>
215
+ );
214
216
  };
215
217
 
216
218
  export const Callout = memo(CalloutComponentBase) as unknown as CalloutComponent;