@onlynative/components 0.1.0-alpha.0

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 (87) hide show
  1. package/README.md +99 -0
  2. package/dist/appbar/index.d.ts +71 -0
  3. package/dist/appbar/index.js +952 -0
  4. package/dist/button/index.d.ts +41 -0
  5. package/dist/button/index.js +454 -0
  6. package/dist/card/index.d.ts +31 -0
  7. package/dist/card/index.js +264 -0
  8. package/dist/checkbox/index.d.ts +25 -0
  9. package/dist/checkbox/index.js +291 -0
  10. package/dist/chip/index.d.ts +62 -0
  11. package/dist/chip/index.js +452 -0
  12. package/dist/icon-button/index.d.ts +10 -0
  13. package/dist/icon-button/index.js +575 -0
  14. package/dist/index.d.ts +19 -0
  15. package/dist/index.js +3374 -0
  16. package/dist/layout/index.d.ts +98 -0
  17. package/dist/layout/index.js +282 -0
  18. package/dist/list/index.d.ts +60 -0
  19. package/dist/list/index.js +300 -0
  20. package/dist/radio/index.d.ts +25 -0
  21. package/dist/radio/index.js +250 -0
  22. package/dist/switch/index.d.ts +37 -0
  23. package/dist/switch/index.js +315 -0
  24. package/dist/text-field/index.d.ts +52 -0
  25. package/dist/text-field/index.js +496 -0
  26. package/dist/types-D3hlyvz-.d.ts +51 -0
  27. package/dist/typography/index.d.ts +28 -0
  28. package/dist/typography/index.js +69 -0
  29. package/package.json +166 -0
  30. package/src/appbar/AppBar.tsx +302 -0
  31. package/src/appbar/index.ts +2 -0
  32. package/src/appbar/styles.ts +92 -0
  33. package/src/appbar/types.ts +67 -0
  34. package/src/button/Button.tsx +130 -0
  35. package/src/button/index.ts +2 -0
  36. package/src/button/styles.ts +288 -0
  37. package/src/button/types.ts +42 -0
  38. package/src/card/Card.tsx +69 -0
  39. package/src/card/index.ts +2 -0
  40. package/src/card/styles.ts +151 -0
  41. package/src/card/types.ts +27 -0
  42. package/src/checkbox/Checkbox.tsx +109 -0
  43. package/src/checkbox/index.ts +2 -0
  44. package/src/checkbox/styles.ts +155 -0
  45. package/src/checkbox/types.ts +20 -0
  46. package/src/chip/Chip.tsx +182 -0
  47. package/src/chip/index.ts +2 -0
  48. package/src/chip/styles.ts +240 -0
  49. package/src/chip/types.ts +58 -0
  50. package/src/icon-button/IconButton.tsx +358 -0
  51. package/src/icon-button/index.ts +6 -0
  52. package/src/icon-button/styles.ts +259 -0
  53. package/src/icon-button/types.ts +55 -0
  54. package/src/index.ts +51 -0
  55. package/src/layout/Box.tsx +99 -0
  56. package/src/layout/Column.tsx +16 -0
  57. package/src/layout/Grid.tsx +49 -0
  58. package/src/layout/Layout.tsx +81 -0
  59. package/src/layout/Row.tsx +22 -0
  60. package/src/layout/index.ts +13 -0
  61. package/src/layout/resolveSpacing.ts +11 -0
  62. package/src/layout/types.ts +82 -0
  63. package/src/list/List.tsx +17 -0
  64. package/src/list/ListDivider.tsx +20 -0
  65. package/src/list/ListItem.tsx +128 -0
  66. package/src/list/index.ts +9 -0
  67. package/src/list/styles.ts +132 -0
  68. package/src/list/types.ts +54 -0
  69. package/src/radio/Radio.tsx +103 -0
  70. package/src/radio/index.ts +2 -0
  71. package/src/radio/styles.ts +139 -0
  72. package/src/radio/types.ts +20 -0
  73. package/src/switch/Switch.tsx +118 -0
  74. package/src/switch/index.ts +2 -0
  75. package/src/switch/styles.ts +172 -0
  76. package/src/switch/types.ts +32 -0
  77. package/src/test-utils/render-with-theme.tsx +13 -0
  78. package/src/text-field/TextField.tsx +298 -0
  79. package/src/text-field/index.ts +2 -0
  80. package/src/text-field/styles.ts +240 -0
  81. package/src/text-field/types.ts +49 -0
  82. package/src/typography/Typography.tsx +65 -0
  83. package/src/typography/index.ts +3 -0
  84. package/src/typography/types.ts +17 -0
  85. package/src/utils/color.ts +64 -0
  86. package/src/utils/elevation.ts +33 -0
  87. package/src/utils/rtl.ts +19 -0
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { ViewProps } from 'react-native'
3
+
4
+ /** Surface style variant of the card following Material Design 3 roles. */
5
+ export type CardVariant = 'elevated' | 'filled' | 'outlined'
6
+
7
+ export interface CardProps extends ViewProps {
8
+ /** Content rendered inside the card surface. */
9
+ children: ReactNode
10
+ /**
11
+ * Surface style variant.
12
+ * @default 'elevated'
13
+ */
14
+ variant?: CardVariant
15
+ /** When provided, the card becomes interactive (Pressable). Omit to render as a plain View. */
16
+ onPress?: () => void
17
+ /**
18
+ * Disables the press interaction and reduces opacity. Only effective when `onPress` is provided.
19
+ * @default false
20
+ */
21
+ disabled?: boolean
22
+ /**
23
+ * Override the container (background) color.
24
+ * State-layer colors (hover, press) are derived automatically.
25
+ */
26
+ containerColor?: string
27
+ }
@@ -0,0 +1,109 @@
1
+ import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
2
+ import { useMemo } from 'react'
3
+ import { Platform, Pressable, StyleSheet, View } from 'react-native'
4
+ import type { StyleProp, ViewStyle } from 'react-native'
5
+ import { useTheme } from '@onlynative/core'
6
+
7
+ import { createStyles } from './styles'
8
+ import type { CheckboxProps } from './types'
9
+
10
+ interface PressableState {
11
+ pressed: boolean
12
+ hovered?: boolean
13
+ }
14
+
15
+ function resolveStyle(
16
+ containerStyle: StyleProp<ViewStyle>,
17
+ hoveredContainerStyle: StyleProp<ViewStyle>,
18
+ pressedContainerStyle: StyleProp<ViewStyle>,
19
+ disabledContainerStyle: StyleProp<ViewStyle>,
20
+ disabled: boolean,
21
+ style: CheckboxProps['style'],
22
+ ): (state: PressableState) => StyleProp<ViewStyle> {
23
+ if (typeof style === 'function') {
24
+ return (state) => [
25
+ containerStyle,
26
+ state.hovered && !state.pressed && !disabled
27
+ ? hoveredContainerStyle
28
+ : undefined,
29
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
30
+ disabled ? disabledContainerStyle : undefined,
31
+ style(state),
32
+ ]
33
+ }
34
+
35
+ return (state) => [
36
+ containerStyle,
37
+ state.hovered && !state.pressed && !disabled
38
+ ? hoveredContainerStyle
39
+ : undefined,
40
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
41
+ disabled ? disabledContainerStyle : undefined,
42
+ style,
43
+ ]
44
+ }
45
+
46
+ export function Checkbox({
47
+ style,
48
+ value = false,
49
+ onValueChange,
50
+ containerColor,
51
+ contentColor,
52
+ disabled = false,
53
+ ...props
54
+ }: CheckboxProps) {
55
+ const isDisabled = Boolean(disabled)
56
+ const isChecked = Boolean(value)
57
+
58
+ const theme = useTheme()
59
+ const styles = useMemo(
60
+ () => createStyles(theme, isChecked, containerColor, contentColor),
61
+ [theme, isChecked, containerColor, contentColor],
62
+ )
63
+
64
+ const resolvedIconColor = useMemo(() => {
65
+ const base = StyleSheet.flatten([
66
+ styles.iconColor,
67
+ isDisabled ? styles.disabledIconColor : undefined,
68
+ ])
69
+ return typeof base?.color === 'string' ? base.color : undefined
70
+ }, [styles.iconColor, styles.disabledIconColor, isDisabled])
71
+
72
+ const handlePress = () => {
73
+ if (!isDisabled) {
74
+ onValueChange?.(!isChecked)
75
+ }
76
+ }
77
+
78
+ return (
79
+ <Pressable
80
+ {...props}
81
+ accessibilityRole="checkbox"
82
+ accessibilityState={{
83
+ disabled: isDisabled,
84
+ checked: isChecked,
85
+ }}
86
+ hitSlop={Platform.OS === 'web' ? undefined : 4}
87
+ disabled={isDisabled}
88
+ onPress={handlePress}
89
+ style={resolveStyle(
90
+ styles.container,
91
+ styles.hoveredContainer,
92
+ styles.pressedContainer,
93
+ styles.disabledContainer,
94
+ isDisabled,
95
+ style,
96
+ )}
97
+ >
98
+ <View style={[styles.box, isDisabled ? styles.disabledBox : undefined]}>
99
+ {isChecked ? (
100
+ <MaterialCommunityIcons
101
+ name="check"
102
+ size={14}
103
+ color={resolvedIconColor}
104
+ />
105
+ ) : null}
106
+ </View>
107
+ </Pressable>
108
+ )
109
+ }
@@ -0,0 +1,2 @@
1
+ export { Checkbox } from './Checkbox'
2
+ export type { CheckboxProps } from './types'
@@ -0,0 +1,155 @@
1
+ import { StyleSheet } from 'react-native'
2
+ import type { Theme } from '@onlynative/core'
3
+
4
+ import { alphaColor, blendColor } from '../utils/color'
5
+
6
+ interface BoxColors {
7
+ backgroundColor: string
8
+ borderColor: string
9
+ borderWidth: number
10
+ iconColor: string
11
+ hoveredBackgroundColor: string
12
+ pressedBackgroundColor: string
13
+ disabledBackgroundColor: string
14
+ disabledBorderColor: string
15
+ disabledBorderWidth: number
16
+ disabledIconColor: string
17
+ }
18
+
19
+ function getColors(theme: Theme, checked: boolean): BoxColors {
20
+ const disabledOnSurface38 = alphaColor(theme.colors.onSurface, 0.38)
21
+
22
+ if (checked) {
23
+ return {
24
+ backgroundColor: theme.colors.primary,
25
+ borderColor: 'transparent',
26
+ borderWidth: 0,
27
+ iconColor: theme.colors.onPrimary,
28
+ hoveredBackgroundColor: blendColor(
29
+ theme.colors.primary,
30
+ theme.colors.onPrimary,
31
+ theme.stateLayer.hoveredOpacity,
32
+ ),
33
+ pressedBackgroundColor: blendColor(
34
+ theme.colors.primary,
35
+ theme.colors.onPrimary,
36
+ theme.stateLayer.pressedOpacity,
37
+ ),
38
+ disabledBackgroundColor: disabledOnSurface38,
39
+ disabledBorderColor: 'transparent',
40
+ disabledBorderWidth: 0,
41
+ disabledIconColor: theme.colors.surface,
42
+ }
43
+ }
44
+
45
+ return {
46
+ backgroundColor: 'transparent',
47
+ borderColor: theme.colors.onSurfaceVariant,
48
+ borderWidth: 2,
49
+ iconColor: 'transparent',
50
+ hoveredBackgroundColor: alphaColor(
51
+ theme.colors.onSurface,
52
+ theme.stateLayer.hoveredOpacity,
53
+ ),
54
+ pressedBackgroundColor: alphaColor(
55
+ theme.colors.onSurface,
56
+ theme.stateLayer.pressedOpacity,
57
+ ),
58
+ disabledBackgroundColor: 'transparent',
59
+ disabledBorderColor: disabledOnSurface38,
60
+ disabledBorderWidth: 2,
61
+ disabledIconColor: 'transparent',
62
+ }
63
+ }
64
+
65
+ function applyColorOverrides(
66
+ theme: Theme,
67
+ colors: BoxColors,
68
+ containerColor?: string,
69
+ contentColor?: string,
70
+ ): BoxColors {
71
+ if (!containerColor && !contentColor) return colors
72
+
73
+ const result = { ...colors }
74
+
75
+ if (contentColor) {
76
+ result.iconColor = contentColor
77
+ }
78
+
79
+ if (containerColor) {
80
+ const overlay = contentColor ?? colors.iconColor
81
+ result.backgroundColor = containerColor
82
+ result.borderColor = containerColor
83
+ result.hoveredBackgroundColor = blendColor(
84
+ containerColor,
85
+ overlay,
86
+ theme.stateLayer.hoveredOpacity,
87
+ )
88
+ result.pressedBackgroundColor = blendColor(
89
+ containerColor,
90
+ overlay,
91
+ theme.stateLayer.pressedOpacity,
92
+ )
93
+ }
94
+
95
+ return result
96
+ }
97
+
98
+ export function createStyles(
99
+ theme: Theme,
100
+ checked: boolean,
101
+ containerColor?: string,
102
+ contentColor?: string,
103
+ ) {
104
+ const colors = applyColorOverrides(
105
+ theme,
106
+ getColors(theme, checked),
107
+ containerColor,
108
+ contentColor,
109
+ )
110
+
111
+ const size = 18
112
+ const touchTarget = 48
113
+
114
+ return StyleSheet.create({
115
+ container: {
116
+ width: touchTarget,
117
+ height: touchTarget,
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ cursor: 'pointer',
121
+ },
122
+ hoveredContainer: {
123
+ borderRadius: touchTarget / 2,
124
+ backgroundColor: colors.hoveredBackgroundColor,
125
+ },
126
+ pressedContainer: {
127
+ borderRadius: touchTarget / 2,
128
+ backgroundColor: colors.pressedBackgroundColor,
129
+ },
130
+ disabledContainer: {
131
+ cursor: 'auto',
132
+ },
133
+ box: {
134
+ width: size,
135
+ height: size,
136
+ borderRadius: theme.shape.cornerExtraSmall,
137
+ backgroundColor: colors.backgroundColor,
138
+ borderColor: colors.borderColor,
139
+ borderWidth: colors.borderWidth,
140
+ alignItems: 'center' as const,
141
+ justifyContent: 'center' as const,
142
+ },
143
+ disabledBox: {
144
+ backgroundColor: colors.disabledBackgroundColor,
145
+ borderColor: colors.disabledBorderColor,
146
+ borderWidth: colors.disabledBorderWidth,
147
+ },
148
+ iconColor: {
149
+ color: colors.iconColor,
150
+ },
151
+ disabledIconColor: {
152
+ color: colors.disabledIconColor,
153
+ },
154
+ })
155
+ }
@@ -0,0 +1,20 @@
1
+ import type { PressableProps } from 'react-native'
2
+
3
+ export interface CheckboxProps extends Omit<PressableProps, 'children'> {
4
+ /**
5
+ * Whether the checkbox is checked.
6
+ * @default false
7
+ */
8
+ value?: boolean
9
+ /** Callback fired when the checkbox is toggled. Receives the new value. */
10
+ onValueChange?: (value: boolean) => void
11
+ /**
12
+ * Override the container (box) color when checked.
13
+ * State-layer colors (hover, press) are derived automatically.
14
+ */
15
+ containerColor?: string
16
+ /**
17
+ * Override the checkmark icon color.
18
+ */
19
+ contentColor?: string
20
+ }
@@ -0,0 +1,182 @@
1
+ import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
2
+ import { useMemo } from 'react'
3
+ import { Platform, Pressable, StyleSheet, Text, View } from 'react-native'
4
+ import type { StyleProp, ViewStyle } from 'react-native'
5
+ import { useTheme } from '@onlynative/core'
6
+
7
+ import { createStyles } from './styles'
8
+ import type { ChipProps } from './types'
9
+
10
+ interface PressableState {
11
+ pressed: boolean
12
+ hovered?: boolean
13
+ }
14
+
15
+ function resolveStyle(
16
+ containerStyle: StyleProp<ViewStyle>,
17
+ hoveredContainerStyle: StyleProp<ViewStyle>,
18
+ pressedContainerStyle: StyleProp<ViewStyle>,
19
+ disabledContainerStyle: StyleProp<ViewStyle>,
20
+ disabled: boolean,
21
+ style: ChipProps['style'],
22
+ ): (state: PressableState) => StyleProp<ViewStyle> {
23
+ if (typeof style === 'function') {
24
+ return (state) => [
25
+ containerStyle,
26
+ state.hovered && !state.pressed && !disabled
27
+ ? hoveredContainerStyle
28
+ : undefined,
29
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
30
+ disabled ? disabledContainerStyle : undefined,
31
+ style(state),
32
+ ]
33
+ }
34
+
35
+ return (state) => [
36
+ containerStyle,
37
+ state.hovered && !state.pressed && !disabled
38
+ ? hoveredContainerStyle
39
+ : undefined,
40
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
41
+ disabled ? disabledContainerStyle : undefined,
42
+ style,
43
+ ]
44
+ }
45
+
46
+ export function Chip({
47
+ children,
48
+ style,
49
+ variant = 'assist',
50
+ elevated = false,
51
+ selected = false,
52
+ leadingIcon,
53
+ iconSize = 18,
54
+ avatar,
55
+ onClose,
56
+ containerColor,
57
+ contentColor,
58
+ labelStyle: labelStyleOverride,
59
+ disabled = false,
60
+ ...props
61
+ }: ChipProps) {
62
+ const isDisabled = Boolean(disabled)
63
+ const isSelected = variant === 'filter' ? Boolean(selected) : false
64
+
65
+ const showCloseIcon =
66
+ onClose !== undefined &&
67
+ (variant === 'input' || (variant === 'filter' && isSelected))
68
+
69
+ const hasLeadingContent = Boolean(
70
+ (variant === 'input' && avatar) ||
71
+ leadingIcon ||
72
+ (variant === 'filter' && isSelected),
73
+ )
74
+
75
+ const theme = useTheme()
76
+ const styles = useMemo(
77
+ () =>
78
+ createStyles(
79
+ theme,
80
+ variant,
81
+ elevated,
82
+ isSelected,
83
+ hasLeadingContent,
84
+ showCloseIcon,
85
+ containerColor,
86
+ contentColor,
87
+ ),
88
+ [
89
+ theme,
90
+ variant,
91
+ elevated,
92
+ isSelected,
93
+ hasLeadingContent,
94
+ showCloseIcon,
95
+ containerColor,
96
+ contentColor,
97
+ ],
98
+ )
99
+
100
+ const resolvedIconColor = useMemo(() => {
101
+ const base = StyleSheet.flatten([
102
+ styles.label,
103
+ isDisabled ? styles.disabledLabel : undefined,
104
+ ])
105
+ return typeof base?.color === 'string' ? base.color : undefined
106
+ }, [styles.label, styles.disabledLabel, isDisabled])
107
+
108
+ const computedLabelStyle = useMemo(
109
+ () => [
110
+ styles.label,
111
+ isDisabled ? styles.disabledLabel : undefined,
112
+ labelStyleOverride,
113
+ ],
114
+ [isDisabled, styles.disabledLabel, styles.label, labelStyleOverride],
115
+ )
116
+
117
+ const renderLeadingContent = () => {
118
+ if (variant === 'input' && avatar) {
119
+ return <View style={styles.avatar}>{avatar}</View>
120
+ }
121
+ if (leadingIcon) {
122
+ return (
123
+ <MaterialCommunityIcons
124
+ name={leadingIcon}
125
+ size={iconSize}
126
+ color={resolvedIconColor}
127
+ style={styles.leadingIcon}
128
+ />
129
+ )
130
+ }
131
+ if (variant === 'filter' && isSelected) {
132
+ return (
133
+ <MaterialCommunityIcons
134
+ name="check"
135
+ size={iconSize}
136
+ color={resolvedIconColor}
137
+ style={styles.leadingIcon}
138
+ />
139
+ )
140
+ }
141
+ return null
142
+ }
143
+
144
+ return (
145
+ <Pressable
146
+ {...props}
147
+ accessibilityRole="button"
148
+ accessibilityState={{
149
+ disabled: isDisabled,
150
+ ...(variant === 'filter' ? { selected: isSelected } : undefined),
151
+ }}
152
+ hitSlop={Platform.OS === 'web' ? undefined : 4}
153
+ disabled={isDisabled}
154
+ style={resolveStyle(
155
+ styles.container,
156
+ styles.hoveredContainer,
157
+ styles.pressedContainer,
158
+ styles.disabledContainer,
159
+ isDisabled,
160
+ style,
161
+ )}
162
+ >
163
+ {renderLeadingContent()}
164
+ <Text style={computedLabelStyle}>{children}</Text>
165
+ {showCloseIcon ? (
166
+ <Pressable
167
+ onPress={onClose}
168
+ accessibilityRole="button"
169
+ accessibilityLabel="Remove"
170
+ hitSlop={4}
171
+ style={styles.closeButton}
172
+ >
173
+ <MaterialCommunityIcons
174
+ name="close"
175
+ size={iconSize}
176
+ color={resolvedIconColor}
177
+ />
178
+ </Pressable>
179
+ ) : null}
180
+ </Pressable>
181
+ )
182
+ }
@@ -0,0 +1,2 @@
1
+ export { Chip } from './Chip'
2
+ export type { ChipProps, ChipVariant } from './types'