@shohojdhara/atomix 0.2.7 → 0.2.8

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/CHANGELOG.md +2 -0
  2. package/dist/atomix.css +332 -54
  3. package/dist/atomix.min.css +2 -2
  4. package/dist/index.d.ts +286 -10
  5. package/dist/index.esm.js +451 -124
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +451 -124
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/dist/themes/applemix.css +332 -54
  12. package/dist/themes/applemix.min.css +2 -2
  13. package/dist/themes/boomdevs.css +331 -53
  14. package/dist/themes/boomdevs.min.css +2 -2
  15. package/dist/themes/esrar.css +332 -54
  16. package/dist/themes/esrar.min.css +2 -2
  17. package/dist/themes/flashtrade.css +1636 -512
  18. package/dist/themes/flashtrade.min.css +113 -7
  19. package/dist/themes/mashroom.css +331 -53
  20. package/dist/themes/mashroom.min.css +2 -2
  21. package/dist/themes/shaj-default.css +331 -53
  22. package/dist/themes/shaj-default.min.css +2 -2
  23. package/package.json +1 -1
  24. package/src/components/Button/Button.stories.tsx +174 -0
  25. package/src/components/Button/Button.tsx +238 -78
  26. package/src/components/Card/Card.stories.tsx +202 -0
  27. package/src/components/Card/Card.tsx +248 -77
  28. package/src/components/Form/Input.stories.tsx +228 -2
  29. package/src/components/Tooltip/Tooltip.tsx +68 -66
  30. package/src/lib/composables/useButton.ts +37 -5
  31. package/src/lib/composables/useInput.ts +39 -1
  32. package/src/lib/constants/components.ts +53 -0
  33. package/src/lib/types/components.ts +278 -4
  34. package/src/styles/01-settings/_settings.tooltip.scss +2 -2
  35. package/src/styles/06-components/_components.button.scss +100 -0
  36. package/src/styles/06-components/_components.card.scss +219 -1
  37. package/src/styles/06-components/_components.tooltip.scss +89 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shohojdhara/atomix",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Atomix Design System - A modern component library for web applications",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -56,6 +56,35 @@ const meta = {
56
56
  control: { type: 'boolean' },
57
57
  description: 'Apply glass morphism effect to the button',
58
58
  },
59
+ loading: {
60
+ control: 'boolean',
61
+ description: 'Whether the button is in a loading state',
62
+ },
63
+ loadingText: {
64
+ control: 'text',
65
+ description: 'Custom text to display during loading',
66
+ },
67
+ fullWidth: {
68
+ control: 'boolean',
69
+ description: 'Whether the button should take full width',
70
+ },
71
+ block: {
72
+ control: 'boolean',
73
+ description: 'Whether the button should be block-level',
74
+ },
75
+ active: {
76
+ control: 'boolean',
77
+ description: 'Whether the button is in active state',
78
+ },
79
+ selected: {
80
+ control: 'boolean',
81
+ description: 'Whether the button is selected',
82
+ },
83
+ iconPosition: {
84
+ control: { type: 'select' },
85
+ options: ['start', 'end'],
86
+ description: 'Position of the icon',
87
+ },
59
88
  },
60
89
  } satisfies Meta<typeof Button>;
61
90
 
@@ -1161,3 +1190,148 @@ export const GlassStatesShowcase: Story = {
1161
1190
  </div>
1162
1191
  ),
1163
1192
  };
1193
+
1194
+ // Loading State
1195
+ export const Loading: Story = {
1196
+ args: {
1197
+ label: 'Save',
1198
+ variant: 'primary',
1199
+ loading: true,
1200
+ },
1201
+ };
1202
+
1203
+ export const LoadingWithText: Story = {
1204
+ args: {
1205
+ label: 'Save',
1206
+ variant: 'primary',
1207
+ loading: true,
1208
+ loadingText: 'Saving...',
1209
+ },
1210
+ };
1211
+
1212
+ export const LoadingStates: Story = {
1213
+ render: () => (
1214
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
1215
+ <Button label="Loading Primary" variant="primary" loading />
1216
+ <Button label="Loading Secondary" variant="secondary" loading />
1217
+ <Button label="Saving..." variant="success" loading loadingText="Saving..." />
1218
+ <Button label="Deleting..." variant="error" loading loadingText="Deleting..." />
1219
+ </div>
1220
+ ),
1221
+ };
1222
+
1223
+ // Full Width
1224
+ export const FullWidth: Story = {
1225
+ args: {
1226
+ label: 'Full Width Button',
1227
+ variant: 'primary',
1228
+ fullWidth: true,
1229
+ },
1230
+ };
1231
+
1232
+ export const FullWidthButtons: Story = {
1233
+ render: () => (
1234
+ <div style={{ width: '400px', display: 'flex', flexDirection: 'column', gap: '1rem' }}>
1235
+ <Button label="Full Width Primary" variant="primary" fullWidth />
1236
+ <Button label="Full Width Secondary" variant="secondary" fullWidth />
1237
+ <Button label="Full Width Loading" variant="success" fullWidth loading />
1238
+ </div>
1239
+ ),
1240
+ };
1241
+
1242
+ // Block
1243
+ export const Block: Story = {
1244
+ args: {
1245
+ label: 'Block Button',
1246
+ variant: 'primary',
1247
+ block: true,
1248
+ },
1249
+ };
1250
+
1251
+ // Icon Positioning
1252
+ export const IconStart: Story = {
1253
+ args: {
1254
+ label: 'Icon Start',
1255
+ variant: 'primary',
1256
+ icon: <Icon />,
1257
+ iconPosition: 'start',
1258
+ },
1259
+ };
1260
+
1261
+ export const IconEnd: Story = {
1262
+ args: {
1263
+ label: 'Icon End',
1264
+ variant: 'primary',
1265
+ icon: <Icon />,
1266
+ iconPosition: 'end',
1267
+ },
1268
+ };
1269
+
1270
+ export const IconPositions: Story = {
1271
+ render: () => (
1272
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
1273
+ <Button label="Icon Start" variant="primary" icon={<Icon />} iconPosition="start" />
1274
+ <Button label="Icon End" variant="secondary" icon={<Icon />} iconPosition="end" />
1275
+ <Button label="Loading Start" variant="success" loading iconPosition="start" />
1276
+ </div>
1277
+ ),
1278
+ };
1279
+
1280
+ // Active State
1281
+ export const Active: Story = {
1282
+ args: {
1283
+ label: 'Active Button',
1284
+ variant: 'primary',
1285
+ active: true,
1286
+ },
1287
+ };
1288
+
1289
+ // Selected State
1290
+ export const Selected: Story = {
1291
+ args: {
1292
+ label: 'Selected Button',
1293
+ variant: 'primary',
1294
+ selected: true,
1295
+ },
1296
+ };
1297
+
1298
+ // Comprehensive Example
1299
+ export const Comprehensive: Story = {
1300
+ render: () => (
1301
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', maxWidth: '600px' }}>
1302
+ <div>
1303
+ <h3 style={{ marginBottom: '1rem' }}>Loading States</h3>
1304
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
1305
+ <Button label="Save" variant="primary" loading />
1306
+ <Button label="Delete" variant="error" loading loadingText="Deleting..." />
1307
+ </div>
1308
+ </div>
1309
+
1310
+ <div>
1311
+ <h3 style={{ marginBottom: '1rem' }}>Layout Variants</h3>
1312
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
1313
+ <Button label="Full Width Button" variant="primary" fullWidth />
1314
+ <Button label="Block Button" variant="secondary" block />
1315
+ </div>
1316
+ </div>
1317
+
1318
+ <div>
1319
+ <h3 style={{ marginBottom: '1rem' }}>Icon Positioning</h3>
1320
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
1321
+ <Button label="Icon Start" variant="primary" icon={<Icon />} iconPosition="start" />
1322
+ <Button label="Icon End" variant="secondary" icon={<Icon />} iconPosition="end" />
1323
+ </div>
1324
+ </div>
1325
+
1326
+ <div>
1327
+ <h3 style={{ marginBottom: '1rem' }}>States</h3>
1328
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
1329
+ <Button label="Active" variant="primary" active />
1330
+ <Button label="Selected" variant="success" selected />
1331
+ <Button label="Loading" variant="info" loading />
1332
+ <Button label="Disabled" variant="warning" disabled />
1333
+ </div>
1334
+ </div>
1335
+ </div>
1336
+ ),
1337
+ };
@@ -1,7 +1,9 @@
1
- import { ElementType, forwardRef } from 'react';
1
+ import React, { ElementType, forwardRef, useCallback, useMemo } from 'react';
2
2
  import { useButton } from '../../lib/composables/useButton';
3
3
  import { ButtonProps } from '../../lib/types/components';
4
4
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
5
+ import { Spinner } from '../Spinner/Spinner';
6
+ import { BUTTON } from '../../lib/constants/components';
5
7
 
6
8
  export type ButtonAsProp = {
7
9
  as?: ElementType;
@@ -10,85 +12,243 @@ export type ButtonAsProp = {
10
12
  [key: string]: any;
11
13
  };
12
14
 
13
- export const Button = forwardRef<HTMLButtonElement, ButtonProps & ButtonAsProp>(
14
- (
15
- {
16
- label,
17
- children,
18
- onClick,
19
- variant = 'primary',
20
- size = 'md',
21
- disabled = false,
22
- icon,
23
- iconOnly = false,
24
- rounded = false,
25
- className = '',
26
- as: Component = 'button',
27
- glass,
28
- style,
29
- ...props
30
- },
31
- ref
32
- ) => {
33
- const { generateButtonClass, handleClick } = useButton({
34
- variant,
35
- size,
36
- disabled,
37
- rounded,
38
- glass,
39
- });
40
-
41
- const buttonClass = generateButtonClass({
42
- variant,
43
- size,
44
- disabled,
45
- rounded,
46
- iconOnly,
47
- glass,
48
- className,
49
- });
50
-
51
- // Custom styles for glass effect
52
- const glassStyles = glass ? {} : {};
53
-
54
- // Handle the case when the button is rendered as a link or another component
55
- const buttonProps = {
56
- ref,
57
- className: buttonClass,
58
- onClick: handleClick(onClick),
59
- disabled,
60
- 'aria-disabled': disabled,
61
- style: glass ? { ...glassStyles, ...style } : style,
62
- ...props,
63
- };
64
-
65
- const buttonContent = (
66
- <>
67
- {icon && <span className="c-btn__icon">{icon}</span>}
68
- {!iconOnly && <span className="c-btn__label">{label || children}</span>}
69
- </>
70
- );
71
-
72
- if (glass) {
73
- // Default glass settings for buttons
74
- const defaultGlassProps = {
75
- displacementScale: 20,
76
- blurAmount: 0,
77
- saturation: 200,
78
- elasticity: 0,
79
- };
80
-
81
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
82
-
83
- return (
84
- <AtomixGlass {...glassProps}>
85
- <Component {...buttonProps}>{buttonContent}</Component>
86
- </AtomixGlass>
15
+ export const Button = React.memo(
16
+ forwardRef<HTMLButtonElement, ButtonProps & ButtonAsProp>(
17
+ (
18
+ {
19
+ label,
20
+ children,
21
+ onClick,
22
+ variant = 'primary',
23
+ size = 'md',
24
+ disabled = false,
25
+ loading = false,
26
+ loadingText,
27
+ icon,
28
+ iconPosition = 'start',
29
+ iconOnly = false,
30
+ rounded = false,
31
+ fullWidth = false,
32
+ block = false,
33
+ active = false,
34
+ selected = false,
35
+ type = 'button',
36
+ className = '',
37
+ as: Component = 'button',
38
+ glass,
39
+ onHover,
40
+ onFocus,
41
+ onBlur,
42
+ ariaLabel,
43
+ ariaDescribedBy,
44
+ ariaExpanded,
45
+ ariaControls,
46
+ tabIndex,
47
+ style,
48
+ ...props
49
+ },
50
+ ref
51
+ ) => {
52
+ const isDisabled = disabled || loading;
53
+
54
+ const { generateButtonClass, handleClick } = useButton({
55
+ variant,
56
+ size,
57
+ disabled: isDisabled,
58
+ rounded,
59
+ glass,
60
+ loading,
61
+ fullWidth,
62
+ block,
63
+ active,
64
+ selected,
65
+ });
66
+
67
+ const buttonClass = useMemo(
68
+ () =>
69
+ generateButtonClass({
70
+ variant,
71
+ size,
72
+ disabled: isDisabled,
73
+ rounded,
74
+ iconOnly,
75
+ glass,
76
+ loading,
77
+ fullWidth,
78
+ block,
79
+ active,
80
+ selected,
81
+ className,
82
+ }),
83
+ [variant, size, isDisabled, rounded, iconOnly, glass, loading, fullWidth, block, active, selected, className, generateButtonClass]
87
84
  );
88
- }
89
85
 
90
- return <Component {...buttonProps}>{buttonContent}</Component>;
91
- }
86
+ // Handle click with loading check
87
+ const handleClickEvent = useCallback(
88
+ (event: React.MouseEvent<HTMLButtonElement>) => {
89
+ if (isDisabled) {
90
+ event.preventDefault();
91
+ return;
92
+ }
93
+ onClick?.(event);
94
+ },
95
+ [isDisabled, onClick]
96
+ );
97
+
98
+ // Handle hover
99
+ const handleMouseEnter = useCallback(
100
+ (event: React.MouseEvent<HTMLButtonElement>) => {
101
+ if (!isDisabled) {
102
+ onHover?.(event);
103
+ }
104
+ },
105
+ [isDisabled, onHover]
106
+ );
107
+
108
+ // Handle focus
109
+ const handleFocusEvent = useCallback(
110
+ (event: React.FocusEvent<HTMLButtonElement>) => {
111
+ if (!isDisabled) {
112
+ onFocus?.(event);
113
+ }
114
+ },
115
+ [isDisabled, onFocus]
116
+ );
117
+
118
+ // Handle blur
119
+ const handleBlurEvent = useCallback(
120
+ (event: React.FocusEvent<HTMLButtonElement>) => {
121
+ if (!isDisabled) {
122
+ onBlur?.(event);
123
+ }
124
+ },
125
+ [isDisabled, onBlur]
126
+ );
127
+
128
+ // Determine button text
129
+ const buttonText = useMemo(() => {
130
+ if (loading && loadingText) return loadingText;
131
+ if (loading && !loadingText) return label || children;
132
+ return label || children;
133
+ }, [loading, loadingText, label, children]);
134
+
135
+ // Determine spinner size based on button size
136
+ const spinnerSize = useMemo(() => {
137
+ if (size === 'sm') return 'sm';
138
+ if (size === 'lg') return 'md';
139
+ return 'sm';
140
+ }, [size]);
141
+
142
+ // Button content with icon positioning
143
+ const buttonContent = useMemo(() => {
144
+ const iconElement = icon && !loading && (
145
+ <span className={BUTTON.ICON_CLASS} aria-hidden="true">
146
+ {icon}
147
+ </span>
148
+ );
149
+
150
+ const spinnerElement = loading && (
151
+ <span className={BUTTON.SPINNER_CLASS} aria-hidden="true">
152
+ <Spinner
153
+ size={spinnerSize}
154
+ variant={
155
+ variant === 'link' || (typeof variant === 'string' && variant.startsWith('outline-'))
156
+ ? 'primary'
157
+ : (variant === 'danger' ? 'error' : (variant as any))
158
+ }
159
+ />
160
+ </span>
161
+ );
162
+
163
+ const labelElement = !iconOnly && buttonText && (
164
+ <span className={BUTTON.LABEL_CLASS}>{buttonText}</span>
165
+ );
166
+
167
+ if (iconPosition === 'end') {
168
+ return (
169
+ <>
170
+ {labelElement}
171
+ {spinnerElement}
172
+ {iconElement}
173
+ </>
174
+ );
175
+ }
176
+
177
+ return (
178
+ <>
179
+ {spinnerElement}
180
+ {iconElement}
181
+ {labelElement}
182
+ </>
183
+ );
184
+ }, [icon, iconPosition, iconOnly, buttonText, loading, spinnerSize, variant]);
185
+
186
+ // Button props
187
+ const buttonProps = useMemo(
188
+ () => ({
189
+ ref,
190
+ className: buttonClass,
191
+ type: Component === 'button' ? type : undefined,
192
+ onClick: handleClickEvent,
193
+ onMouseEnter: onHover ? handleMouseEnter : undefined,
194
+ onFocus: onFocus ? handleFocusEvent : undefined,
195
+ onBlur: onBlur ? handleBlurEvent : undefined,
196
+ disabled: isDisabled && Component === 'button',
197
+ 'aria-disabled': isDisabled,
198
+ 'aria-busy': loading,
199
+ 'aria-label': ariaLabel || (iconOnly ? label || children : undefined),
200
+ 'aria-describedby': ariaDescribedBy,
201
+ 'aria-expanded': ariaExpanded,
202
+ 'aria-controls': ariaControls,
203
+ tabIndex: tabIndex !== undefined ? tabIndex : (isDisabled ? -1 : 0),
204
+ style,
205
+ ...props,
206
+ }),
207
+ [
208
+ ref,
209
+ buttonClass,
210
+ Component,
211
+ type,
212
+ handleClickEvent,
213
+ handleMouseEnter,
214
+ handleFocusEvent,
215
+ handleBlurEvent,
216
+ isDisabled,
217
+ loading,
218
+ ariaLabel,
219
+ iconOnly,
220
+ label,
221
+ children,
222
+ ariaDescribedBy,
223
+ ariaExpanded,
224
+ ariaControls,
225
+ tabIndex,
226
+ style,
227
+ props,
228
+ ]
229
+ );
230
+
231
+ if (glass) {
232
+ // Default glass settings for buttons
233
+ const defaultGlassProps = {
234
+ displacementScale: 20,
235
+ blurAmount: 0,
236
+ saturation: 200,
237
+ elasticity: 0,
238
+ };
239
+
240
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
241
+
242
+ return (
243
+ <AtomixGlass {...glassProps}>
244
+ <Component {...buttonProps}>{buttonContent}</Component>
245
+ </AtomixGlass>
246
+ );
247
+ }
248
+
249
+ return <Component {...buttonProps}>{buttonContent}</Component>;
250
+ }
251
+ )
92
252
  );
93
253
 
94
254
  Button.displayName = 'Button';