@idealyst/components 1.2.29 → 1.2.31
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.
- package/README.md +3 -3
- package/package.json +4 -4
- package/plugin/__tests__/web.test.ts +2 -2
- package/plugin/web.js +2 -0
- package/src/Accordion/Accordion.native.tsx +3 -2
- package/src/ActivityIndicator/ActivityIndicator.native.tsx +4 -2
- package/src/ActivityIndicator/ActivityIndicator.styles.tsx +22 -27
- package/src/ActivityIndicator/ActivityIndicator.web.tsx +17 -29
- package/src/Alert/Alert.native.tsx +20 -10
- package/src/Alert/Alert.styles.tsx +173 -86
- package/src/Alert/Alert.web.tsx +34 -30
- package/src/Alert/types.ts +53 -3
- package/src/Avatar/Avatar.native.tsx +3 -2
- package/src/Avatar/Avatar.web.tsx +2 -1
- package/src/Avatar/types.ts +1 -1
- package/src/Badge/Badge.native.tsx +18 -6
- package/src/Badge/Badge.styles.tsx +22 -5
- package/src/Badge/Badge.web.tsx +12 -4
- package/src/Badge/types.ts +14 -2
- package/src/Breadcrumb/Breadcrumb.native.tsx +3 -2
- package/src/Button/Button.native.tsx +16 -6
- package/src/Button/Button.styles.tsx +2 -2
- package/src/Button/Button.web.tsx +19 -15
- package/src/Button/types.ts +6 -10
- package/src/Card/Card.native.tsx +27 -3
- package/src/Card/Card.web.tsx +30 -4
- package/src/Card/types.ts +15 -0
- package/src/Checkbox/Checkbox.native.tsx +5 -4
- package/src/Checkbox/Checkbox.styles.tsx +62 -52
- package/src/Checkbox/Checkbox.web.tsx +4 -3
- package/src/Checkbox/types.ts +1 -1
- package/src/Chip/Chip.native.tsx +30 -7
- package/src/Chip/Chip.web.tsx +28 -5
- package/src/Chip/types.ts +15 -0
- package/src/Dialog/Dialog.native.tsx +6 -6
- package/src/Dialog/Dialog.web.tsx +5 -5
- package/src/Dialog/types.ts +2 -2
- package/src/Divider/Divider.native.tsx +20 -17
- package/src/Divider/Divider.styles.tsx +51 -29
- package/src/Divider/Divider.web.tsx +5 -4
- package/src/Divider/types.ts +3 -3
- package/src/Icon/Icon.native.tsx +3 -2
- package/src/Icon/Icon.web.tsx +2 -1
- package/src/Icon/IconSvg/IconSvg.native.tsx +3 -2
- package/src/IconButton/IconButton.native.tsx +219 -0
- package/src/IconButton/IconButton.styles.tsx +127 -0
- package/src/IconButton/IconButton.web.tsx +198 -0
- package/src/IconButton/index.native.ts +5 -0
- package/src/IconButton/index.ts +5 -0
- package/src/IconButton/index.web.ts +5 -0
- package/src/IconButton/types.ts +84 -0
- package/src/Image/Image.native.tsx +3 -2
- package/src/Input/Input.native.tsx +42 -290
- package/src/Input/Input.styles.tsx +1 -1
- package/src/Input/Input.web.tsx +37 -288
- package/src/Input/index.native.ts +9 -2
- package/src/Input/index.ts +8 -1
- package/src/Input/index.web.ts +8 -1
- package/src/Input/types.ts +1 -1
- package/src/List/List.native.tsx +3 -2
- package/src/List/ListItem.native.tsx +3 -2
- package/src/List/ListSection.native.tsx +3 -2
- package/src/Menu/Menu.native.tsx +2 -1
- package/src/Menu/Menu.styles.tsx +79 -29
- package/src/Menu/Menu.web.tsx +2 -1
- package/src/Menu/MenuItem.native.tsx +4 -3
- package/src/Menu/MenuItem.styles.tsx +81 -32
- package/src/Menu/MenuItem.web.tsx +2 -1
- package/src/Menu/docs.ts +1 -1
- package/src/Popover/Popover.native.tsx +2 -1
- package/src/Popover/Popover.web.tsx +2 -1
- package/src/Popover/types.ts +15 -4
- package/src/Pressable/Pressable.native.tsx +3 -2
- package/src/Pressable/Pressable.web.tsx +3 -5
- package/src/Progress/Progress.native.tsx +5 -4
- package/src/Progress/Progress.web.tsx +3 -3
- package/src/Progress/types.ts +3 -3
- package/src/RadioButton/RadioButton.native.tsx +4 -3
- package/src/RadioButton/RadioButton.styles.tsx +53 -33
- package/src/RadioButton/RadioGroup.native.tsx +3 -2
- package/src/SVGImage/SVGImage.native.tsx +5 -4
- package/src/SVGImage/SVGImage.styles.tsx +44 -10
- package/src/SVGImage/SVGImage.web.tsx +2 -1
- package/src/Screen/Screen.native.tsx +2 -1
- package/src/Screen/Screen.web.tsx +2 -1
- package/src/Select/Select.native.tsx +6 -5
- package/src/Select/Select.styles.tsx +1 -1
- package/src/Select/Select.web.tsx +4 -3
- package/src/Select/types.ts +1 -1
- package/src/Skeleton/Skeleton.native.tsx +2 -1
- package/src/Skeleton/Skeleton.web.tsx +1 -1
- package/src/Slider/Slider.native.tsx +9 -8
- package/src/Slider/Slider.web.tsx +10 -9
- package/src/Slider/types.ts +9 -2
- package/src/Switch/Switch.native.tsx +7 -6
- package/src/Switch/Switch.styles.tsx +52 -17
- package/src/Switch/Switch.web.tsx +15 -16
- package/src/Switch/types.ts +44 -4
- package/src/TabBar/TabBar.native.tsx +3 -2
- package/src/Text/Text.native.tsx +3 -2
- package/src/Text/Text.web.tsx +2 -1
- package/src/TextArea/TextArea.native.tsx +3 -2
- package/src/TextArea/TextArea.styles.tsx +2 -2
- package/src/TextArea/TextArea.web.tsx +2 -1
- package/src/TextInput/TextInput.native.tsx +300 -0
- package/src/TextInput/TextInput.styles.tsx +207 -0
- package/src/TextInput/TextInput.web.tsx +301 -0
- package/src/TextInput/index.native.ts +3 -0
- package/src/TextInput/index.ts +5 -0
- package/src/TextInput/index.web.ts +5 -0
- package/src/TextInput/types.ts +163 -0
- package/src/Tooltip/Tooltip.native.tsx +3 -2
- package/src/Video/Video.native.tsx +4 -3
- package/src/View/View.native.tsx +2 -1
- package/src/View/View.styles.tsx +1 -0
- package/src/View/View.web.tsx +9 -2
- package/src/examples/ActivityIndicatorExamples.tsx +177 -0
- package/src/examples/AlertExamples.tsx +5 -5
- package/src/examples/ButtonExamples.tsx +12 -12
- package/src/examples/CardExamples.tsx +1 -1
- package/src/examples/CheckboxExamples.tsx +2 -2
- package/src/examples/ChipExamples.tsx +6 -6
- package/src/examples/DialogExamples.tsx +1 -1
- package/src/examples/DividerExamples.tsx +1 -1
- package/src/examples/InputExamples.tsx +1 -1
- package/src/examples/LinkExamples.tsx +1 -1
- package/src/examples/ListExamples.tsx +1 -1
- package/src/examples/MenuExamples.tsx +2 -2
- package/src/examples/ProgressExamples.tsx +1 -1
- package/src/examples/RadioButtonExamples.tsx +5 -5
- package/src/examples/SVGImageExamples.tsx +1 -1
- package/src/examples/SelectExamples.tsx +1 -1
- package/src/examples/SliderExamples.tsx +5 -5
- package/src/examples/SwitchExamples.tsx +26 -26
- package/src/examples/TableExamples.tsx +1 -1
- package/src/examples/TooltipExamples.tsx +2 -2
- package/src/examples/index.ts +1 -0
- package/src/extensions/index.ts +1 -0
- package/src/extensions/types.ts +22 -3
- package/src/index.native.ts +4 -0
- package/src/index.ts +27 -2
- package/src/utils/index.ts +12 -0
- package/src/utils/refTypes.ts +50 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IconButton styles using defineStyle with $iterator expansion.
|
|
3
|
+
*
|
|
4
|
+
* Dynamic style functions are used for intent/type combinations since
|
|
5
|
+
* the color depends on both values (compound logic).
|
|
6
|
+
*/
|
|
7
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
8
|
+
import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
|
|
9
|
+
import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
|
|
10
|
+
import { IconButtonGradient } from './types';
|
|
11
|
+
|
|
12
|
+
// Required: Unistyles must see StyleSheet usage in original source to process this file
|
|
13
|
+
void StyleSheet;
|
|
14
|
+
|
|
15
|
+
// Wrap theme for $iterator support
|
|
16
|
+
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
17
|
+
|
|
18
|
+
type IconButtonSize = Size;
|
|
19
|
+
type IconButtonType = 'contained' | 'outlined' | 'text';
|
|
20
|
+
|
|
21
|
+
export type IconButtonVariants = {
|
|
22
|
+
size: IconButtonSize;
|
|
23
|
+
intent: Intent;
|
|
24
|
+
type: IconButtonType;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
gradient?: IconButtonGradient;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* All dynamic props passed to icon button style functions.
|
|
31
|
+
*/
|
|
32
|
+
export type IconButtonDynamicProps = {
|
|
33
|
+
intent?: Intent;
|
|
34
|
+
type?: IconButtonType;
|
|
35
|
+
size?: Size;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
gradient?: IconButtonGradient;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* IconButton styles with $iterator expansion for size variants.
|
|
42
|
+
* Circular button that only contains an icon.
|
|
43
|
+
*/
|
|
44
|
+
export const iconButtonStyles = defineStyle('IconButton', (theme: Theme) => ({
|
|
45
|
+
button: ({ intent = 'primary', type = 'contained' }: IconButtonDynamicProps) => ({
|
|
46
|
+
boxSizing: 'border-box',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
borderRadius: 9999, // Fully circular
|
|
50
|
+
// Inline theme accesses so Unistyles can trace them
|
|
51
|
+
backgroundColor: type === 'contained'
|
|
52
|
+
? theme.intents[intent].primary
|
|
53
|
+
: type === 'outlined'
|
|
54
|
+
? theme.colors.surface.primary
|
|
55
|
+
: 'transparent',
|
|
56
|
+
borderColor: type === 'outlined'
|
|
57
|
+
? theme.intents[intent].primary
|
|
58
|
+
: 'transparent',
|
|
59
|
+
borderWidth: type === 'outlined' ? 1 : 0,
|
|
60
|
+
borderStyle: type === 'outlined' ? 'solid' as const : undefined,
|
|
61
|
+
_web: {
|
|
62
|
+
display: 'flex',
|
|
63
|
+
transition: 'all 0.1s ease',
|
|
64
|
+
},
|
|
65
|
+
variants: {
|
|
66
|
+
type: {
|
|
67
|
+
contained: {
|
|
68
|
+
backgroundColor: theme.$intents.primary,
|
|
69
|
+
borderColor: 'transparent',
|
|
70
|
+
},
|
|
71
|
+
outlined: {
|
|
72
|
+
backgroundColor: 'transparent',
|
|
73
|
+
borderColor: theme.$intents.primary,
|
|
74
|
+
},
|
|
75
|
+
text: {
|
|
76
|
+
backgroundColor: 'transparent',
|
|
77
|
+
borderColor: 'transparent',
|
|
78
|
+
borderWidth: 0,
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
// Size variants - circular so width equals height
|
|
82
|
+
size: {
|
|
83
|
+
width: theme.sizes.$iconButton.size,
|
|
84
|
+
height: theme.sizes.$iconButton.size,
|
|
85
|
+
minWidth: theme.sizes.$iconButton.size,
|
|
86
|
+
minHeight: theme.sizes.$iconButton.size,
|
|
87
|
+
},
|
|
88
|
+
disabled: {
|
|
89
|
+
true: { opacity: 0.6 },
|
|
90
|
+
false: { opacity: 1, _web: { cursor: 'pointer', _hover: { opacity: 0.90 }, _active: { opacity: 0.75 } } },
|
|
91
|
+
},
|
|
92
|
+
gradient: {
|
|
93
|
+
darken: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(0, 0, 0, 0.15) 100%)' } },
|
|
94
|
+
lighten: { _web: { backgroundImage: 'linear-gradient(135deg, transparent 0%, rgba(255, 255, 255, 0.2) 100%)' } },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}),
|
|
98
|
+
icon: ({ intent = 'primary', type = 'contained' }: IconButtonDynamicProps) => ({
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
color: type === 'contained'
|
|
103
|
+
? theme.intents[intent].contrast
|
|
104
|
+
: theme.intents[intent].primary,
|
|
105
|
+
variants: {
|
|
106
|
+
size: {
|
|
107
|
+
width: theme.sizes.$iconButton.iconSize,
|
|
108
|
+
height: theme.sizes.$iconButton.iconSize,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
spinner: ({ intent = 'primary', type = 'contained' }: IconButtonDynamicProps) => ({
|
|
113
|
+
display: 'flex',
|
|
114
|
+
alignItems: 'center',
|
|
115
|
+
justifyContent: 'center',
|
|
116
|
+
// Match the icon color based on button type
|
|
117
|
+
color: type === 'contained'
|
|
118
|
+
? theme.intents[intent].contrast
|
|
119
|
+
: theme.intents[intent].primary,
|
|
120
|
+
variants: {
|
|
121
|
+
size: {
|
|
122
|
+
width: theme.sizes.$iconButton.iconSize,
|
|
123
|
+
height: theme.sizes.$iconButton.iconSize,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
}));
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import React, { isValidElement, forwardRef, useMemo } from 'react';
|
|
2
|
+
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
+
import { IconButtonProps } from './types';
|
|
4
|
+
import { iconButtonStyles } from './IconButton.styles';
|
|
5
|
+
import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
|
|
6
|
+
import useMergeRefs from '../hooks/useMergeRefs';
|
|
7
|
+
import { getWebInteractiveAriaProps, generateAccessibilityId } from '../utils/accessibility';
|
|
8
|
+
import type { IdealystElement } from '../utils/refTypes';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Circular icon button component with multiple visual variants and sizes.
|
|
12
|
+
* Supports contained, outlined, and text styles with customizable intent colors.
|
|
13
|
+
*/
|
|
14
|
+
const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) => {
|
|
15
|
+
const {
|
|
16
|
+
icon,
|
|
17
|
+
onPress,
|
|
18
|
+
onClick,
|
|
19
|
+
disabled = false,
|
|
20
|
+
loading = false,
|
|
21
|
+
type = 'contained',
|
|
22
|
+
intent = 'primary',
|
|
23
|
+
size = 'md',
|
|
24
|
+
gradient,
|
|
25
|
+
style,
|
|
26
|
+
testID,
|
|
27
|
+
id,
|
|
28
|
+
// Accessibility props
|
|
29
|
+
accessibilityLabel,
|
|
30
|
+
accessibilityHint,
|
|
31
|
+
accessibilityDisabled,
|
|
32
|
+
accessibilityHidden,
|
|
33
|
+
accessibilityRole,
|
|
34
|
+
accessibilityLabelledBy,
|
|
35
|
+
accessibilityDescribedBy,
|
|
36
|
+
accessibilityControls,
|
|
37
|
+
accessibilityExpanded,
|
|
38
|
+
accessibilityPressed,
|
|
39
|
+
accessibilityOwns,
|
|
40
|
+
accessibilityHasPopup,
|
|
41
|
+
} = props;
|
|
42
|
+
|
|
43
|
+
// Button is effectively disabled when loading
|
|
44
|
+
const isDisabled = disabled || loading;
|
|
45
|
+
|
|
46
|
+
// Apply variants for size, disabled, gradient
|
|
47
|
+
iconButtonStyles.useVariants({
|
|
48
|
+
size,
|
|
49
|
+
disabled: isDisabled,
|
|
50
|
+
gradient,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Determine the handler to use - onPress takes precedence
|
|
54
|
+
const pressHandler = onPress ?? onClick;
|
|
55
|
+
|
|
56
|
+
// Warn about deprecated onClick usage in development
|
|
57
|
+
if (process.env.NODE_ENV !== 'production' && onClick && !onPress) {
|
|
58
|
+
console.warn(
|
|
59
|
+
'IconButton: onClick prop is deprecated. Use onPress instead for cross-platform compatibility.'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
if (!isDisabled && pressHandler) {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
pressHandler();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Generate unique ID for accessibility
|
|
72
|
+
const buttonId = useMemo(() => id || generateAccessibilityId('icon-button'), [id]);
|
|
73
|
+
|
|
74
|
+
// Generate ARIA props - accessibilityLabel is critical for icon-only buttons
|
|
75
|
+
const ariaProps = useMemo(() => {
|
|
76
|
+
const computedLabel = accessibilityLabel ?? (typeof icon === 'string' ? icon : undefined);
|
|
77
|
+
|
|
78
|
+
return getWebInteractiveAriaProps({
|
|
79
|
+
accessibilityLabel: computedLabel,
|
|
80
|
+
accessibilityHint,
|
|
81
|
+
accessibilityDisabled: accessibilityDisabled ?? isDisabled,
|
|
82
|
+
accessibilityHidden,
|
|
83
|
+
accessibilityRole: accessibilityRole ?? 'button',
|
|
84
|
+
accessibilityLabelledBy,
|
|
85
|
+
accessibilityDescribedBy,
|
|
86
|
+
accessibilityControls,
|
|
87
|
+
accessibilityExpanded,
|
|
88
|
+
accessibilityPressed,
|
|
89
|
+
accessibilityOwns,
|
|
90
|
+
accessibilityHasPopup,
|
|
91
|
+
});
|
|
92
|
+
}, [
|
|
93
|
+
accessibilityLabel,
|
|
94
|
+
icon,
|
|
95
|
+
accessibilityHint,
|
|
96
|
+
accessibilityDisabled,
|
|
97
|
+
isDisabled,
|
|
98
|
+
accessibilityHidden,
|
|
99
|
+
accessibilityRole,
|
|
100
|
+
accessibilityLabelledBy,
|
|
101
|
+
accessibilityDescribedBy,
|
|
102
|
+
accessibilityControls,
|
|
103
|
+
accessibilityExpanded,
|
|
104
|
+
accessibilityPressed,
|
|
105
|
+
accessibilityOwns,
|
|
106
|
+
accessibilityHasPopup,
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// Compute dynamic styles with all props for full flexibility
|
|
110
|
+
const dynamicProps = { intent, type, size, disabled: isDisabled, gradient };
|
|
111
|
+
const buttonStyleArray = [
|
|
112
|
+
(iconButtonStyles.button as any)(dynamicProps),
|
|
113
|
+
style as any,
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// Use getWebProps to generate className and ref for web
|
|
117
|
+
const webProps = getWebProps(buttonStyleArray);
|
|
118
|
+
|
|
119
|
+
// Icon styles with dynamic function
|
|
120
|
+
const iconStyleArray = [(iconButtonStyles.icon as any)(dynamicProps)];
|
|
121
|
+
const iconProps = getWebProps(iconStyleArray);
|
|
122
|
+
|
|
123
|
+
// Spinner styles that match the icon color
|
|
124
|
+
const spinnerStyleArray = [(iconButtonStyles.spinner as any)(dynamicProps)];
|
|
125
|
+
const spinnerProps = getWebProps(spinnerStyleArray);
|
|
126
|
+
|
|
127
|
+
// Helper to render icon
|
|
128
|
+
const renderIcon = () => {
|
|
129
|
+
if (typeof icon === 'string') {
|
|
130
|
+
return (
|
|
131
|
+
<IconSvg
|
|
132
|
+
name={icon}
|
|
133
|
+
{...iconProps}
|
|
134
|
+
aria-label={icon}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
} else if (isValidElement(icon)) {
|
|
138
|
+
return icon;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Render spinner with inline CSS animation (absolutely centered)
|
|
144
|
+
const renderSpinner = () => (
|
|
145
|
+
<>
|
|
146
|
+
<style>
|
|
147
|
+
{`
|
|
148
|
+
@keyframes icon-button-spin {
|
|
149
|
+
from { transform: rotate(0deg); }
|
|
150
|
+
to { transform: rotate(360deg); }
|
|
151
|
+
}
|
|
152
|
+
`}
|
|
153
|
+
</style>
|
|
154
|
+
<span
|
|
155
|
+
{...spinnerProps}
|
|
156
|
+
style={{
|
|
157
|
+
position: 'absolute',
|
|
158
|
+
display: 'inline-block',
|
|
159
|
+
width: '1em',
|
|
160
|
+
height: '1em',
|
|
161
|
+
border: '2px solid currentColor',
|
|
162
|
+
borderTopColor: 'transparent',
|
|
163
|
+
borderRadius: '50%',
|
|
164
|
+
animation: 'icon-button-spin 0.8s linear infinite',
|
|
165
|
+
}}
|
|
166
|
+
role="status"
|
|
167
|
+
aria-label="Loading"
|
|
168
|
+
/>
|
|
169
|
+
</>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Merge unistyles web ref with forwarded ref
|
|
173
|
+
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
174
|
+
|
|
175
|
+
// Content opacity - hide when loading but keep for sizing
|
|
176
|
+
const contentStyle = loading ? { opacity: 0 } : undefined;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<button
|
|
180
|
+
{...webProps}
|
|
181
|
+
{...ariaProps}
|
|
182
|
+
ref={mergedRef}
|
|
183
|
+
id={buttonId}
|
|
184
|
+
onClick={handleClick}
|
|
185
|
+
disabled={isDisabled}
|
|
186
|
+
data-testid={testID}
|
|
187
|
+
aria-busy={loading ? 'true' : undefined}
|
|
188
|
+
style={{ position: 'relative' }}
|
|
189
|
+
>
|
|
190
|
+
{loading && renderSpinner()}
|
|
191
|
+
<span style={contentStyle}>{renderIcon()}</span>
|
|
192
|
+
</button>
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
IconButton.displayName = 'IconButton';
|
|
197
|
+
|
|
198
|
+
export default IconButton;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type { IconName } from '../Icon/icon-types';
|
|
4
|
+
import { Intent, Size } from '@idealyst/theme';
|
|
5
|
+
import { BaseProps } from '../utils/viewStyleProps';
|
|
6
|
+
import { InteractiveAccessibilityProps } from '../utils/accessibility';
|
|
7
|
+
|
|
8
|
+
// Component-specific type aliases for future extensibility
|
|
9
|
+
export type IconButtonType = 'contained' | 'outlined' | 'text';
|
|
10
|
+
export type IconButtonIntentVariant = Intent;
|
|
11
|
+
export type IconButtonSizeVariant = Size;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gradient overlay options for icon buttons.
|
|
15
|
+
* Applies a transparent gradient over the intent background color.
|
|
16
|
+
* - 'darken': Transparent to semi-transparent black (darkens one corner)
|
|
17
|
+
* - 'lighten': Transparent to semi-transparent white (lightens one corner)
|
|
18
|
+
*/
|
|
19
|
+
export type IconButtonGradient = 'darken' | 'lighten';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Circular icon button component with multiple visual variants, sizes, and a single icon.
|
|
23
|
+
* Supports contained, outlined, and text styles with customizable intent colors.
|
|
24
|
+
*/
|
|
25
|
+
export interface IconButtonProps extends BaseProps, InteractiveAccessibilityProps {
|
|
26
|
+
/**
|
|
27
|
+
* The icon to display. Can be an icon name string or a custom ReactNode.
|
|
28
|
+
*/
|
|
29
|
+
icon: IconName | ReactNode;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Called when the button is pressed
|
|
33
|
+
*/
|
|
34
|
+
onPress?: () => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated Use `onPress` instead. This prop exists for web compatibility only.
|
|
38
|
+
* Using onClick will log a deprecation warning in development.
|
|
39
|
+
*/
|
|
40
|
+
onClick?: () => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether the button is disabled
|
|
44
|
+
*/
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The visual style type of the button
|
|
49
|
+
*/
|
|
50
|
+
type?: IconButtonType;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The intent/color scheme of the button
|
|
54
|
+
*/
|
|
55
|
+
intent?: IconButtonIntentVariant;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The size of the button
|
|
59
|
+
*/
|
|
60
|
+
size?: IconButtonSizeVariant;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Apply a gradient background enhancement.
|
|
64
|
+
* Only applies to 'contained' button type.
|
|
65
|
+
*/
|
|
66
|
+
gradient?: IconButtonGradient;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether the button is in a loading state.
|
|
70
|
+
* When true, shows a spinner and disables interaction.
|
|
71
|
+
* The spinner color matches the icon color.
|
|
72
|
+
*/
|
|
73
|
+
loading?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Additional styles (platform-specific)
|
|
77
|
+
*/
|
|
78
|
+
style?: StyleProp<ViewStyle>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Test ID for testing
|
|
82
|
+
*/
|
|
83
|
+
testID?: string;
|
|
84
|
+
}
|
|
@@ -3,8 +3,9 @@ import { Image as RNImage, View } from 'react-native';
|
|
|
3
3
|
import { imageStyles } from './Image.styles';
|
|
4
4
|
import type { ImageProps } from './types';
|
|
5
5
|
import ActivityIndicator from '../ActivityIndicator';
|
|
6
|
+
import type { IdealystElement } from '../utils/refTypes';
|
|
6
7
|
|
|
7
|
-
const Image = forwardRef<
|
|
8
|
+
const Image = forwardRef<IdealystElement, ImageProps>(({
|
|
8
9
|
source,
|
|
9
10
|
alt,
|
|
10
11
|
width,
|
|
@@ -57,7 +58,7 @@ const Image = forwardRef<View, ImageProps>(({
|
|
|
57
58
|
];
|
|
58
59
|
|
|
59
60
|
return (
|
|
60
|
-
<View ref={ref} nativeID={id} style={containerStyle as any} testID={testID} accessibilityRole="image" accessibilityLabel={accessibilityLabel || alt}>
|
|
61
|
+
<View ref={ref as any} nativeID={id} style={containerStyle as any} testID={testID} accessibilityRole="image" accessibilityLabel={accessibilityLabel || alt}>
|
|
61
62
|
<RNImage
|
|
62
63
|
source={imageSource as any}
|
|
63
64
|
style={[imageStyles.image, { borderRadius }]}
|