@idealyst/components 1.2.33 → 1.2.34
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/package.json +3 -3
- package/src/Card/Card.native.tsx +2 -0
- package/src/Card/Card.web.tsx +4 -1
- package/src/Card/types.ts +7 -1
- package/src/Screen/Screen.native.tsx +3 -1
- package/src/Screen/Screen.web.tsx +5 -1
- package/src/Screen/types.ts +7 -1
- package/src/View/View.native.tsx +3 -1
- package/src/View/View.web.tsx +5 -1
- package/src/View/types.ts +8 -1
- package/src/examples/CardExamples.tsx +27 -0
- package/src/examples/ScreenExamples.tsx +31 -0
- package/src/hooks/useWebLayout/index.native.ts +1 -0
- package/src/hooks/useWebLayout/index.ts +1 -0
- package/src/hooks/useWebLayout/index.web.ts +1 -0
- package/src/hooks/useWebLayout/useWebLayout.native.ts +15 -0
- package/src/hooks/useWebLayout/useWebLayout.web.ts +60 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.34",
|
|
4
4
|
"description": "Shared component library for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"publish:npm": "npm publish"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@idealyst/theme": "^1.2.
|
|
59
|
+
"@idealyst/theme": "^1.2.34",
|
|
60
60
|
"@mdi/js": ">=7.0.0",
|
|
61
61
|
"@mdi/react": ">=1.0.0",
|
|
62
62
|
"@react-native-vector-icons/common": ">=12.0.0",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@idealyst/theme": "^1.2.
|
|
109
|
+
"@idealyst/theme": "^1.2.34",
|
|
110
110
|
"@idealyst/tooling": "^1.2.30",
|
|
111
111
|
"@mdi/react": "^1.6.1",
|
|
112
112
|
"@types/react": "^19.1.0",
|
package/src/Card/Card.native.tsx
CHANGED
|
@@ -17,6 +17,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
|
|
|
17
17
|
onPress,
|
|
18
18
|
onClick,
|
|
19
19
|
disabled = false,
|
|
20
|
+
onLayout,
|
|
20
21
|
// Spacing variants from ContainerStyleProps
|
|
21
22
|
gap,
|
|
22
23
|
padding,
|
|
@@ -93,6 +94,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
|
|
|
93
94
|
nativeID: id,
|
|
94
95
|
style: [cardStyle, style],
|
|
95
96
|
testID,
|
|
97
|
+
onLayout,
|
|
96
98
|
...nativeA11yProps,
|
|
97
99
|
...(clickable && {
|
|
98
100
|
onPress: disabled ? undefined : pressHandler,
|
package/src/Card/Card.web.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
|
|
|
3
3
|
import { CardProps } from './types';
|
|
4
4
|
import { cardStyles } from './Card.styles';
|
|
5
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
6
|
+
import { useWebLayout } from '../hooks/useWebLayout';
|
|
6
7
|
import { getWebInteractiveAriaProps } from '../utils/accessibility';
|
|
7
8
|
import type { IdealystElement } from '../utils/refTypes';
|
|
8
9
|
|
|
@@ -23,6 +24,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
|
|
|
23
24
|
onPress,
|
|
24
25
|
onClick,
|
|
25
26
|
disabled = false,
|
|
27
|
+
onLayout,
|
|
26
28
|
// Spacing variants from ContainerStyleProps
|
|
27
29
|
gap,
|
|
28
30
|
padding,
|
|
@@ -43,6 +45,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
|
|
|
43
45
|
accessibilityPressed,
|
|
44
46
|
}, ref) => {
|
|
45
47
|
const hasWarnedRef = useRef(false);
|
|
48
|
+
const layoutRef = useWebLayout<HTMLElement>(onLayout);
|
|
46
49
|
|
|
47
50
|
// Warn about onClick usage (deprecated)
|
|
48
51
|
useEffect(() => {
|
|
@@ -105,7 +108,7 @@ const Card = forwardRef<IdealystElement, CardProps>(({
|
|
|
105
108
|
// Generate web props
|
|
106
109
|
const webProps = getWebProps([cardStyle, style as any]);
|
|
107
110
|
|
|
108
|
-
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
111
|
+
const mergedRef = useMergeRefs(ref, webProps.ref, layoutRef);
|
|
109
112
|
|
|
110
113
|
// Use appropriate HTML element based on clickable state
|
|
111
114
|
const Component: any = clickable ? 'button' : 'div';
|
package/src/Card/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Intent, Size } from '@idealyst/theme';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type { StyleProp, ViewStyle, LayoutChangeEvent } from 'react-native';
|
|
4
4
|
import { ContainerStyleProps } from '../utils/viewStyleProps';
|
|
5
5
|
import { InteractiveAccessibilityProps } from '../utils/accessibility';
|
|
6
6
|
|
|
@@ -78,4 +78,10 @@ export interface CardProps extends ContainerStyleProps, InteractiveAccessibility
|
|
|
78
78
|
* Test ID for testing
|
|
79
79
|
*/
|
|
80
80
|
testID?: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Called when the layout of the card changes.
|
|
84
|
+
* Provides the new width, height, x, and y coordinates.
|
|
85
|
+
*/
|
|
86
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
81
87
|
}
|
|
@@ -11,6 +11,7 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
11
11
|
safeArea = true,
|
|
12
12
|
scrollable = true,
|
|
13
13
|
contentInset,
|
|
14
|
+
onLayout,
|
|
14
15
|
// Spacing variants from ContainerStyleProps
|
|
15
16
|
gap,
|
|
16
17
|
padding,
|
|
@@ -66,6 +67,7 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
66
67
|
style={[screenStyle, style]}
|
|
67
68
|
contentContainerStyle={{ flexGrow: 1 }}
|
|
68
69
|
testID={testID}
|
|
70
|
+
onLayout={onLayout}
|
|
69
71
|
>
|
|
70
72
|
<RNView style={[contentInsetStyle, { flex: 1 }]}>
|
|
71
73
|
{children}
|
|
@@ -75,7 +77,7 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
return (
|
|
78
|
-
<RNView ref={ref as any} nativeID={id} style={[screenStyle, safeAreaStyle, style]} testID={testID}>
|
|
80
|
+
<RNView ref={ref as any} nativeID={id} style={[screenStyle, safeAreaStyle, style]} testID={testID} onLayout={onLayout}>
|
|
79
81
|
{children}
|
|
80
82
|
</RNView>
|
|
81
83
|
);
|
|
@@ -3,6 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
|
|
|
3
3
|
import { ScreenProps } from './types';
|
|
4
4
|
import { screenStyles } from './Screen.styles';
|
|
5
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
6
|
+
import { useWebLayout } from '../hooks/useWebLayout';
|
|
6
7
|
import type { IdealystElement } from '../utils/refTypes';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -13,6 +14,7 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
13
14
|
children,
|
|
14
15
|
background = 'screen',
|
|
15
16
|
safeArea = false,
|
|
17
|
+
onLayout,
|
|
16
18
|
// Spacing variants from ContainerStyleProps
|
|
17
19
|
gap,
|
|
18
20
|
padding,
|
|
@@ -25,6 +27,8 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
25
27
|
testID,
|
|
26
28
|
id,
|
|
27
29
|
}, ref) => {
|
|
30
|
+
const layoutRef = useWebLayout<HTMLDivElement>(onLayout);
|
|
31
|
+
|
|
28
32
|
screenStyles.useVariants({
|
|
29
33
|
background,
|
|
30
34
|
safeArea,
|
|
@@ -40,7 +44,7 @@ const Screen = forwardRef<IdealystElement, ScreenProps>(({
|
|
|
40
44
|
// Call style as function to get theme-reactive styles
|
|
41
45
|
const webProps = getWebProps([(screenStyles.screen as any)({}), style as any]);
|
|
42
46
|
|
|
43
|
-
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
47
|
+
const mergedRef = useMergeRefs(ref, webProps.ref, layoutRef);
|
|
44
48
|
|
|
45
49
|
return (
|
|
46
50
|
<div
|
package/src/Screen/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import type { StyleProp, ViewStyle, LayoutChangeEvent } from 'react-native';
|
|
3
3
|
import { Surface } from '@idealyst/theme';
|
|
4
4
|
import { ContainerStyleProps } from '../utils/viewStyleProps';
|
|
5
5
|
|
|
@@ -49,4 +49,10 @@ export interface ScreenProps extends ContainerStyleProps {
|
|
|
49
49
|
* Scrollable content
|
|
50
50
|
*/
|
|
51
51
|
scrollable?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Called when the layout of the screen changes.
|
|
55
|
+
* Provides the new width, height, x, and y coordinates.
|
|
56
|
+
*/
|
|
57
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
52
58
|
}
|
package/src/View/View.native.tsx
CHANGED
|
@@ -45,6 +45,7 @@ const View = forwardRef<IdealystElement, ViewProps>(({
|
|
|
45
45
|
style,
|
|
46
46
|
testID,
|
|
47
47
|
id,
|
|
48
|
+
onLayout,
|
|
48
49
|
}, ref) => {
|
|
49
50
|
// Set active variants for this render
|
|
50
51
|
viewStyles.useVariants({
|
|
@@ -89,6 +90,7 @@ const View = forwardRef<IdealystElement, ViewProps>(({
|
|
|
89
90
|
contentContainerStyle={[viewStyle, overrideStyles]}
|
|
90
91
|
testID={testID}
|
|
91
92
|
nativeID={id}
|
|
93
|
+
onLayout={onLayout}
|
|
92
94
|
>
|
|
93
95
|
{children}
|
|
94
96
|
</RNScrollView>
|
|
@@ -96,7 +98,7 @@ const View = forwardRef<IdealystElement, ViewProps>(({
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
return (
|
|
99
|
-
<RNView ref={ref as any} style={[viewStyle, overrideStyles, finalStyle]} testID={testID} nativeID={id}>
|
|
101
|
+
<RNView ref={ref as any} style={[viewStyle, overrideStyles, finalStyle]} testID={testID} nativeID={id} onLayout={onLayout}>
|
|
100
102
|
{children}
|
|
101
103
|
</RNView>
|
|
102
104
|
);
|
package/src/View/View.web.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
|
|
|
3
3
|
import { ViewProps } from './types';
|
|
4
4
|
import { viewStyles } from './View.styles';
|
|
5
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
6
|
+
import { useWebLayout } from '../hooks/useWebLayout';
|
|
6
7
|
import type { IdealystElement } from '../utils/refTypes';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -31,7 +32,10 @@ const View = forwardRef<IdealystElement, ViewProps>(({
|
|
|
31
32
|
style,
|
|
32
33
|
testID,
|
|
33
34
|
id,
|
|
35
|
+
onLayout,
|
|
34
36
|
}, ref) => {
|
|
37
|
+
const layoutRef = useWebLayout<HTMLDivElement>(onLayout);
|
|
38
|
+
|
|
35
39
|
viewStyles.useVariants({
|
|
36
40
|
background,
|
|
37
41
|
radius,
|
|
@@ -51,7 +55,7 @@ const View = forwardRef<IdealystElement, ViewProps>(({
|
|
|
51
55
|
/** @ts-ignore */
|
|
52
56
|
const wrapperWebProps = getWebProps(viewStyles.scrollableWrapper);
|
|
53
57
|
|
|
54
|
-
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
58
|
+
const mergedRef = useMergeRefs(ref, webProps.ref, layoutRef);
|
|
55
59
|
|
|
56
60
|
// When scrollable, render a wrapper + content structure
|
|
57
61
|
// Wrapper: sizing and margin (positioning in parent layout)
|
package/src/View/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Size, Surface, ResponsiveStyle } from '@idealyst/theme';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type { StyleProp, ViewStyle, LayoutChangeEvent } from 'react-native';
|
|
4
4
|
import { ContainerStyleProps } from '../utils/viewStyleProps';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -99,4 +99,11 @@ export interface ViewProps extends ContainerStyleProps {
|
|
|
99
99
|
* Test ID for testing
|
|
100
100
|
*/
|
|
101
101
|
testID?: string;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Callback when the view's layout changes.
|
|
105
|
+
* Called with layout information (x, y, width, height) when the component
|
|
106
|
+
* mounts or when its dimensions change.
|
|
107
|
+
*/
|
|
108
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
102
109
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import { Screen, View, Text, Card, Button } from '../index';
|
|
2
3
|
|
|
3
4
|
export const CardExamples = () => {
|
|
@@ -5,6 +6,8 @@ export const CardExamples = () => {
|
|
|
5
6
|
console.log(`Card pressed: ${cardType}`);
|
|
6
7
|
};
|
|
7
8
|
|
|
9
|
+
const [cardDimensions, setCardDimensions] = useState({ width: 0, height: 0 });
|
|
10
|
+
|
|
8
11
|
return (
|
|
9
12
|
<Screen background="primary" padding="lg">
|
|
10
13
|
<View gap="xl">
|
|
@@ -163,6 +166,30 @@ export const CardExamples = () => {
|
|
|
163
166
|
</View>
|
|
164
167
|
</Card>
|
|
165
168
|
</View>
|
|
169
|
+
|
|
170
|
+
{/* onLayout Example */}
|
|
171
|
+
<View gap="md">
|
|
172
|
+
<Text typography="subtitle1">onLayout Callback</Text>
|
|
173
|
+
<Text typography="caption" color="secondary">
|
|
174
|
+
Track card dimensions as they change
|
|
175
|
+
</Text>
|
|
176
|
+
<Card
|
|
177
|
+
type="outlined"
|
|
178
|
+
padding="md"
|
|
179
|
+
onLayout={(event) => {
|
|
180
|
+
const { width, height } = event.nativeEvent.layout;
|
|
181
|
+
setCardDimensions({ width, height });
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<Text weight="semibold">Responsive Card</Text>
|
|
185
|
+
<Text typography="caption" color="secondary">
|
|
186
|
+
Width: {Math.round(cardDimensions.width)}px
|
|
187
|
+
</Text>
|
|
188
|
+
<Text typography="caption" color="secondary">
|
|
189
|
+
Height: {Math.round(cardDimensions.height)}px
|
|
190
|
+
</Text>
|
|
191
|
+
</Card>
|
|
192
|
+
</View>
|
|
166
193
|
</View>
|
|
167
194
|
</Screen>
|
|
168
195
|
);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import { Screen, View, Text } from '../index';
|
|
2
3
|
|
|
3
4
|
export const ScreenExamples = () => {
|
|
5
|
+
const [screenDimensions, setScreenDimensions] = useState({ width: 0, height: 0 });
|
|
6
|
+
|
|
4
7
|
return (
|
|
5
8
|
<Screen background="primary" padding="lg">
|
|
6
9
|
<View gap="lg">
|
|
@@ -147,6 +150,34 @@ export const ScreenExamples = () => {
|
|
|
147
150
|
</Screen>
|
|
148
151
|
</View>
|
|
149
152
|
</View>
|
|
153
|
+
|
|
154
|
+
{/* onLayout Example */}
|
|
155
|
+
<View gap="md">
|
|
156
|
+
<Text typography="subtitle1">onLayout Callback</Text>
|
|
157
|
+
<Text typography="caption" color="secondary">
|
|
158
|
+
Track screen dimensions as they change
|
|
159
|
+
</Text>
|
|
160
|
+
<View style={{ height: 120, borderWidth: 1, borderColor: '#ccc' }}>
|
|
161
|
+
<Screen
|
|
162
|
+
background="secondary"
|
|
163
|
+
padding="md"
|
|
164
|
+
onLayout={(event) => {
|
|
165
|
+
const { width, height } = event.nativeEvent.layout;
|
|
166
|
+
setScreenDimensions({ width, height });
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
|
|
170
|
+
<Text weight="semibold">Responsive Screen</Text>
|
|
171
|
+
<Text typography="caption" color="secondary">
|
|
172
|
+
Width: {Math.round(screenDimensions.width)}px
|
|
173
|
+
</Text>
|
|
174
|
+
<Text typography="caption" color="secondary">
|
|
175
|
+
Height: {Math.round(screenDimensions.height)}px
|
|
176
|
+
</Text>
|
|
177
|
+
</View>
|
|
178
|
+
</Screen>
|
|
179
|
+
</View>
|
|
180
|
+
</View>
|
|
150
181
|
</View>
|
|
151
182
|
</Screen>
|
|
152
183
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWebLayout } from './useWebLayout.native';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWebLayout } from './useWebLayout';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useWebLayout } from './useWebLayout.web';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* No-op hook for native - onLayout is handled natively by React Native components.
|
|
6
|
+
* Returns a ref for API compatibility, but it's not used on native.
|
|
7
|
+
*
|
|
8
|
+
* @param _onLayout - Unused on native (React Native components handle onLayout directly)
|
|
9
|
+
* @returns A ref (unused on native, for API compatibility)
|
|
10
|
+
*/
|
|
11
|
+
export function useWebLayout<T = any>(
|
|
12
|
+
_onLayout: ((event: LayoutChangeEvent) => void) | undefined
|
|
13
|
+
) {
|
|
14
|
+
return useRef<T>(null);
|
|
15
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook that provides onLayout functionality for web components using ResizeObserver.
|
|
6
|
+
* Returns a ref that should be attached to the element you want to observe.
|
|
7
|
+
*
|
|
8
|
+
* @param onLayout - Callback fired when layout changes, with React Native compatible event shape
|
|
9
|
+
* @returns A ref to attach to the observed element
|
|
10
|
+
*/
|
|
11
|
+
export function useWebLayout<T extends HTMLElement = HTMLElement>(
|
|
12
|
+
onLayout: ((event: LayoutChangeEvent) => void) | undefined
|
|
13
|
+
) {
|
|
14
|
+
const ref = useRef<T>(null);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!onLayout || !ref.current) return;
|
|
18
|
+
|
|
19
|
+
const element = ref.current;
|
|
20
|
+
|
|
21
|
+
// Call immediately with initial layout
|
|
22
|
+
const rect = element.getBoundingClientRect();
|
|
23
|
+
onLayout({
|
|
24
|
+
nativeEvent: {
|
|
25
|
+
layout: {
|
|
26
|
+
x: rect.left,
|
|
27
|
+
y: rect.top,
|
|
28
|
+
width: rect.width,
|
|
29
|
+
height: rect.height,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} as LayoutChangeEvent);
|
|
33
|
+
|
|
34
|
+
// Set up ResizeObserver for subsequent changes
|
|
35
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const { width, height } = entry.contentRect;
|
|
38
|
+
const boundingRect = element.getBoundingClientRect();
|
|
39
|
+
onLayout({
|
|
40
|
+
nativeEvent: {
|
|
41
|
+
layout: {
|
|
42
|
+
x: boundingRect.left,
|
|
43
|
+
y: boundingRect.top,
|
|
44
|
+
width,
|
|
45
|
+
height,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
} as LayoutChangeEvent);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
resizeObserver.observe(element);
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
resizeObserver.disconnect();
|
|
56
|
+
};
|
|
57
|
+
}, [onLayout]);
|
|
58
|
+
|
|
59
|
+
return ref;
|
|
60
|
+
}
|