@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.
Files changed (72) hide show
  1. package/dist/appbar/index.d.ts +21 -2
  2. package/dist/appbar/index.js +207 -81
  3. package/dist/button/index.js +125 -33
  4. package/dist/card/index.js +88 -20
  5. package/dist/checkbox/index.js +88 -17
  6. package/dist/chip/index.js +122 -30
  7. package/dist/icon-button/index.js +107 -36
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +409 -270
  10. package/dist/list/index.js +71 -24
  11. package/dist/radio/index.js +43 -14
  12. package/dist/switch/index.js +90 -19
  13. package/dist/text-field/index.js +82 -26
  14. package/package.json +18 -7
  15. package/src/appbar/AppBar.tsx +0 -302
  16. package/src/appbar/index.ts +0 -2
  17. package/src/appbar/styles.ts +0 -92
  18. package/src/appbar/types.ts +0 -67
  19. package/src/button/Button.tsx +0 -133
  20. package/src/button/index.ts +0 -2
  21. package/src/button/styles.ts +0 -287
  22. package/src/button/types.ts +0 -42
  23. package/src/card/Card.tsx +0 -69
  24. package/src/card/index.ts +0 -2
  25. package/src/card/styles.ts +0 -150
  26. package/src/card/types.ts +0 -27
  27. package/src/checkbox/Checkbox.tsx +0 -113
  28. package/src/checkbox/index.ts +0 -2
  29. package/src/checkbox/styles.ts +0 -155
  30. package/src/checkbox/types.ts +0 -20
  31. package/src/chip/Chip.tsx +0 -188
  32. package/src/chip/index.ts +0 -2
  33. package/src/chip/styles.ts +0 -239
  34. package/src/chip/types.ts +0 -58
  35. package/src/icon-button/IconButton.tsx +0 -362
  36. package/src/icon-button/index.ts +0 -6
  37. package/src/icon-button/styles.ts +0 -259
  38. package/src/icon-button/types.ts +0 -55
  39. package/src/index.ts +0 -54
  40. package/src/keyboard-avoiding-wrapper/KeyboardAvoidingWrapper.tsx +0 -69
  41. package/src/keyboard-avoiding-wrapper/index.ts +0 -2
  42. package/src/keyboard-avoiding-wrapper/styles.ts +0 -10
  43. package/src/keyboard-avoiding-wrapper/types.ts +0 -37
  44. package/src/layout/Box.tsx +0 -99
  45. package/src/layout/Column.tsx +0 -16
  46. package/src/layout/Grid.tsx +0 -49
  47. package/src/layout/Layout.tsx +0 -81
  48. package/src/layout/Row.tsx +0 -22
  49. package/src/layout/index.ts +0 -13
  50. package/src/layout/resolveSpacing.ts +0 -11
  51. package/src/layout/types.ts +0 -82
  52. package/src/list/List.tsx +0 -17
  53. package/src/list/ListDivider.tsx +0 -20
  54. package/src/list/ListItem.tsx +0 -128
  55. package/src/list/index.ts +0 -9
  56. package/src/list/styles.ts +0 -132
  57. package/src/list/types.ts +0 -54
  58. package/src/radio/Radio.tsx +0 -103
  59. package/src/radio/index.ts +0 -2
  60. package/src/radio/styles.ts +0 -139
  61. package/src/radio/types.ts +0 -20
  62. package/src/switch/Switch.tsx +0 -121
  63. package/src/switch/index.ts +0 -2
  64. package/src/switch/styles.ts +0 -172
  65. package/src/switch/types.ts +0 -32
  66. package/src/text-field/TextField.tsx +0 -301
  67. package/src/text-field/index.ts +0 -2
  68. package/src/text-field/styles.ts +0 -239
  69. package/src/text-field/types.ts +0 -49
  70. package/src/typography/Typography.tsx +0 -79
  71. package/src/typography/index.ts +0 -3
  72. 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
- }
@@ -1,2 +0,0 @@
1
- export { TextField } from './TextField'
2
- export type { TextFieldProps, TextFieldVariant } from './types'
@@ -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
- }
@@ -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
- }
@@ -1,3 +0,0 @@
1
- export { Typography } from './Typography'
2
- export type { TypographyProps } from './Typography'
3
- export type { TypographyVariant } from './types'
@@ -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'