@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
@@ -19,9 +19,29 @@ const meta: Meta<typeof Card> = {
19
19
  image: { control: 'text' },
20
20
  imageAlt: { control: 'text' },
21
21
  footer: { control: 'text' },
22
+ size: {
23
+ control: 'select',
24
+ options: ['sm', 'md', 'lg'],
25
+ },
26
+ variant: {
27
+ control: 'select',
28
+ options: ['primary', 'secondary', 'success', 'error', 'warning', 'info', 'light', 'dark'],
29
+ },
30
+ appearance: {
31
+ control: 'select',
32
+ options: ['filled', 'outlined', 'ghost', 'elevated'],
33
+ },
34
+ elevation: {
35
+ control: 'select',
36
+ options: ['none', 'sm', 'md', 'lg', 'xl'],
37
+ },
22
38
  row: { control: 'boolean' },
23
39
  flat: { control: 'boolean' },
24
40
  active: { control: 'boolean' },
41
+ disabled: { control: 'boolean' },
42
+ loading: { control: 'boolean' },
43
+ selected: { control: 'boolean' },
44
+ interactive: { control: 'boolean' },
25
45
  className: { control: 'text' },
26
46
  },
27
47
  };
@@ -806,3 +826,185 @@ export const GlassCardLayouts: Story = {
806
826
  </div>
807
827
  ),
808
828
  };
829
+
830
+ // Size Variants
831
+ export const SizeVariants: Story = {
832
+ render: () => (
833
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
834
+ <Card
835
+ size="sm"
836
+ title="Small Card"
837
+ text="This is a small card with compact spacing."
838
+ actions={<button className="c-btn c-btn--primary c-btn--sm">Action</button>}
839
+ />
840
+ <Card
841
+ size="md"
842
+ title="Medium Card"
843
+ text="This is a medium card with default spacing."
844
+ actions={<button className="c-btn c-btn--primary c-btn--sm">Action</button>}
845
+ />
846
+ <Card
847
+ size="lg"
848
+ title="Large Card"
849
+ text="This is a large card with spacious padding."
850
+ actions={<button className="c-btn c-btn--primary c-btn--sm">Action</button>}
851
+ />
852
+ </div>
853
+ ),
854
+ };
855
+
856
+ // Color Variants
857
+ export const ColorVariants: Story = {
858
+ render: () => (
859
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '1rem' }}>
860
+ <Card variant="primary" title="Primary Card" text="Primary variant card." />
861
+ <Card variant="secondary" title="Secondary Card" text="Secondary variant card." />
862
+ <Card variant="success" title="Success Card" text="Success variant card." />
863
+ <Card variant="error" title="Error Card" text="Error variant card." />
864
+ <Card variant="warning" title="Warning Card" text="Warning variant card." />
865
+ <Card variant="info" title="Info Card" text="Info variant card." />
866
+ <Card variant="light" title="Light Card" text="Light variant card." />
867
+ <Card variant="dark" title="Dark Card" text="Dark variant card." />
868
+ </div>
869
+ ),
870
+ };
871
+
872
+ // Appearance Variants
873
+ export const AppearanceVariants: Story = {
874
+ render: () => (
875
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '1rem' }}>
876
+ <Card appearance="filled" variant="primary" title="Filled Card" text="Filled appearance with solid background." />
877
+ <Card appearance="outlined" variant="primary" title="Outlined Card" text="Outlined appearance with border only." />
878
+ <Card appearance="ghost" variant="primary" title="Ghost Card" text="Ghost appearance with minimal styling." />
879
+ <Card appearance="elevated" variant="primary" title="Elevated Card" text="Elevated appearance with shadow." />
880
+ </div>
881
+ ),
882
+ };
883
+
884
+ // Elevation Levels
885
+ export const ElevationLevels: Story = {
886
+ render: () => (
887
+ <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
888
+ <Card elevation="none" title="No Elevation" text="Card with no shadow." />
889
+ <Card elevation="sm" title="Small Elevation" text="Card with small shadow." />
890
+ <Card elevation="md" title="Medium Elevation" text="Card with medium shadow." />
891
+ <Card elevation="lg" title="Large Elevation" text="Card with large shadow." />
892
+ <Card elevation="xl" title="Extra Large Elevation" text="Card with extra large shadow." />
893
+ </div>
894
+ ),
895
+ };
896
+
897
+ // Disabled State
898
+ export const Disabled: Story = {
899
+ args: {
900
+ title: 'Disabled Card',
901
+ text: 'This card is disabled and cannot be interacted with.',
902
+ disabled: true,
903
+ actions: (
904
+ <React.Fragment>
905
+ <button className="c-btn c-btn--primary c-btn--sm">Action</button>
906
+ </React.Fragment>
907
+ ),
908
+ },
909
+ };
910
+
911
+ // Loading State
912
+ export const Loading: Story = {
913
+ args: {
914
+ title: 'Loading Card',
915
+ text: 'This card is in a loading state.',
916
+ loading: true,
917
+ actions: (
918
+ <React.Fragment>
919
+ <button className="c-btn c-btn--primary c-btn--sm">Action</button>
920
+ </React.Fragment>
921
+ ),
922
+ },
923
+ };
924
+
925
+ // Selected State
926
+ export const Selected: Story = {
927
+ args: {
928
+ title: 'Selected Card',
929
+ text: 'This card is in a selected state.',
930
+ selected: true,
931
+ variant: 'primary',
932
+ },
933
+ };
934
+
935
+ // Interactive Card
936
+ export const Interactive: Story = {
937
+ args: {
938
+ title: 'Interactive Card',
939
+ text: 'This card is interactive and responds to hover and click.',
940
+ interactive: true,
941
+ onClick: () => alert('Interactive card clicked!'),
942
+ variant: 'primary',
943
+ elevation: 'md',
944
+ },
945
+ };
946
+
947
+ // Link Card
948
+ export const LinkCard: Story = {
949
+ args: {
950
+ title: 'Link Card',
951
+ text: 'This card acts as a link. Click to navigate.',
952
+ href: 'https://example.com',
953
+ target: '_blank',
954
+ variant: 'primary',
955
+ interactive: true,
956
+ },
957
+ };
958
+
959
+ // Comprehensive Example
960
+ export const Comprehensive: Story = {
961
+ render: () => (
962
+ <Container>
963
+ <Grid>
964
+ <GridCol sm={6} lg={4}>
965
+ <Card
966
+ size="lg"
967
+ variant="primary"
968
+ appearance="elevated"
969
+ elevation="lg"
970
+ title="Premium Feature"
971
+ text="This is a comprehensive card example with all new features enabled."
972
+ image="https://placehold.co/400x200"
973
+ imageAlt="Feature image"
974
+ interactive
975
+ onClick={() => alert('Card clicked!')}
976
+ actions={
977
+ <React.Fragment>
978
+ <button className="c-btn c-btn--primary c-btn--sm">Get Started</button>
979
+ <button className="c-btn c-btn--outline-primary c-btn--sm">Learn More</button>
980
+ </React.Fragment>
981
+ }
982
+ />
983
+ </GridCol>
984
+ <GridCol sm={6} lg={4}>
985
+ <Card
986
+ size="md"
987
+ variant="success"
988
+ appearance="outlined"
989
+ elevation="md"
990
+ title="Success Card"
991
+ text="This card uses the success variant with outlined appearance."
992
+ selected
993
+ actions={<button className="c-btn c-btn--success c-btn--sm">Action</button>}
994
+ />
995
+ </GridCol>
996
+ <GridCol sm={6} lg={4}>
997
+ <Card
998
+ size="sm"
999
+ variant="error"
1000
+ appearance="ghost"
1001
+ title="Error Card"
1002
+ text="This is a small error card with ghost appearance."
1003
+ disabled
1004
+ actions={<button className="c-btn c-btn--error c-btn--sm">Action</button>}
1005
+ />
1006
+ </GridCol>
1007
+ </Grid>
1008
+ </Container>
1009
+ ),
1010
+ };
@@ -1,96 +1,267 @@
1
- import React, { forwardRef, Ref } from 'react';
1
+ import React, { forwardRef, useCallback, useMemo } from 'react';
2
2
  import { CARD } from '../../lib/constants/components';
3
3
  import { CardProps } from '../../lib/types/components';
4
4
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
5
5
 
6
- export const Card = forwardRef<HTMLDivElement, CardProps>(
7
- (
8
- {
9
- header,
10
- image,
11
- imageAlt = '',
12
- title,
13
- text,
14
- actions,
15
- icon,
16
- footer,
17
- row = false,
18
- flat = false,
19
- active = false,
20
- className = '',
21
- children,
22
- onClick,
23
- style,
24
- glass,
25
- ...rest
26
- },
27
- ref
28
- ) => {
29
- const cardClasses = [
30
- CARD.CLASSES.BASE,
31
- row ? CARD.CLASSES.ROW : '',
32
- flat ? CARD.CLASSES.FLAT : '',
33
- active ? CARD.CLASSES.ACTIVE : '',
34
- className,
35
- ]
36
- .filter(Boolean)
37
- .join(' ');
38
-
39
- const cardContent = (
40
- <>
41
- {(image || icon || header) && (
42
- <div className={CARD.SELECTORS.HEADER.substring(1)}>
43
- {header}
44
- {image && (
45
- <img src={image} alt={imageAlt} className={CARD.SELECTORS.IMAGE.substring(1)} />
46
- )}
6
+ export const Card = React.memo(
7
+ forwardRef<HTMLDivElement | HTMLAnchorElement, CardProps>(
8
+ (
9
+ {
10
+ // Variants
11
+ size = 'md',
12
+ variant = 'primary',
13
+ appearance = 'filled',
14
+ elevation = 'none',
15
+ // Layout
16
+ row = false,
17
+ flat = false,
18
+ // States
19
+ active = false,
20
+ disabled = false,
21
+ loading = false,
22
+ selected = false,
23
+ interactive = false,
24
+ // Content
25
+ header,
26
+ image,
27
+ imageAlt = '',
28
+ title,
29
+ text,
30
+ actions,
31
+ icon,
32
+ footer,
33
+ children,
34
+ // Interaction
35
+ onClick,
36
+ onHover,
37
+ onFocus,
38
+ href,
39
+ target,
40
+ // Glass
41
+ glass,
42
+ // Accessibility
43
+ role,
44
+ ariaLabel,
45
+ ariaDescribedBy,
46
+ tabIndex,
47
+ // Styling
48
+ className = '',
49
+ style,
50
+ ...rest
51
+ },
52
+ ref
53
+ ) => {
54
+ // Determine if card is clickable/interactive
55
+ const isClickable = Boolean(onClick || href || interactive);
56
+ const isDisabled = disabled || loading;
57
+
58
+ // Build CSS classes using BEM methodology
59
+ const cardClasses = useMemo(
60
+ () =>
61
+ [
62
+ CARD.CLASSES.BASE,
63
+ // Size modifiers
64
+ size === 'sm' ? CARD.CLASSES.SM : '',
65
+ size === 'md' ? CARD.CLASSES.MD : '',
66
+ size === 'lg' ? CARD.CLASSES.LG : '',
67
+ // Variant modifiers (will be handled in SCSS with @each)
68
+ variant ? `c-card--${variant}` : '',
69
+ // Appearance modifiers
70
+ appearance === 'filled' ? CARD.CLASSES.FILLED : '',
71
+ appearance === 'outlined' ? CARD.CLASSES.OUTLINED : '',
72
+ appearance === 'ghost' ? CARD.CLASSES.GHOST : '',
73
+ appearance === 'elevated' ? CARD.CLASSES.ELEVATED : '',
74
+ // Elevation modifiers
75
+ elevation === 'none' ? CARD.CLASSES.ELEVATION_NONE : '',
76
+ elevation === 'sm' ? CARD.CLASSES.ELEVATION_SM : '',
77
+ elevation === 'md' ? CARD.CLASSES.ELEVATION_MD : '',
78
+ elevation === 'lg' ? CARD.CLASSES.ELEVATION_LG : '',
79
+ elevation === 'xl' ? CARD.CLASSES.ELEVATION_XL : '',
80
+ // Layout modifiers
81
+ row ? CARD.CLASSES.ROW : '',
82
+ flat ? CARD.CLASSES.FLAT : '',
83
+ // State modifiers
84
+ active ? CARD.CLASSES.ACTIVE : '',
85
+ disabled ? CARD.CLASSES.DISABLED : '',
86
+ loading ? CARD.CLASSES.LOADING : '',
87
+ selected ? CARD.CLASSES.SELECTED : '',
88
+ interactive || isClickable ? CARD.CLASSES.INTERACTIVE : '',
89
+ glass ? CARD.CLASSES.GLASS : '',
90
+ className,
91
+ ]
92
+ .filter(Boolean)
93
+ .join(' '),
94
+ [size, variant, appearance, elevation, row, flat, active, disabled, loading, selected, interactive, isClickable, glass, className]
95
+ );
96
+
97
+ // Determine ARIA role
98
+ const cardRole = useMemo(() => {
99
+ if (role) return role;
100
+ if (href) return 'link';
101
+ if (isClickable) return 'button';
102
+ return 'article';
103
+ }, [role, href, isClickable]);
104
+
105
+ // Handle click events
106
+ const handleClick = useCallback(
107
+ (event: React.MouseEvent<HTMLDivElement | HTMLAnchorElement>) => {
108
+ if (isDisabled) {
109
+ event.preventDefault();
110
+ return;
111
+ }
112
+ onClick?.(event);
113
+ },
114
+ [isDisabled, onClick]
115
+ );
116
+
117
+ // Handle keyboard events for accessibility
118
+ const handleKeyDown = useCallback(
119
+ (event: React.KeyboardEvent<HTMLDivElement | HTMLAnchorElement>) => {
120
+ if (isDisabled) {
121
+ event.preventDefault();
122
+ return;
123
+ }
124
+
125
+ // Enter or Space activates clickable cards
126
+ if (isClickable && (event.key === 'Enter' || event.key === ' ')) {
127
+ event.preventDefault();
128
+ if (onClick) {
129
+ onClick(event as unknown as React.MouseEvent<HTMLDivElement | HTMLAnchorElement>);
130
+ }
131
+ // If href is provided, the anchor will handle navigation
132
+ }
133
+ },
134
+ [isDisabled, isClickable, onClick]
135
+ );
136
+
137
+ // Handle hover events
138
+ const handleMouseEnter = useCallback(
139
+ (event: React.MouseEvent<HTMLDivElement | HTMLAnchorElement>) => {
140
+ if (!isDisabled) {
141
+ onHover?.(event);
142
+ }
143
+ },
144
+ [isDisabled, onHover]
145
+ );
146
+
147
+ // Handle focus events
148
+ const handleFocusEvent = useCallback(
149
+ (event: React.FocusEvent<HTMLDivElement | HTMLAnchorElement>) => {
150
+ if (!isDisabled) {
151
+ onFocus?.(event);
152
+ }
153
+ },
154
+ [isDisabled, onFocus]
155
+ );
156
+
157
+ // Determine tab index
158
+ const effectiveTabIndex = useMemo(() => {
159
+ if (tabIndex !== undefined) return tabIndex;
160
+ if (isDisabled) return -1;
161
+ if (isClickable) return 0;
162
+ return undefined;
163
+ }, [tabIndex, isDisabled, isClickable]);
47
164
 
48
- {icon && <div className={CARD.SELECTORS.ICON.substring(1)}>{icon}</div>}
49
- </div>
50
- )}
165
+ // Card content structure
166
+ const cardContent = useMemo(
167
+ () => (
168
+ <>
169
+ {loading && (
170
+ <div className="c-card__loading" aria-label="Loading">
171
+ <div className="c-card__spinner" />
172
+ </div>
173
+ )}
51
174
 
52
- <div className={CARD.SELECTORS.BODY.substring(1)}>
53
- {title && <h3 className={CARD.SELECTORS.TITLE.substring(1)}>{title}</h3>}
175
+ {(image || icon || header) && (
176
+ <div className={CARD.SELECTORS.HEADER.substring(1)}>
177
+ {header}
178
+ {image && (
179
+ <img
180
+ src={image}
181
+ alt={imageAlt}
182
+ className={CARD.SELECTORS.IMAGE.substring(1)}
183
+ loading="lazy"
184
+ />
185
+ )}
186
+ {icon && <div className={CARD.SELECTORS.ICON.substring(1)}>{icon}</div>}
187
+ </div>
188
+ )}
54
189
 
55
- {text && <p className={CARD.SELECTORS.TEXT.substring(1)}>{text}</p>}
190
+ <div className={CARD.SELECTORS.BODY.substring(1)}>
191
+ {title && <h3 className={CARD.SELECTORS.TITLE.substring(1)}>{title}</h3>}
192
+ {text && <p className={CARD.SELECTORS.TEXT.substring(1)}>{text}</p>}
193
+ {children}
194
+ </div>
56
195
 
57
- {children}
58
- </div>
196
+ {actions && <div className={CARD.SELECTORS.ACTIONS.substring(1)}>{actions}</div>}
59
197
 
60
- {actions && <div className={CARD.SELECTORS.ACTIONS.substring(1)}>{actions}</div>}
198
+ {footer && <div className={CARD.SELECTORS.FOOTER.substring(1)}>{footer}</div>}
199
+ </>
200
+ ),
201
+ [loading, image, imageAlt, icon, header, title, text, children, actions, footer]
202
+ );
61
203
 
62
- {footer && <div className={CARD.SELECTORS.FOOTER.substring(1)}>{footer}</div>}
63
- </>
64
- );
204
+ // Common props for both div and anchor
205
+ const commonProps = {
206
+ ref: ref as React.Ref<HTMLDivElement | HTMLAnchorElement>,
207
+ className: cardClasses,
208
+ style,
209
+ role: cardRole,
210
+ 'aria-label': ariaLabel,
211
+ 'aria-describedby': ariaDescribedBy,
212
+ 'aria-disabled': isDisabled ? true : undefined,
213
+ tabIndex: effectiveTabIndex,
214
+ onClick: isClickable ? handleClick : undefined,
215
+ onKeyDown: isClickable ? handleKeyDown : undefined,
216
+ onMouseEnter: onHover ? handleMouseEnter : undefined,
217
+ onFocus: onFocus ? handleFocusEvent : undefined,
218
+ ...rest,
219
+ };
65
220
 
66
- if (glass) {
67
- const glassProps = glass === true ? {} : glass;
68
- return (
69
- <AtomixGlass {...{ ...glassProps, elasticity: 0 }}>
70
- {' '}
71
- <div
72
- ref={ref}
73
- className={cardClasses + ' c-card--glass'}
74
- onClick={onClick}
75
- {...rest}
76
- style={{ ...style }}
221
+ // Render as anchor if href is provided
222
+ if (href && !isDisabled) {
223
+ const anchorElement = (
224
+ <a
225
+ {...commonProps}
226
+ href={href}
227
+ target={target}
228
+ rel={target === '_blank' ? 'noopener noreferrer' : undefined}
77
229
  >
78
230
  {cardContent}
79
- </div>
80
- </AtomixGlass>
81
- );
82
- }
231
+ </a>
232
+ );
83
233
 
84
- return (
85
- <div ref={ref} className={cardClasses} onClick={onClick} {...rest} style={{ ...style }}>
86
- {cardContent}
87
- </div>
88
- );
89
- }
90
- );
234
+ if (glass) {
235
+ const glassProps = glass === true ? {} : glass;
236
+ return (
237
+ <AtomixGlass {...{ ...glassProps, elasticity: 0 }}>
238
+ {anchorElement}
239
+ </AtomixGlass>
240
+ );
241
+ }
91
242
 
92
- export type { CardProps };
243
+ return anchorElement;
244
+ }
245
+
246
+ // Render as div
247
+ const divElement = <div {...commonProps}>{cardContent}</div>;
248
+
249
+ if (glass) {
250
+ const glassProps = glass === true ? {} : glass;
251
+ return (
252
+ <AtomixGlass {...{ ...glassProps, elasticity: 0 }}>
253
+ {divElement}
254
+ </AtomixGlass>
255
+ );
256
+ }
257
+
258
+ return divElement;
259
+ }
260
+ )
261
+ );
93
262
 
94
263
  Card.displayName = 'Card';
95
264
 
265
+ export type { CardProps };
266
+
96
267
  export default Card;