@idealyst/components 1.2.14 → 1.2.16
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/Accordion/Accordion.web.tsx +1 -1
- package/src/Alert/Alert.native.tsx +1 -1
- package/src/Alert/Alert.web.tsx +1 -1
- package/src/Badge/Badge.web.tsx +6 -2
- package/src/Badge/types.ts +5 -0
- package/src/Button/Button.native.tsx +3 -3
- package/src/Button/Button.web.tsx +5 -1
- package/src/Button/types.ts +5 -0
- package/src/Card/Card.web.tsx +4 -1
- package/src/Card/types.ts +5 -0
- package/src/Dialog/Dialog.native.tsx +1 -1
- package/src/Divider/Divider.web.tsx +2 -2
- package/src/Icon/Icon.web.tsx +2 -2
- package/src/Image/Image.styles.tsx +5 -5
- package/src/Image/Image.web.tsx +3 -3
- package/src/List/List.native.tsx +1 -2
- package/src/List/List.web.tsx +1 -2
- package/src/List/ListSection.web.tsx +3 -3
- package/src/Menu/Menu.web.tsx +8 -10
- package/src/Menu/MenuItem.web.tsx +1 -1
- package/src/Popover/Popover.web.tsx +1 -1
- package/src/Pressable/Pressable.web.tsx +1 -1
- package/src/Progress/Progress.styles.tsx +76 -30
- package/src/Progress/Progress.web.tsx +13 -15
- package/src/SVGImage/SVGImage.web.tsx +1 -1
- package/src/Select/Select.web.tsx +2 -2
- package/src/Slider/Slider.styles.tsx +131 -44
- package/src/Slider/Slider.web.tsx +22 -22
- package/src/Text/Text.web.tsx +29 -3
- package/src/Text/types.ts +14 -1
- package/src/TextArea/TextArea.styles.tsx +96 -57
- package/src/TextArea/TextArea.web.tsx +19 -28
- package/src/Tooltip/Tooltip.web.tsx +3 -3
- package/src/Video/Video.styles.tsx +3 -3
- package/src/Video/Video.web.tsx +1 -1
- package/src/View/View.styles.tsx +2 -2
- package/src/View/View.web.tsx +95 -9
- package/src/View/types.ts +5 -1
- package/src/examples/ViewExamples.tsx +34 -0
- package/src/extensions/index.ts +0 -7
- package/src/hooks/useMergeRefs.ts +12 -6
- package/src/utils/accessibility/keyboardPatterns.ts +4 -0
- package/src/utils/accessibility/types.ts +5 -1
- package/src/utils/accessibility/useAnnounce.ts +1 -1
- package/src/utils/accessibility/useKeyboardNavigation.ts +1 -1
- package/src/utils/index.ts +0 -3
- package/src/utils/viewStyleProps.ts +2 -0
- package/src/extensions/applyExtension.ts +0 -210
- package/src/utils/buildSizeVariants.ts +0 -16
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TextArea styles using
|
|
2
|
+
* TextArea styles using static styles with variants.
|
|
3
3
|
*/
|
|
4
4
|
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
-
import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
|
|
5
|
+
import { defineStyle, ThemeStyleWrapper, CompoundVariants } from '@idealyst/theme';
|
|
6
6
|
import type { Theme as BaseTheme, Intent, Size } from '@idealyst/theme';
|
|
7
7
|
import { ViewStyleSize } from '../utils/viewStyleProps';
|
|
8
8
|
|
|
@@ -12,26 +12,32 @@ void StyleSheet;
|
|
|
12
12
|
// Wrap theme for $iterator support
|
|
13
13
|
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
14
14
|
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
resize?: ResizeMode;
|
|
23
|
-
isNearLimit?: boolean;
|
|
24
|
-
isAtLimit?: boolean;
|
|
15
|
+
export type TextAreaVariants = {
|
|
16
|
+
size: Size;
|
|
17
|
+
intent: Intent;
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
hasError: boolean;
|
|
20
|
+
isNearLimit: boolean;
|
|
21
|
+
isAtLimit: boolean;
|
|
25
22
|
margin?: ViewStyleSize;
|
|
26
23
|
marginVertical?: ViewStyleSize;
|
|
27
24
|
marginHorizontal?: ViewStyleSize;
|
|
28
25
|
};
|
|
29
26
|
|
|
27
|
+
// Create intent variants dynamically from theme
|
|
28
|
+
function createIntentVariants(theme: Theme) {
|
|
29
|
+
const variants: Record<string, object> = {};
|
|
30
|
+
for (const intent in theme.intents) {
|
|
31
|
+
variants[intent] = {};
|
|
32
|
+
}
|
|
33
|
+
return variants;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
/**
|
|
31
|
-
* TextArea styles with
|
|
37
|
+
* TextArea styles with static styles and variants.
|
|
32
38
|
*/
|
|
33
39
|
export const textAreaStyles = defineStyle('TextArea', (theme: Theme) => ({
|
|
34
|
-
container:
|
|
40
|
+
container: {
|
|
35
41
|
display: 'flex' as const,
|
|
36
42
|
flexDirection: 'column' as const,
|
|
37
43
|
gap: 4,
|
|
@@ -49,70 +55,103 @@ export const textAreaStyles = defineStyle('TextArea', (theme: Theme) => ({
|
|
|
49
55
|
marginHorizontal: theme.sizes.$view.padding,
|
|
50
56
|
},
|
|
51
57
|
},
|
|
52
|
-
}
|
|
58
|
+
},
|
|
53
59
|
|
|
54
|
-
label:
|
|
60
|
+
label: {
|
|
55
61
|
fontSize: 14,
|
|
56
62
|
fontWeight: '500' as const,
|
|
57
63
|
color: theme.colors.text.primary,
|
|
58
|
-
|
|
64
|
+
variants: {
|
|
65
|
+
disabled: {
|
|
66
|
+
true: { opacity: 0.5 },
|
|
67
|
+
false: { opacity: 1 },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
59
71
|
|
|
60
|
-
textareaContainer:
|
|
72
|
+
textareaContainer: {
|
|
61
73
|
position: 'relative' as const,
|
|
62
74
|
variants: {
|
|
75
|
+
disabled: {
|
|
76
|
+
true: { opacity: 0.8 },
|
|
77
|
+
false: { opacity: 1 },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
textarea: {
|
|
83
|
+
width: '100%',
|
|
84
|
+
color: theme.colors.text.primary,
|
|
85
|
+
variants: {
|
|
86
|
+
size: {
|
|
87
|
+
fontSize: theme.sizes.$textarea.fontSize,
|
|
88
|
+
padding: theme.sizes.$textarea.padding,
|
|
89
|
+
lineHeight: theme.sizes.$textarea.lineHeight,
|
|
90
|
+
minHeight: theme.sizes.$textarea.minHeight,
|
|
91
|
+
},
|
|
63
92
|
disabled: {
|
|
64
93
|
true: {
|
|
65
|
-
opacity:
|
|
94
|
+
opacity: 0.5,
|
|
95
|
+
_web: {
|
|
96
|
+
cursor: 'not-allowed',
|
|
97
|
+
},
|
|
66
98
|
},
|
|
67
99
|
false: {
|
|
68
|
-
opacity:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}),
|
|
73
|
-
|
|
74
|
-
textarea: ({ disabled = false, resize = 'none' }: TextAreaDynamicProps) => ({
|
|
75
|
-
width: '100%',
|
|
76
|
-
color: theme.colors.text.primary,
|
|
77
|
-
opacity: disabled ? 0.5 : 1,
|
|
78
|
-
variants: {
|
|
79
|
-
size: {
|
|
80
|
-
fontSize: theme.sizes.$textarea.fontSize,
|
|
81
|
-
padding: theme.sizes.$textarea.padding,
|
|
82
|
-
lineHeight: theme.sizes.$textarea.lineHeight,
|
|
83
|
-
minHeight: theme.sizes.$textarea.minHeight,
|
|
100
|
+
opacity: 1,
|
|
101
|
+
_web: {
|
|
102
|
+
cursor: 'text',
|
|
103
|
+
},
|
|
84
104
|
},
|
|
85
105
|
},
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}),
|
|
106
|
+
},
|
|
107
|
+
_web: {
|
|
108
|
+
fontFamily: 'inherit',
|
|
109
|
+
outline: 'none',
|
|
110
|
+
transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
|
|
111
|
+
boxSizing: 'border-box',
|
|
112
|
+
overflowY: 'hidden',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
96
115
|
|
|
97
|
-
helperText:
|
|
116
|
+
helperText: {
|
|
98
117
|
fontSize: 12,
|
|
99
|
-
|
|
100
|
-
|
|
118
|
+
variants: {
|
|
119
|
+
hasError: {
|
|
120
|
+
true: { color: theme.intents.error.primary },
|
|
121
|
+
false: { color: theme.colors.text.secondary },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
101
125
|
|
|
102
|
-
footer:
|
|
126
|
+
footer: {
|
|
103
127
|
display: 'flex' as const,
|
|
104
128
|
flexDirection: 'row' as const,
|
|
105
129
|
justifyContent: 'space-between' as const,
|
|
106
130
|
alignItems: 'center' as const,
|
|
107
131
|
gap: 4,
|
|
108
|
-
}
|
|
132
|
+
},
|
|
109
133
|
|
|
110
|
-
characterCount:
|
|
134
|
+
characterCount: {
|
|
111
135
|
fontSize: 12,
|
|
112
|
-
color:
|
|
113
|
-
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
:
|
|
117
|
-
|
|
136
|
+
color: theme.colors.text.secondary,
|
|
137
|
+
variants: {
|
|
138
|
+
isAtLimit: {
|
|
139
|
+
true: { color: theme.intents.error.primary },
|
|
140
|
+
false: {},
|
|
141
|
+
},
|
|
142
|
+
isNearLimit: {
|
|
143
|
+
true: {},
|
|
144
|
+
false: {},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
compoundVariants: [
|
|
148
|
+
{
|
|
149
|
+
isNearLimit: true,
|
|
150
|
+
isAtLimit: false,
|
|
151
|
+
styles: {
|
|
152
|
+
color: theme.intents.warning.primary,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
] as CompoundVariants<keyof TextAreaVariants>,
|
|
156
|
+
},
|
|
118
157
|
}));
|
|
@@ -117,37 +117,29 @@ const TextArea = forwardRef<HTMLDivElement, TextAreaProps>(({
|
|
|
117
117
|
accessibilityAutoComplete,
|
|
118
118
|
]);
|
|
119
119
|
|
|
120
|
+
const isNearLimit = maxLength ? value.length >= maxLength * 0.9 : false;
|
|
121
|
+
const isAtLimit = maxLength ? value.length >= maxLength : false;
|
|
122
|
+
|
|
120
123
|
// Apply variants
|
|
121
124
|
textAreaStyles.useVariants({
|
|
122
125
|
size,
|
|
123
126
|
intent,
|
|
124
127
|
disabled,
|
|
125
128
|
hasError,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
isAtLimit: maxLength ? value.length >= maxLength : false,
|
|
129
|
+
isNearLimit,
|
|
130
|
+
isAtLimit,
|
|
129
131
|
margin,
|
|
130
132
|
marginVertical,
|
|
131
133
|
marginHorizontal,
|
|
132
134
|
});
|
|
133
135
|
|
|
134
|
-
// Get
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
isNearLimit: maxLength ? value.length >= maxLength * 0.9 : false,
|
|
142
|
-
isAtLimit: maxLength ? value.length >= maxLength : false,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const containerProps = getWebProps([containerStyle, style as any]);
|
|
146
|
-
const labelProps = getWebProps([labelStyle]);
|
|
147
|
-
const textareaContainerProps = getWebProps([textareaContainerStyle]);
|
|
148
|
-
const footerProps = getWebProps([footerStyle]);
|
|
149
|
-
const helperTextProps = getWebProps([helperTextStyle]);
|
|
150
|
-
const characterCountProps = getWebProps([characterCountStyle]);
|
|
136
|
+
// Get static styles (cast to any for Unistyles variant compatibility)
|
|
137
|
+
const containerProps = getWebProps([textAreaStyles.container as any, style as any]);
|
|
138
|
+
const labelProps = getWebProps([textAreaStyles.label as any]);
|
|
139
|
+
const textareaContainerProps = getWebProps([textAreaStyles.textareaContainer as any]);
|
|
140
|
+
const footerProps = getWebProps([textAreaStyles.footer as any]);
|
|
141
|
+
const helperTextProps = getWebProps([textAreaStyles.helperText as any]);
|
|
142
|
+
const characterCountProps = getWebProps([textAreaStyles.characterCount as any]);
|
|
151
143
|
|
|
152
144
|
const adjustHeight = () => {
|
|
153
145
|
if (!autoGrow || !textareaRef.current) return;
|
|
@@ -189,16 +181,15 @@ const TextArea = forwardRef<HTMLDivElement, TextAreaProps>(({
|
|
|
189
181
|
};
|
|
190
182
|
|
|
191
183
|
const showFooter = (error || helperText) || (showCharacterCount && maxLength);
|
|
192
|
-
const isNearLimit = maxLength ? value.length >= maxLength * 0.9 : false;
|
|
193
|
-
const isAtLimit = maxLength ? value.length >= maxLength : false;
|
|
194
184
|
|
|
195
185
|
const computedTextareaProps = getWebProps([
|
|
196
|
-
textAreaStyles.textarea
|
|
197
|
-
textareaStyle,
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
186
|
+
textAreaStyles.textarea as any,
|
|
187
|
+
textareaStyle as any,
|
|
188
|
+
{ resize } as any, // Apply resize as inline style since it's CSS-only
|
|
189
|
+
minHeight ? { minHeight: `${minHeight}px` } : null,
|
|
190
|
+
maxHeight ? { maxHeight: `${maxHeight}px` } : null,
|
|
191
|
+
autoGrow && maxHeight && textareaRef.current && textareaRef.current.scrollHeight > maxHeight ? { overflowY: 'auto' as const } : null,
|
|
192
|
+
].filter(Boolean));
|
|
202
193
|
|
|
203
194
|
const mergedRef = useMergeRefs(ref, containerProps.ref);
|
|
204
195
|
const mergedTextareaRef = useMergeRefs(textareaRef, computedTextareaProps.ref);
|
|
@@ -27,7 +27,7 @@ const Tooltip: React.FC<TooltipProps> = ({
|
|
|
27
27
|
accessibilityRole,
|
|
28
28
|
}) => {
|
|
29
29
|
const [visible, setVisible] = useState(false);
|
|
30
|
-
const timeoutRef = useRef<
|
|
30
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
31
31
|
const anchorRef = useRef<HTMLDivElement>(null);
|
|
32
32
|
|
|
33
33
|
// Generate unique ID for tooltip
|
|
@@ -105,9 +105,9 @@ const Tooltip: React.FC<TooltipProps> = ({
|
|
|
105
105
|
return (
|
|
106
106
|
<>
|
|
107
107
|
<div
|
|
108
|
-
ref={anchorRef}
|
|
109
108
|
{...containerProps}
|
|
110
109
|
{...ariaProps}
|
|
110
|
+
ref={anchorRef}
|
|
111
111
|
id={triggerId}
|
|
112
112
|
onMouseEnter={handleMouseEnter}
|
|
113
113
|
onMouseLeave={handleMouseLeave}
|
|
@@ -123,7 +123,7 @@ const Tooltip: React.FC<TooltipProps> = ({
|
|
|
123
123
|
|
|
124
124
|
<PositionedPortal
|
|
125
125
|
open={visible}
|
|
126
|
-
anchor={anchorRef}
|
|
126
|
+
anchor={anchorRef as React.RefObject<HTMLElement>}
|
|
127
127
|
placement={placement}
|
|
128
128
|
offset={8}
|
|
129
129
|
zIndex={1000}
|
|
@@ -20,7 +20,7 @@ export const videoStyles = defineStyle('Video', (theme: Theme) => ({
|
|
|
20
20
|
container: (_props: VideoDynamicProps) => ({
|
|
21
21
|
position: 'relative' as const,
|
|
22
22
|
overflow: 'hidden' as const,
|
|
23
|
-
backgroundColor: theme.colors[
|
|
23
|
+
backgroundColor: theme.colors.pallet.gray[900],
|
|
24
24
|
}),
|
|
25
25
|
|
|
26
26
|
video: (_props: VideoDynamicProps) => ({
|
|
@@ -37,7 +37,7 @@ export const videoStyles = defineStyle('Video', (theme: Theme) => ({
|
|
|
37
37
|
display: 'flex' as const,
|
|
38
38
|
alignItems: 'center' as const,
|
|
39
39
|
justifyContent: 'center' as const,
|
|
40
|
-
backgroundColor: theme.colors[
|
|
41
|
-
color: theme.colors[
|
|
40
|
+
backgroundColor: theme.colors.pallet.gray[300],
|
|
41
|
+
color: theme.colors.pallet.gray[600],
|
|
42
42
|
}),
|
|
43
43
|
}));
|
package/src/Video/Video.web.tsx
CHANGED
|
@@ -45,7 +45,7 @@ const Video: React.FC<VideoProps> = ({
|
|
|
45
45
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
46
46
|
|
|
47
47
|
const containerProps = getWebProps([videoStyles.container, style as any]);
|
|
48
|
-
const videoProps = getWebProps([videoStyles.video]);
|
|
48
|
+
const videoProps = getWebProps([videoStyles.video as any]);
|
|
49
49
|
|
|
50
50
|
const videoSource = typeof source === 'string'
|
|
51
51
|
? source
|
package/src/View/View.styles.tsx
CHANGED
|
@@ -60,8 +60,8 @@ export const viewStyles = defineStyle('View', (theme: Theme) => ({
|
|
|
60
60
|
},
|
|
61
61
|
border: {
|
|
62
62
|
none: { borderWidth: 0 },
|
|
63
|
-
thin: { borderWidth: 1, borderStyle: 'solid' as const, borderColor: theme.colors[
|
|
64
|
-
thick: { borderWidth: 2, borderStyle: 'solid' as const, borderColor: theme.colors[
|
|
63
|
+
thin: { borderWidth: 1, borderStyle: 'solid' as const, borderColor: theme.colors.pallet.gray[300] },
|
|
64
|
+
thick: { borderWidth: 2, borderStyle: 'solid' as const, borderColor: theme.colors.pallet.gray[300] },
|
|
65
65
|
},
|
|
66
66
|
// $iterator expands for each view size
|
|
67
67
|
gap: {
|
package/src/View/View.web.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import React, { forwardRef
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
-
import { useResponsiveStyle } from '@idealyst/theme';
|
|
4
3
|
import { ViewProps } from './types';
|
|
5
4
|
import { viewStyles } from './View.styles';
|
|
6
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
@@ -22,12 +21,12 @@ const View = forwardRef<HTMLDivElement, ViewProps>(({
|
|
|
22
21
|
margin,
|
|
23
22
|
marginVertical,
|
|
24
23
|
marginHorizontal,
|
|
25
|
-
// Override props
|
|
26
|
-
backgroundColor,
|
|
27
|
-
borderRadius,
|
|
28
|
-
borderWidth,
|
|
29
|
-
borderColor,
|
|
30
|
-
scrollable,
|
|
24
|
+
// Override props (accepted but handled via style prop on web)
|
|
25
|
+
backgroundColor: _backgroundColor,
|
|
26
|
+
borderRadius: _borderRadius,
|
|
27
|
+
borderWidth: _borderWidth,
|
|
28
|
+
borderColor: _borderColor,
|
|
29
|
+
scrollable,
|
|
31
30
|
style,
|
|
32
31
|
testID,
|
|
33
32
|
id,
|
|
@@ -48,9 +47,96 @@ const View = forwardRef<HTMLDivElement, ViewProps>(({
|
|
|
48
47
|
// Call style as function to get theme-reactive styles
|
|
49
48
|
/** @ts-ignore */
|
|
50
49
|
const webProps = getWebProps((viewStyles.view as any)({}));
|
|
51
|
-
|
|
50
|
+
|
|
52
51
|
const mergedRef = useMergeRefs(ref, webProps.ref);
|
|
53
52
|
|
|
53
|
+
// When scrollable, render a wrapper + content structure
|
|
54
|
+
// Wrapper: sizing and margin (positioning in parent layout)
|
|
55
|
+
// Content: absolutely positioned with overflow:auto, visual styles (padding, background, border)
|
|
56
|
+
if (scrollable) {
|
|
57
|
+
// Extract layout/sizing styles for the wrapper, visual styles go to content
|
|
58
|
+
const styleObj = (style as React.CSSProperties) || {};
|
|
59
|
+
const {
|
|
60
|
+
// Sizing - goes to wrapper
|
|
61
|
+
width,
|
|
62
|
+
height,
|
|
63
|
+
minWidth,
|
|
64
|
+
minHeight,
|
|
65
|
+
maxWidth,
|
|
66
|
+
maxHeight,
|
|
67
|
+
// Flex properties - goes to wrapper
|
|
68
|
+
flex,
|
|
69
|
+
flexGrow,
|
|
70
|
+
flexShrink,
|
|
71
|
+
flexBasis,
|
|
72
|
+
alignSelf,
|
|
73
|
+
// Margin - goes to wrapper (positioning in parent)
|
|
74
|
+
margin,
|
|
75
|
+
marginTop,
|
|
76
|
+
marginRight,
|
|
77
|
+
marginBottom,
|
|
78
|
+
marginLeft,
|
|
79
|
+
marginBlock,
|
|
80
|
+
marginInline,
|
|
81
|
+
// Everything else (padding, background, border, etc.) - goes to content
|
|
82
|
+
...contentStyles
|
|
83
|
+
} = styleObj;
|
|
84
|
+
|
|
85
|
+
const wrapperStyles: React.CSSProperties = {
|
|
86
|
+
display: 'flex',
|
|
87
|
+
flexDirection: 'column',
|
|
88
|
+
position: 'relative',
|
|
89
|
+
boxSizing: 'border-box',
|
|
90
|
+
// If no explicit sizing, flex-grow to fill remaining space
|
|
91
|
+
...(width === undefined && height === undefined && flex === undefined && { flex: 1 }),
|
|
92
|
+
// Apply sizing
|
|
93
|
+
width,
|
|
94
|
+
height,
|
|
95
|
+
minWidth,
|
|
96
|
+
minHeight: minHeight ?? 0, // Important for flex children to allow shrinking
|
|
97
|
+
maxWidth,
|
|
98
|
+
maxHeight,
|
|
99
|
+
// Apply flex properties
|
|
100
|
+
flex,
|
|
101
|
+
flexGrow,
|
|
102
|
+
flexShrink,
|
|
103
|
+
flexBasis,
|
|
104
|
+
alignSelf,
|
|
105
|
+
// Apply margin
|
|
106
|
+
margin,
|
|
107
|
+
marginTop,
|
|
108
|
+
marginRight,
|
|
109
|
+
marginBottom,
|
|
110
|
+
marginLeft,
|
|
111
|
+
marginBlock,
|
|
112
|
+
marginInline,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div style={wrapperStyles}>
|
|
117
|
+
<div
|
|
118
|
+
{...webProps}
|
|
119
|
+
style={{
|
|
120
|
+
// Content is block (not flex) for natural scrolling behavior
|
|
121
|
+
display: 'block',
|
|
122
|
+
// Absolutely positioned to fill wrapper
|
|
123
|
+
position: 'absolute',
|
|
124
|
+
inset: 0,
|
|
125
|
+
overflow: 'auto',
|
|
126
|
+
boxSizing: 'border-box',
|
|
127
|
+
// Apply visual styles (padding, background, border, etc.)
|
|
128
|
+
...contentStyles,
|
|
129
|
+
}}
|
|
130
|
+
ref={mergedRef}
|
|
131
|
+
id={id}
|
|
132
|
+
data-testid={testID}
|
|
133
|
+
>
|
|
134
|
+
{children}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
54
140
|
return (
|
|
55
141
|
<div
|
|
56
142
|
style={style as any}
|
package/src/View/types.ts
CHANGED
|
@@ -87,7 +87,11 @@ export interface ViewProps extends ContainerStyleProps {
|
|
|
87
87
|
style?: ViewStyleProp;
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* Enable scrollable content
|
|
90
|
+
* Enable scrollable content.
|
|
91
|
+
* - Native: Wraps children in a ScrollView
|
|
92
|
+
* - Web: Renders a wrapper + content structure where the wrapper fills available
|
|
93
|
+
* space (or uses explicit dimensions) and content is absolutely positioned with
|
|
94
|
+
* overflow:auto. Sizing/margin styles go to wrapper, visual styles to content.
|
|
91
95
|
*/
|
|
92
96
|
scrollable?: boolean;
|
|
93
97
|
|
|
@@ -131,6 +131,40 @@ export const ViewExamples = () => {
|
|
|
131
131
|
</View>
|
|
132
132
|
</View>
|
|
133
133
|
</View>
|
|
134
|
+
|
|
135
|
+
{/* Scrollable Example */}
|
|
136
|
+
<View gap="md">
|
|
137
|
+
<Text typography="subtitle1">Scrollable View</Text>
|
|
138
|
+
<Text typography="body2" color="secondary">
|
|
139
|
+
Fixed 200x200px container with scrollable content
|
|
140
|
+
</Text>
|
|
141
|
+
<View
|
|
142
|
+
scrollable
|
|
143
|
+
background="secondary"
|
|
144
|
+
padding="md"
|
|
145
|
+
radius="md"
|
|
146
|
+
border="thin"
|
|
147
|
+
style={{ width: 200, height: 200 }}
|
|
148
|
+
>
|
|
149
|
+
<View>
|
|
150
|
+
<Text typography="body2">Line 1: Scroll down to see more content</Text>
|
|
151
|
+
<Text typography="body2">Line 2</Text>
|
|
152
|
+
<Text typography="body2">Line 3</Text>
|
|
153
|
+
<Text typography="body2">Line 4</Text>
|
|
154
|
+
<Text typography="body2">Line 5</Text>
|
|
155
|
+
<Text typography="body2">Line 6</Text>
|
|
156
|
+
<Text typography="body2">Line 7</Text>
|
|
157
|
+
<Text typography="body2">Line 8</Text>
|
|
158
|
+
<Text typography="body2">Line 9</Text>
|
|
159
|
+
<Text typography="body2">Line 10</Text>
|
|
160
|
+
<Text typography="body2">Line 11</Text>
|
|
161
|
+
<Text typography="body2">Line 12</Text>
|
|
162
|
+
<Text typography="body2">Line 13</Text>
|
|
163
|
+
<Text typography="body2">Line 14</Text>
|
|
164
|
+
<Text typography="body2">Line 15: End of scrollable content</Text>
|
|
165
|
+
</View>
|
|
166
|
+
</View>
|
|
167
|
+
</View>
|
|
134
168
|
</View>
|
|
135
169
|
</Screen>
|
|
136
170
|
);
|
package/src/extensions/index.ts
CHANGED
|
@@ -49,13 +49,6 @@ export {
|
|
|
49
49
|
|
|
50
50
|
// Internal API (for component stylesheets)
|
|
51
51
|
export { getExtension, getReplacement } from './extendComponent';
|
|
52
|
-
export {
|
|
53
|
-
withExtension,
|
|
54
|
-
withSimpleExtension,
|
|
55
|
-
normalizeStyleFn,
|
|
56
|
-
normalizeSimpleStyleFn,
|
|
57
|
-
applyExtensions,
|
|
58
|
-
} from './applyExtension';
|
|
59
52
|
|
|
60
53
|
// Types
|
|
61
54
|
export type {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Useful when working with getWebProps and forwarding refs.
|
|
3
|
+
* Accepts various ref types including callback refs and ref objects.
|
|
4
|
+
* Uses a permissive type to handle refs from different React type versions in monorepos.
|
|
5
|
+
* @param refs - Array of refs to merge (can include undefined values)
|
|
6
|
+
* @returns A ref callback that updates all provided refs
|
|
5
7
|
*/
|
|
6
|
-
export default function useMergeRefs<T>(
|
|
7
|
-
|
|
8
|
+
export default function useMergeRefs<T>(
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
...refs: any[]
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
): any {
|
|
13
|
+
return (value: T | null) => {
|
|
8
14
|
refs.forEach((ref) => {
|
|
9
15
|
if (typeof ref === 'function') {
|
|
10
16
|
ref(value);
|
|
11
|
-
} else if (ref && 'current' in ref) {
|
|
17
|
+
} else if (ref && typeof ref === 'object' && 'current' in ref) {
|
|
12
18
|
ref.current = value;
|
|
13
19
|
}
|
|
14
20
|
});
|
|
@@ -35,6 +35,10 @@ export const MENU_KEYS = {
|
|
|
35
35
|
navigateVertical: ['ArrowUp', 'ArrowDown'] as const,
|
|
36
36
|
/** Keys for navigating horizontal menus/menubars */
|
|
37
37
|
navigateHorizontal: ['ArrowLeft', 'ArrowRight'] as const,
|
|
38
|
+
/** Keys for moving to next item (down or right) */
|
|
39
|
+
next: ['ArrowDown', 'ArrowRight'] as const,
|
|
40
|
+
/** Keys for moving to previous item (up or left) */
|
|
41
|
+
prev: ['ArrowUp', 'ArrowLeft'] as const,
|
|
38
42
|
/** Keys for selecting an item */
|
|
39
43
|
select: ['Enter', ' '] as const,
|
|
40
44
|
/** Keys for jumping to first item */
|
|
@@ -66,7 +66,11 @@ export type AriaRole =
|
|
|
66
66
|
| 'none'
|
|
67
67
|
| 'presentation'
|
|
68
68
|
| 'heading'
|
|
69
|
-
| 'group'
|
|
69
|
+
| 'group'
|
|
70
|
+
// Additional composite/group roles
|
|
71
|
+
| 'radiogroup'
|
|
72
|
+
// React Native specific roles
|
|
73
|
+
| 'adjustable';
|
|
70
74
|
|
|
71
75
|
/**
|
|
72
76
|
* Base accessibility props shared across all components.
|
|
@@ -77,7 +77,7 @@ export function useAnnounce(options: UseAnnounceOptions = {}): UseAnnounceReturn
|
|
|
77
77
|
|
|
78
78
|
const politeRegionRef = useRef<HTMLDivElement | null>(null);
|
|
79
79
|
const assertiveRegionRef = useRef<HTMLDivElement | null>(null);
|
|
80
|
-
const clearTimeoutRef = useRef<ReturnType<typeof setTimeout
|
|
80
|
+
const clearTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Create a live region element.
|
|
@@ -103,7 +103,7 @@ export function useKeyboardNavigation({
|
|
|
103
103
|
}: UseKeyboardNavigationOptions): UseKeyboardNavigationReturn {
|
|
104
104
|
// Buffer for type-ahead search
|
|
105
105
|
const searchBuffer = useRef('');
|
|
106
|
-
const searchTimeout = useRef<ReturnType<typeof setTimeout
|
|
106
|
+
const searchTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* Focus a specific item by index.
|
package/src/utils/index.ts
CHANGED