@onlynative/components 0.1.1-alpha.0 → 0.1.1-alpha.2
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/dist/appbar/index.d.ts +21 -2
- package/dist/appbar/index.js +207 -81
- package/dist/button/index.js +125 -33
- package/dist/card/index.js +88 -20
- package/dist/checkbox/index.js +88 -17
- package/dist/chip/index.js +122 -30
- package/dist/icon-button/index.js +107 -36
- package/dist/index.d.ts +1 -1
- package/dist/index.js +409 -270
- package/dist/list/index.js +71 -24
- package/dist/radio/index.js +43 -14
- package/dist/switch/index.js +90 -19
- package/dist/text-field/index.js +82 -26
- package/package.json +18 -7
- package/src/appbar/AppBar.tsx +0 -302
- package/src/appbar/index.ts +0 -2
- package/src/appbar/styles.ts +0 -92
- package/src/appbar/types.ts +0 -67
- package/src/button/Button.tsx +0 -133
- package/src/button/index.ts +0 -2
- package/src/button/styles.ts +0 -287
- package/src/button/types.ts +0 -42
- package/src/card/Card.tsx +0 -69
- package/src/card/index.ts +0 -2
- package/src/card/styles.ts +0 -150
- package/src/card/types.ts +0 -27
- package/src/checkbox/Checkbox.tsx +0 -113
- package/src/checkbox/index.ts +0 -2
- package/src/checkbox/styles.ts +0 -155
- package/src/checkbox/types.ts +0 -20
- package/src/chip/Chip.tsx +0 -188
- package/src/chip/index.ts +0 -2
- package/src/chip/styles.ts +0 -239
- package/src/chip/types.ts +0 -58
- package/src/icon-button/IconButton.tsx +0 -362
- package/src/icon-button/index.ts +0 -6
- package/src/icon-button/styles.ts +0 -259
- package/src/icon-button/types.ts +0 -55
- package/src/index.ts +0 -54
- package/src/keyboard-avoiding-wrapper/KeyboardAvoidingWrapper.tsx +0 -69
- package/src/keyboard-avoiding-wrapper/index.ts +0 -2
- package/src/keyboard-avoiding-wrapper/styles.ts +0 -10
- package/src/keyboard-avoiding-wrapper/types.ts +0 -37
- package/src/layout/Box.tsx +0 -99
- package/src/layout/Column.tsx +0 -16
- package/src/layout/Grid.tsx +0 -49
- package/src/layout/Layout.tsx +0 -81
- package/src/layout/Row.tsx +0 -22
- package/src/layout/index.ts +0 -13
- package/src/layout/resolveSpacing.ts +0 -11
- package/src/layout/types.ts +0 -82
- package/src/list/List.tsx +0 -17
- package/src/list/ListDivider.tsx +0 -20
- package/src/list/ListItem.tsx +0 -128
- package/src/list/index.ts +0 -9
- package/src/list/styles.ts +0 -132
- package/src/list/types.ts +0 -54
- package/src/radio/Radio.tsx +0 -103
- package/src/radio/index.ts +0 -2
- package/src/radio/styles.ts +0 -139
- package/src/radio/types.ts +0 -20
- package/src/switch/Switch.tsx +0 -121
- package/src/switch/index.ts +0 -2
- package/src/switch/styles.ts +0 -172
- package/src/switch/types.ts +0 -32
- package/src/text-field/TextField.tsx +0 -301
- package/src/text-field/index.ts +0 -2
- package/src/text-field/styles.ts +0 -239
- package/src/text-field/types.ts +0 -49
- package/src/typography/Typography.tsx +0 -79
- package/src/typography/index.ts +0 -3
- package/src/typography/types.ts +0 -17
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Animated,
|
|
4
|
-
Platform,
|
|
5
|
-
Pressable,
|
|
6
|
-
Text,
|
|
7
|
-
TextInput,
|
|
8
|
-
View,
|
|
9
|
-
} from 'react-native'
|
|
10
|
-
import type { NativeSyntheticEvent, TargetedEvent } from 'react-native'
|
|
11
|
-
import { useTheme } from '@onlynative/core'
|
|
12
|
-
|
|
13
|
-
import { getMaterialCommunityIcons } from '@onlynative/utils'
|
|
14
|
-
import { createStyles, labelPositions } from './styles'
|
|
15
|
-
import type { TextFieldProps } from './types'
|
|
16
|
-
|
|
17
|
-
const ICON_SIZE = 24
|
|
18
|
-
// 12dp icon inset + 24dp icon + 16dp gap
|
|
19
|
-
const ICON_WITH_GAP = 12 + 24 + 16
|
|
20
|
-
|
|
21
|
-
export function TextField({
|
|
22
|
-
value,
|
|
23
|
-
onChangeText,
|
|
24
|
-
label,
|
|
25
|
-
placeholder,
|
|
26
|
-
variant = 'filled',
|
|
27
|
-
supportingText,
|
|
28
|
-
errorText,
|
|
29
|
-
error = false,
|
|
30
|
-
disabled = false,
|
|
31
|
-
leadingIcon,
|
|
32
|
-
trailingIcon,
|
|
33
|
-
onTrailingIconPress,
|
|
34
|
-
multiline = false,
|
|
35
|
-
onFocus,
|
|
36
|
-
onBlur,
|
|
37
|
-
style,
|
|
38
|
-
containerColor,
|
|
39
|
-
contentColor,
|
|
40
|
-
inputStyle,
|
|
41
|
-
...textInputProps
|
|
42
|
-
}: TextFieldProps) {
|
|
43
|
-
const theme = useTheme()
|
|
44
|
-
const isDisabled = Boolean(disabled)
|
|
45
|
-
const isError = Boolean(error) || Boolean(errorText)
|
|
46
|
-
const isFilled = variant === 'filled'
|
|
47
|
-
const hasLeadingIcon = Boolean(leadingIcon)
|
|
48
|
-
|
|
49
|
-
const MaterialCommunityIcons =
|
|
50
|
-
leadingIcon || trailingIcon ? getMaterialCommunityIcons() : null
|
|
51
|
-
|
|
52
|
-
const { colors, styles } = useMemo(
|
|
53
|
-
() => createStyles(theme, variant),
|
|
54
|
-
[theme, variant],
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
const [isFocused, setIsFocused] = useState(false)
|
|
58
|
-
const [internalHasText, setInternalHasText] = useState(
|
|
59
|
-
() => value !== undefined && value !== '',
|
|
60
|
-
)
|
|
61
|
-
const inputRef = useRef<TextInput>(null)
|
|
62
|
-
|
|
63
|
-
const isControlled = value !== undefined
|
|
64
|
-
const hasValue = isControlled ? value !== '' : internalHasText
|
|
65
|
-
const isLabelFloated = isFocused || hasValue
|
|
66
|
-
|
|
67
|
-
// Animation: 0 = resting (label large, centered), 1 = floated (label small, top)
|
|
68
|
-
const labelAnimRef = useRef(new Animated.Value(isLabelFloated ? 1 : 0))
|
|
69
|
-
const labelAnim = labelAnimRef.current
|
|
70
|
-
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
Animated.timing(labelAnim, {
|
|
73
|
-
toValue: isLabelFloated ? 1 : 0,
|
|
74
|
-
duration: 150,
|
|
75
|
-
useNativeDriver: Platform.OS !== 'web',
|
|
76
|
-
}).start()
|
|
77
|
-
}, [isLabelFloated, labelAnim])
|
|
78
|
-
|
|
79
|
-
// Scale: bodyLarge/bodySmall when resting → 1.0 when floated.
|
|
80
|
-
// Label is always rendered at bodySmall size; scale makes it appear as bodyLarge.
|
|
81
|
-
const labelScale = useMemo(() => {
|
|
82
|
-
const restingScale =
|
|
83
|
-
theme.typography.bodyLarge.fontSize / theme.typography.bodySmall.fontSize
|
|
84
|
-
return labelAnim.interpolate({
|
|
85
|
-
inputRange: [0, 1],
|
|
86
|
-
outputRange: [restingScale, 1],
|
|
87
|
-
})
|
|
88
|
-
}, [
|
|
89
|
-
labelAnim,
|
|
90
|
-
theme.typography.bodyLarge.fontSize,
|
|
91
|
-
theme.typography.bodySmall.fontSize,
|
|
92
|
-
])
|
|
93
|
-
|
|
94
|
-
// TranslateY: moves label from floated position down to resting position.
|
|
95
|
-
// Static top = floatedTop; translateY shifts it to restingTop when at rest.
|
|
96
|
-
const labelTranslateY = useMemo(() => {
|
|
97
|
-
const restingTop = isFilled
|
|
98
|
-
? labelPositions.filledRestingTop
|
|
99
|
-
: labelPositions.outlinedRestingTop
|
|
100
|
-
const floatedTop = isFilled
|
|
101
|
-
? labelPositions.filledFloatedTop
|
|
102
|
-
: labelPositions.outlinedFloatedTop
|
|
103
|
-
const restingOffset = restingTop - floatedTop
|
|
104
|
-
return labelAnim.interpolate({
|
|
105
|
-
inputRange: [0, 1],
|
|
106
|
-
outputRange: [restingOffset, 0],
|
|
107
|
-
})
|
|
108
|
-
}, [isFilled, labelAnim])
|
|
109
|
-
|
|
110
|
-
// Label start: 16dp container padding + leading icon space (12dp inset + 24dp + 16dp gap)
|
|
111
|
-
const labelStart =
|
|
112
|
-
theme.spacing.md + (hasLeadingIcon ? ICON_WITH_GAP - theme.spacing.md : 0)
|
|
113
|
-
// Static top = floated position; translateY handles resting offset
|
|
114
|
-
const labelStaticTop = isFilled
|
|
115
|
-
? labelPositions.filledFloatedTop
|
|
116
|
-
: labelPositions.outlinedFloatedTop
|
|
117
|
-
|
|
118
|
-
const handleChangeText = useCallback(
|
|
119
|
-
(text: string) => {
|
|
120
|
-
if (!isControlled) {
|
|
121
|
-
setInternalHasText(text !== '')
|
|
122
|
-
}
|
|
123
|
-
onChangeText?.(text)
|
|
124
|
-
},
|
|
125
|
-
[isControlled, onChangeText],
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
const handleFocus = useCallback(
|
|
129
|
-
(event: NativeSyntheticEvent<TargetedEvent>) => {
|
|
130
|
-
if (isDisabled) return
|
|
131
|
-
setIsFocused(true)
|
|
132
|
-
onFocus?.(event)
|
|
133
|
-
},
|
|
134
|
-
[isDisabled, onFocus],
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
const handleBlur = useCallback(
|
|
138
|
-
(event: NativeSyntheticEvent<TargetedEvent>) => {
|
|
139
|
-
setIsFocused(false)
|
|
140
|
-
onBlur?.(event)
|
|
141
|
-
},
|
|
142
|
-
[onBlur],
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
const handleContainerPress = useCallback(() => {
|
|
146
|
-
if (!isDisabled) {
|
|
147
|
-
inputRef.current?.focus()
|
|
148
|
-
}
|
|
149
|
-
}, [isDisabled])
|
|
150
|
-
|
|
151
|
-
const labelColor = isDisabled
|
|
152
|
-
? colors.disabledLabelColor
|
|
153
|
-
: isError
|
|
154
|
-
? colors.errorLabelColor
|
|
155
|
-
: isFocused
|
|
156
|
-
? colors.focusedLabelColor
|
|
157
|
-
: colors.labelColor
|
|
158
|
-
|
|
159
|
-
const labelBackgroundColor =
|
|
160
|
-
variant === 'outlined' && isLabelFloated
|
|
161
|
-
? theme.colors.surface
|
|
162
|
-
: 'transparent'
|
|
163
|
-
|
|
164
|
-
const iconColor = isDisabled
|
|
165
|
-
? colors.disabledIconColor
|
|
166
|
-
: isError
|
|
167
|
-
? colors.errorIconColor
|
|
168
|
-
: contentColor ?? colors.iconColor
|
|
169
|
-
|
|
170
|
-
const containerStyle = useMemo(
|
|
171
|
-
() => [
|
|
172
|
-
styles.container,
|
|
173
|
-
containerColor && !isDisabled
|
|
174
|
-
? { backgroundColor: containerColor }
|
|
175
|
-
: undefined,
|
|
176
|
-
isFocused && styles.containerFocused,
|
|
177
|
-
isError && !isFocused && styles.containerError,
|
|
178
|
-
isDisabled && styles.containerDisabled,
|
|
179
|
-
],
|
|
180
|
-
[styles, isFocused, isError, isDisabled, containerColor],
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
const indicatorStyle = useMemo(
|
|
184
|
-
() => [
|
|
185
|
-
styles.indicator,
|
|
186
|
-
isFocused && styles.indicatorFocused,
|
|
187
|
-
isError && !isFocused && styles.indicatorError,
|
|
188
|
-
isDisabled && styles.indicatorDisabled,
|
|
189
|
-
],
|
|
190
|
-
[styles, isFocused, isError, isDisabled],
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
const displaySupportingText = isError ? errorText : supportingText
|
|
194
|
-
|
|
195
|
-
return (
|
|
196
|
-
<View style={[styles.root, style]}>
|
|
197
|
-
<Pressable onPress={handleContainerPress} disabled={isDisabled}>
|
|
198
|
-
<View style={containerStyle}>
|
|
199
|
-
{leadingIcon ? (
|
|
200
|
-
<View style={styles.leadingIcon}>
|
|
201
|
-
<MaterialCommunityIcons
|
|
202
|
-
name={leadingIcon}
|
|
203
|
-
size={ICON_SIZE}
|
|
204
|
-
color={iconColor}
|
|
205
|
-
/>
|
|
206
|
-
</View>
|
|
207
|
-
) : null}
|
|
208
|
-
|
|
209
|
-
<View
|
|
210
|
-
style={[
|
|
211
|
-
styles.inputWrapper,
|
|
212
|
-
label ? styles.inputWrapperWithLabel : undefined,
|
|
213
|
-
]}
|
|
214
|
-
>
|
|
215
|
-
<TextInput
|
|
216
|
-
ref={inputRef}
|
|
217
|
-
{...textInputProps}
|
|
218
|
-
value={value}
|
|
219
|
-
onChangeText={handleChangeText}
|
|
220
|
-
editable={!isDisabled}
|
|
221
|
-
onFocus={handleFocus}
|
|
222
|
-
onBlur={handleBlur}
|
|
223
|
-
placeholder={isLabelFloated || !label ? placeholder : undefined}
|
|
224
|
-
placeholderTextColor={colors.placeholderColor}
|
|
225
|
-
multiline={multiline}
|
|
226
|
-
style={[
|
|
227
|
-
styles.input,
|
|
228
|
-
isDisabled ? styles.inputDisabled : undefined,
|
|
229
|
-
contentColor && !isDisabled
|
|
230
|
-
? { color: contentColor }
|
|
231
|
-
: undefined,
|
|
232
|
-
inputStyle,
|
|
233
|
-
]}
|
|
234
|
-
accessibilityLabel={label || undefined}
|
|
235
|
-
accessibilityState={{ disabled: isDisabled }}
|
|
236
|
-
accessibilityHint={isError && errorText ? errorText : undefined}
|
|
237
|
-
/>
|
|
238
|
-
</View>
|
|
239
|
-
|
|
240
|
-
{trailingIcon ? (
|
|
241
|
-
<Pressable
|
|
242
|
-
onPress={onTrailingIconPress}
|
|
243
|
-
disabled={isDisabled || !onTrailingIconPress}
|
|
244
|
-
accessibilityRole="button"
|
|
245
|
-
hitSlop={12}
|
|
246
|
-
style={styles.trailingIconPressable}
|
|
247
|
-
>
|
|
248
|
-
<View style={styles.trailingIcon}>
|
|
249
|
-
<MaterialCommunityIcons
|
|
250
|
-
name={trailingIcon}
|
|
251
|
-
size={ICON_SIZE}
|
|
252
|
-
color={iconColor}
|
|
253
|
-
/>
|
|
254
|
-
</View>
|
|
255
|
-
</Pressable>
|
|
256
|
-
) : null}
|
|
257
|
-
|
|
258
|
-
{/* Label: rendered at bodySmall, scaled up via transform when resting */}
|
|
259
|
-
{label ? (
|
|
260
|
-
<Animated.Text
|
|
261
|
-
numberOfLines={1}
|
|
262
|
-
style={[
|
|
263
|
-
styles.label,
|
|
264
|
-
{
|
|
265
|
-
top: labelStaticTop,
|
|
266
|
-
start: labelStart,
|
|
267
|
-
color: labelColor,
|
|
268
|
-
backgroundColor: labelBackgroundColor,
|
|
269
|
-
transform: [
|
|
270
|
-
{ translateY: labelTranslateY },
|
|
271
|
-
{ scale: labelScale },
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
variant === 'outlined' && isLabelFloated
|
|
275
|
-
? styles.labelNotch
|
|
276
|
-
: undefined,
|
|
277
|
-
]}
|
|
278
|
-
>
|
|
279
|
-
{label}
|
|
280
|
-
</Animated.Text>
|
|
281
|
-
) : null}
|
|
282
|
-
|
|
283
|
-
{isFilled ? <View style={indicatorStyle} /> : null}
|
|
284
|
-
</View>
|
|
285
|
-
</Pressable>
|
|
286
|
-
|
|
287
|
-
{displaySupportingText ? (
|
|
288
|
-
<View style={styles.supportingTextRow}>
|
|
289
|
-
<Text
|
|
290
|
-
style={[
|
|
291
|
-
styles.supportingText,
|
|
292
|
-
isError ? styles.errorSupportingText : undefined,
|
|
293
|
-
]}
|
|
294
|
-
>
|
|
295
|
-
{displaySupportingText}
|
|
296
|
-
</Text>
|
|
297
|
-
</View>
|
|
298
|
-
) : null}
|
|
299
|
-
</View>
|
|
300
|
-
)
|
|
301
|
-
}
|
package/src/text-field/index.ts
DELETED
package/src/text-field/styles.ts
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { StyleSheet } from 'react-native'
|
|
2
|
-
import type { MaterialTheme } from '@onlynative/core'
|
|
3
|
-
|
|
4
|
-
import type { TextFieldVariant } from './types'
|
|
5
|
-
import { alphaColor, transformOrigin } from '@onlynative/utils'
|
|
6
|
-
|
|
7
|
-
const CONTAINER_HEIGHT = 56
|
|
8
|
-
const ICON_SIZE = 24
|
|
9
|
-
const LABEL_FLOATED_LINE_HEIGHT = 16
|
|
10
|
-
|
|
11
|
-
// Filled: label floated 8dp from top, input at 24dp (8 + 16 label height), 8dp bottom.
|
|
12
|
-
// Label resting = vertically centered = (56 - 24) / 2 = 16dp from top.
|
|
13
|
-
const FILLED_LABEL_RESTING_TOP = 16
|
|
14
|
-
const FILLED_LABEL_FLOATED_TOP = 8
|
|
15
|
-
const FILLED_INPUT_TOP = 24 // 8dp label top + 16dp label line-height
|
|
16
|
-
const FILLED_INPUT_BOTTOM = 8
|
|
17
|
-
|
|
18
|
-
// Outlined: input centered 16dp top/bottom. Label resting = same 16dp.
|
|
19
|
-
// Label floated = centered on the border = -(lineHeight / 2).
|
|
20
|
-
const OUTLINED_INPUT_VERTICAL = 16
|
|
21
|
-
const OUTLINED_LABEL_RESTING_TOP = 16
|
|
22
|
-
const OUTLINED_LABEL_FLOATED_TOP = -(LABEL_FLOATED_LINE_HEIGHT / 2) // -8
|
|
23
|
-
|
|
24
|
-
export const labelPositions = {
|
|
25
|
-
filledRestingTop: FILLED_LABEL_RESTING_TOP,
|
|
26
|
-
filledFloatedTop: FILLED_LABEL_FLOATED_TOP,
|
|
27
|
-
outlinedRestingTop: OUTLINED_LABEL_RESTING_TOP,
|
|
28
|
-
outlinedFloatedTop: OUTLINED_LABEL_FLOATED_TOP,
|
|
29
|
-
} as const
|
|
30
|
-
|
|
31
|
-
interface VariantColors {
|
|
32
|
-
backgroundColor: string
|
|
33
|
-
borderColor: string
|
|
34
|
-
focusedBorderColor: string
|
|
35
|
-
errorBorderColor: string
|
|
36
|
-
disabledBorderColor: string
|
|
37
|
-
disabledBackgroundColor: string
|
|
38
|
-
labelColor: string
|
|
39
|
-
focusedLabelColor: string
|
|
40
|
-
errorLabelColor: string
|
|
41
|
-
disabledLabelColor: string
|
|
42
|
-
textColor: string
|
|
43
|
-
disabledTextColor: string
|
|
44
|
-
placeholderColor: string
|
|
45
|
-
supportingTextColor: string
|
|
46
|
-
errorSupportingTextColor: string
|
|
47
|
-
iconColor: string
|
|
48
|
-
errorIconColor: string
|
|
49
|
-
disabledIconColor: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function getVariantColors(
|
|
53
|
-
theme: MaterialTheme,
|
|
54
|
-
variant: TextFieldVariant,
|
|
55
|
-
): VariantColors {
|
|
56
|
-
const disabledOpacity = theme.stateLayer.disabledOpacity
|
|
57
|
-
|
|
58
|
-
const common = {
|
|
59
|
-
focusedBorderColor: theme.colors.primary,
|
|
60
|
-
errorBorderColor: theme.colors.error,
|
|
61
|
-
focusedLabelColor: theme.colors.primary,
|
|
62
|
-
errorLabelColor: theme.colors.error,
|
|
63
|
-
textColor: theme.colors.onSurface,
|
|
64
|
-
disabledTextColor: alphaColor(theme.colors.onSurface, disabledOpacity),
|
|
65
|
-
disabledLabelColor: alphaColor(theme.colors.onSurface, disabledOpacity),
|
|
66
|
-
disabledBorderColor: alphaColor(theme.colors.onSurface, 0.12),
|
|
67
|
-
placeholderColor: theme.colors.onSurfaceVariant,
|
|
68
|
-
supportingTextColor: theme.colors.onSurfaceVariant,
|
|
69
|
-
errorSupportingTextColor: theme.colors.error,
|
|
70
|
-
iconColor: theme.colors.onSurfaceVariant,
|
|
71
|
-
errorIconColor: theme.colors.error,
|
|
72
|
-
disabledIconColor: alphaColor(theme.colors.onSurface, disabledOpacity),
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (variant === 'outlined') {
|
|
76
|
-
return {
|
|
77
|
-
...common,
|
|
78
|
-
backgroundColor: 'transparent',
|
|
79
|
-
borderColor: theme.colors.outline,
|
|
80
|
-
disabledBackgroundColor: 'transparent',
|
|
81
|
-
labelColor: theme.colors.onSurfaceVariant,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
...common,
|
|
87
|
-
backgroundColor: theme.colors.surfaceContainerHighest,
|
|
88
|
-
borderColor: theme.colors.onSurfaceVariant,
|
|
89
|
-
disabledBackgroundColor: alphaColor(theme.colors.onSurface, 0.04),
|
|
90
|
-
labelColor: theme.colors.onSurfaceVariant,
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function createStyles(theme: MaterialTheme, variant: TextFieldVariant) {
|
|
95
|
-
const colors = getVariantColors(theme, variant)
|
|
96
|
-
const bodyLarge = theme.typography.bodyLarge
|
|
97
|
-
const bodySmall = theme.typography.bodySmall
|
|
98
|
-
const isFilled = variant === 'filled'
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
colors,
|
|
102
|
-
styles: StyleSheet.create({
|
|
103
|
-
root: {
|
|
104
|
-
alignSelf: 'stretch',
|
|
105
|
-
},
|
|
106
|
-
container: {
|
|
107
|
-
minHeight: CONTAINER_HEIGHT,
|
|
108
|
-
flexDirection: 'row',
|
|
109
|
-
alignItems: 'stretch',
|
|
110
|
-
backgroundColor: colors.backgroundColor,
|
|
111
|
-
paddingHorizontal: theme.spacing.md,
|
|
112
|
-
...(isFilled
|
|
113
|
-
? {
|
|
114
|
-
borderTopStartRadius: theme.shape.cornerExtraSmall,
|
|
115
|
-
borderTopEndRadius: theme.shape.cornerExtraSmall,
|
|
116
|
-
}
|
|
117
|
-
: {
|
|
118
|
-
borderRadius: theme.shape.cornerExtraSmall,
|
|
119
|
-
borderWidth: 1,
|
|
120
|
-
borderColor: colors.borderColor,
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
|
-
containerFocused: isFilled
|
|
124
|
-
? {}
|
|
125
|
-
: {
|
|
126
|
-
borderWidth: 2,
|
|
127
|
-
borderColor: colors.focusedBorderColor,
|
|
128
|
-
paddingHorizontal: theme.spacing.md - 1,
|
|
129
|
-
},
|
|
130
|
-
containerError: isFilled
|
|
131
|
-
? {}
|
|
132
|
-
: {
|
|
133
|
-
borderWidth: 2,
|
|
134
|
-
borderColor: colors.errorBorderColor,
|
|
135
|
-
paddingHorizontal: theme.spacing.md - 1,
|
|
136
|
-
},
|
|
137
|
-
containerDisabled: isFilled
|
|
138
|
-
? { backgroundColor: colors.disabledBackgroundColor }
|
|
139
|
-
: {
|
|
140
|
-
borderColor: colors.disabledBorderColor,
|
|
141
|
-
},
|
|
142
|
-
indicator: {
|
|
143
|
-
position: 'absolute',
|
|
144
|
-
start: 0,
|
|
145
|
-
end: 0,
|
|
146
|
-
bottom: 0,
|
|
147
|
-
height: 1,
|
|
148
|
-
backgroundColor: colors.borderColor,
|
|
149
|
-
},
|
|
150
|
-
indicatorFocused: {
|
|
151
|
-
height: 2,
|
|
152
|
-
backgroundColor: colors.focusedBorderColor,
|
|
153
|
-
},
|
|
154
|
-
indicatorError: {
|
|
155
|
-
height: 2,
|
|
156
|
-
backgroundColor: colors.errorBorderColor,
|
|
157
|
-
},
|
|
158
|
-
indicatorDisabled: {
|
|
159
|
-
backgroundColor: colors.disabledBorderColor,
|
|
160
|
-
},
|
|
161
|
-
inputWrapper: {
|
|
162
|
-
flex: 1,
|
|
163
|
-
justifyContent: 'center',
|
|
164
|
-
},
|
|
165
|
-
// When label is present, use explicit padding so the input position
|
|
166
|
-
// matches the label resting top exactly.
|
|
167
|
-
inputWrapperWithLabel: {
|
|
168
|
-
justifyContent: 'flex-start',
|
|
169
|
-
paddingTop: isFilled ? FILLED_INPUT_TOP : OUTLINED_INPUT_VERTICAL,
|
|
170
|
-
paddingBottom: isFilled ? FILLED_INPUT_BOTTOM : OUTLINED_INPUT_VERTICAL,
|
|
171
|
-
},
|
|
172
|
-
label: {
|
|
173
|
-
position: 'absolute',
|
|
174
|
-
zIndex: 1,
|
|
175
|
-
fontFamily: bodySmall.fontFamily,
|
|
176
|
-
fontSize: bodySmall.fontSize,
|
|
177
|
-
lineHeight: bodySmall.lineHeight,
|
|
178
|
-
fontWeight: bodySmall.fontWeight,
|
|
179
|
-
letterSpacing: bodySmall.letterSpacing,
|
|
180
|
-
color: colors.labelColor,
|
|
181
|
-
transformOrigin: transformOrigin('top'),
|
|
182
|
-
},
|
|
183
|
-
labelNotch: {
|
|
184
|
-
paddingHorizontal: 4,
|
|
185
|
-
},
|
|
186
|
-
input: {
|
|
187
|
-
fontFamily: bodyLarge.fontFamily,
|
|
188
|
-
fontSize: bodyLarge.fontSize,
|
|
189
|
-
lineHeight: bodyLarge.lineHeight,
|
|
190
|
-
fontWeight: bodyLarge.fontWeight,
|
|
191
|
-
letterSpacing: bodyLarge.letterSpacing,
|
|
192
|
-
color: colors.textColor,
|
|
193
|
-
paddingVertical: 0,
|
|
194
|
-
paddingHorizontal: 0,
|
|
195
|
-
margin: 0,
|
|
196
|
-
includeFontPadding: false,
|
|
197
|
-
},
|
|
198
|
-
inputDisabled: {
|
|
199
|
-
color: colors.disabledTextColor,
|
|
200
|
-
},
|
|
201
|
-
leadingIcon: {
|
|
202
|
-
alignSelf: 'center',
|
|
203
|
-
marginStart: -4, // 16dp container padding → 12dp icon inset per M3
|
|
204
|
-
marginEnd: theme.spacing.md,
|
|
205
|
-
width: ICON_SIZE,
|
|
206
|
-
height: ICON_SIZE,
|
|
207
|
-
alignItems: 'center',
|
|
208
|
-
justifyContent: 'center',
|
|
209
|
-
},
|
|
210
|
-
trailingIcon: {
|
|
211
|
-
alignSelf: 'center',
|
|
212
|
-
marginStart: theme.spacing.md,
|
|
213
|
-
marginEnd: -4, // 16dp container padding → 12dp icon inset per M3
|
|
214
|
-
width: ICON_SIZE,
|
|
215
|
-
height: ICON_SIZE,
|
|
216
|
-
alignItems: 'center',
|
|
217
|
-
justifyContent: 'center',
|
|
218
|
-
},
|
|
219
|
-
trailingIconPressable: {
|
|
220
|
-
alignSelf: 'center',
|
|
221
|
-
},
|
|
222
|
-
supportingTextRow: {
|
|
223
|
-
paddingHorizontal: theme.spacing.md,
|
|
224
|
-
paddingTop: theme.spacing.xs,
|
|
225
|
-
},
|
|
226
|
-
supportingText: {
|
|
227
|
-
fontFamily: bodySmall.fontFamily,
|
|
228
|
-
fontSize: bodySmall.fontSize,
|
|
229
|
-
lineHeight: bodySmall.lineHeight,
|
|
230
|
-
fontWeight: bodySmall.fontWeight,
|
|
231
|
-
letterSpacing: bodySmall.letterSpacing,
|
|
232
|
-
color: colors.supportingTextColor,
|
|
233
|
-
},
|
|
234
|
-
errorSupportingText: {
|
|
235
|
-
color: colors.errorSupportingTextColor,
|
|
236
|
-
},
|
|
237
|
-
}),
|
|
238
|
-
}
|
|
239
|
-
}
|
package/src/text-field/types.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
|
|
2
|
-
import type { ComponentProps } from 'react'
|
|
3
|
-
import type { StyleProp, TextInputProps, TextStyle } from 'react-native'
|
|
4
|
-
|
|
5
|
-
/** Visual container style for the text field. */
|
|
6
|
-
export type TextFieldVariant = 'filled' | 'outlined'
|
|
7
|
-
|
|
8
|
-
export interface TextFieldProps
|
|
9
|
-
extends Omit<TextInputProps, 'placeholderTextColor' | 'editable'> {
|
|
10
|
-
/** Floating label text. Animates above the input when focused or filled. */
|
|
11
|
-
label?: string
|
|
12
|
-
/**
|
|
13
|
-
* Container style.
|
|
14
|
-
* @default 'filled'
|
|
15
|
-
*/
|
|
16
|
-
variant?: TextFieldVariant
|
|
17
|
-
/** Helper text displayed below the field. Hidden when `error` or `errorText` is active. */
|
|
18
|
-
supportingText?: string
|
|
19
|
-
/** Error message. When provided, implicitly sets `error` to `true` and replaces `supportingText`. */
|
|
20
|
-
errorText?: string
|
|
21
|
-
/**
|
|
22
|
-
* When `true`, renders the field in error state with error colors.
|
|
23
|
-
* @default false
|
|
24
|
-
*/
|
|
25
|
-
error?: boolean
|
|
26
|
-
/**
|
|
27
|
-
* Disables text input and reduces opacity.
|
|
28
|
-
* @default false
|
|
29
|
-
*/
|
|
30
|
-
disabled?: boolean
|
|
31
|
-
/** MaterialCommunityIcons icon name rendered at the start of the field. */
|
|
32
|
-
leadingIcon?: ComponentProps<typeof MaterialCommunityIcons>['name']
|
|
33
|
-
/** MaterialCommunityIcons icon name rendered at the end of the field. */
|
|
34
|
-
trailingIcon?: ComponentProps<typeof MaterialCommunityIcons>['name']
|
|
35
|
-
/** Called when the trailing icon is pressed. */
|
|
36
|
-
onTrailingIconPress?: () => void
|
|
37
|
-
/**
|
|
38
|
-
* Override the container (background) color.
|
|
39
|
-
* Disabled state still uses the standard disabled appearance.
|
|
40
|
-
*/
|
|
41
|
-
containerColor?: string
|
|
42
|
-
/**
|
|
43
|
-
* Override the content (input text and icon) color.
|
|
44
|
-
* Error and disabled states take precedence.
|
|
45
|
-
*/
|
|
46
|
-
contentColor?: string
|
|
47
|
-
/** Additional style applied to the text input element. */
|
|
48
|
-
inputStyle?: StyleProp<TextStyle>
|
|
49
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
import type { ComponentType, ReactNode } from 'react'
|
|
3
|
-
import type { StyleProp, TextProps, TextStyle } from 'react-native'
|
|
4
|
-
import { StyleSheet, Text } from 'react-native'
|
|
5
|
-
import { useTheme } from '@onlynative/core'
|
|
6
|
-
import type { MaterialTheme } from '@onlynative/core'
|
|
7
|
-
|
|
8
|
-
import type { TypographyVariant } from './types'
|
|
9
|
-
|
|
10
|
-
const HEADING_VARIANTS: ReadonlySet<TypographyVariant> = new Set([
|
|
11
|
-
'displayLarge',
|
|
12
|
-
'displayMedium',
|
|
13
|
-
'displaySmall',
|
|
14
|
-
'headlineLarge',
|
|
15
|
-
'headlineMedium',
|
|
16
|
-
'headlineSmall',
|
|
17
|
-
])
|
|
18
|
-
|
|
19
|
-
export interface TypographyProps extends Omit<TextProps, 'children' | 'style'> {
|
|
20
|
-
/** Content to display. Accepts strings, numbers, or nested elements. */
|
|
21
|
-
children: ReactNode
|
|
22
|
-
/**
|
|
23
|
-
* MD3 type scale role. Controls font size, weight, line height, and letter spacing.
|
|
24
|
-
* @default 'bodyMedium'
|
|
25
|
-
*/
|
|
26
|
-
variant?: TypographyVariant
|
|
27
|
-
/** Override the text color. Takes priority over `style.color`. Defaults to the theme's `onSurface` color. */
|
|
28
|
-
color?: string
|
|
29
|
-
/** Additional text styles. Can override the default theme color via `style.color` when no `color` prop is set. */
|
|
30
|
-
style?: StyleProp<TextStyle>
|
|
31
|
-
/**
|
|
32
|
-
* Override the underlying text component (e.g. Animated.Text).
|
|
33
|
-
* @default Text
|
|
34
|
-
*/
|
|
35
|
-
as?: ComponentType<TextProps>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function Typography({
|
|
39
|
-
children,
|
|
40
|
-
variant = 'bodyMedium',
|
|
41
|
-
color,
|
|
42
|
-
style,
|
|
43
|
-
as: Component = Text,
|
|
44
|
-
accessibilityRole,
|
|
45
|
-
...textProps
|
|
46
|
-
}: TypographyProps) {
|
|
47
|
-
const theme = useTheme() as MaterialTheme
|
|
48
|
-
const typographyStyle = theme.typography[variant]
|
|
49
|
-
const resolvedRole =
|
|
50
|
-
accessibilityRole ?? (HEADING_VARIANTS.has(variant) ? 'header' : undefined)
|
|
51
|
-
|
|
52
|
-
// When the consumer overrides fontSize via style, auto-adjust lineHeight
|
|
53
|
-
// proportionally so text isn't clipped inside overflow:hidden containers.
|
|
54
|
-
// Skipped when: no style prop (theme lineHeight is already proportional),
|
|
55
|
-
// no fontSize override, or consumer explicitly sets lineHeight.
|
|
56
|
-
const lineHeightFix = useMemo(() => {
|
|
57
|
-
if (!style) return undefined
|
|
58
|
-
const flat = StyleSheet.flatten(style)
|
|
59
|
-
if (!flat?.fontSize || flat.lineHeight) return undefined
|
|
60
|
-
const ratio = typographyStyle.lineHeight / typographyStyle.fontSize
|
|
61
|
-
return { lineHeight: Math.ceil(flat.fontSize * ratio) }
|
|
62
|
-
}, [style, typographyStyle.fontSize, typographyStyle.lineHeight])
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<Component
|
|
66
|
-
{...textProps}
|
|
67
|
-
accessibilityRole={resolvedRole}
|
|
68
|
-
style={[
|
|
69
|
-
{ color: theme.colors.onSurface },
|
|
70
|
-
typographyStyle,
|
|
71
|
-
style,
|
|
72
|
-
lineHeightFix,
|
|
73
|
-
color != null ? { color } : undefined,
|
|
74
|
-
]}
|
|
75
|
-
>
|
|
76
|
-
{children}
|
|
77
|
-
</Component>
|
|
78
|
-
)
|
|
79
|
-
}
|
package/src/typography/index.ts
DELETED
package/src/typography/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/** Material Design 3 type scale role. */
|
|
2
|
-
export type TypographyVariant =
|
|
3
|
-
| 'displayLarge'
|
|
4
|
-
| 'displayMedium'
|
|
5
|
-
| 'displaySmall'
|
|
6
|
-
| 'headlineLarge'
|
|
7
|
-
| 'headlineMedium'
|
|
8
|
-
| 'headlineSmall'
|
|
9
|
-
| 'titleLarge'
|
|
10
|
-
| 'titleMedium'
|
|
11
|
-
| 'titleSmall'
|
|
12
|
-
| 'bodyLarge'
|
|
13
|
-
| 'bodyMedium'
|
|
14
|
-
| 'bodySmall'
|
|
15
|
-
| 'labelLarge'
|
|
16
|
-
| 'labelMedium'
|
|
17
|
-
| 'labelSmall'
|