@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,82 @@
1
+ import type { FlexAlignType, ViewProps } from 'react-native'
2
+ import type { Theme } from '@onlynative/core'
3
+
4
+ /** A theme spacing token name or a raw numeric value in dp. */
5
+ export type SpacingValue = keyof Theme['spacing'] | number
6
+
7
+ export interface BoxProps extends ViewProps {
8
+ /** Padding on all sides */
9
+ p?: SpacingValue
10
+ /** Horizontal padding (paddingStart + paddingEnd) */
11
+ px?: SpacingValue
12
+ /** Vertical padding (paddingTop + paddingBottom) */
13
+ py?: SpacingValue
14
+ /** Padding top */
15
+ pt?: SpacingValue
16
+ /** Padding bottom */
17
+ pb?: SpacingValue
18
+ /** Padding start (left in LTR, right in RTL) */
19
+ ps?: SpacingValue
20
+ /** Padding end (right in LTR, left in RTL) */
21
+ pe?: SpacingValue
22
+ /** Margin on all sides */
23
+ m?: SpacingValue
24
+ /** Horizontal margin (marginStart + marginEnd) */
25
+ mx?: SpacingValue
26
+ /** Vertical margin (marginTop + marginBottom) */
27
+ my?: SpacingValue
28
+ /** Margin top */
29
+ mt?: SpacingValue
30
+ /** Margin bottom */
31
+ mb?: SpacingValue
32
+ /** Margin start */
33
+ ms?: SpacingValue
34
+ /** Margin end */
35
+ me?: SpacingValue
36
+ /** Gap between children */
37
+ gap?: SpacingValue
38
+ /** Row gap between children */
39
+ rowGap?: SpacingValue
40
+ /** Column gap between children */
41
+ columnGap?: SpacingValue
42
+ /** Flex value */
43
+ flex?: number
44
+ /** Align items along the cross axis */
45
+ align?: FlexAlignType
46
+ /** Justify content along the main axis */
47
+ justify?:
48
+ | 'flex-start'
49
+ | 'flex-end'
50
+ | 'center'
51
+ | 'space-between'
52
+ | 'space-around'
53
+ | 'space-evenly'
54
+ /** Background color */
55
+ bg?: string
56
+ }
57
+
58
+ export interface RowProps extends BoxProps {
59
+ /**
60
+ * Whether children should wrap to the next line when they overflow.
61
+ * @default false
62
+ */
63
+ wrap?: boolean
64
+ /**
65
+ * Reverses the layout direction (`row-reverse`).
66
+ * @default false
67
+ */
68
+ inverted?: boolean
69
+ }
70
+
71
+ export interface ColumnProps extends BoxProps {
72
+ /**
73
+ * Reverses the layout direction (`column-reverse`).
74
+ * @default false
75
+ */
76
+ inverted?: boolean
77
+ }
78
+
79
+ export interface GridProps extends RowProps {
80
+ /** Number of equal-width columns. */
81
+ columns: number
82
+ }
@@ -0,0 +1,17 @@
1
+ import { useMemo } from 'react'
2
+ import { View } from 'react-native'
3
+ import { useTheme } from '@onlynative/core'
4
+
5
+ import { createListStyles } from './styles'
6
+ import type { ListProps } from './types'
7
+
8
+ export function List({ children, style, ...props }: ListProps) {
9
+ const theme = useTheme()
10
+ const styles = useMemo(() => createListStyles(theme), [theme])
11
+
12
+ return (
13
+ <View {...props} style={[styles.container, style]}>
14
+ {children}
15
+ </View>
16
+ )
17
+ }
@@ -0,0 +1,20 @@
1
+ import { useMemo } from 'react'
2
+ import { View } from 'react-native'
3
+ import { useTheme } from '@onlynative/core'
4
+
5
+ import { createDividerStyles } from './styles'
6
+ import type { ListDividerProps } from './types'
7
+
8
+ export function ListDivider({
9
+ inset = false,
10
+ style,
11
+ ...props
12
+ }: ListDividerProps) {
13
+ const theme = useTheme()
14
+ const styles = useMemo(
15
+ () => createDividerStyles(theme, inset),
16
+ [theme, inset],
17
+ )
18
+
19
+ return <View {...props} style={[styles.divider, style]} />
20
+ }
@@ -0,0 +1,128 @@
1
+ import { useMemo } from 'react'
2
+ import { Platform, Pressable, Text, View } from 'react-native'
3
+ import type { StyleProp, ViewStyle } from 'react-native'
4
+ import { useTheme } from '@onlynative/core'
5
+
6
+ import { createListItemStyles } from './styles'
7
+ import type { ListItemLines, ListItemProps } from './types'
8
+
9
+ interface PressableState {
10
+ pressed: boolean
11
+ hovered?: boolean
12
+ }
13
+
14
+ function getLines(
15
+ supportingText?: string,
16
+ overlineText?: string,
17
+ supportingTextNumberOfLines?: number,
18
+ ): ListItemLines {
19
+ if (
20
+ (supportingText && overlineText) ||
21
+ (supportingText && supportingTextNumberOfLines && supportingTextNumberOfLines > 1)
22
+ ) {
23
+ return 3
24
+ }
25
+ if (supportingText || overlineText) return 2
26
+ return 1
27
+ }
28
+
29
+ export function ListItem({
30
+ headlineText,
31
+ supportingText,
32
+ overlineText,
33
+ trailingSupportingText,
34
+ leadingContent,
35
+ trailingContent,
36
+ onPress,
37
+ disabled = false,
38
+ containerColor,
39
+ supportingTextNumberOfLines = 1,
40
+ style,
41
+ ...props
42
+ }: ListItemProps) {
43
+ const isDisabled = Boolean(disabled)
44
+ const isInteractive = onPress !== undefined
45
+ const theme = useTheme()
46
+ const lines = getLines(supportingText, overlineText, supportingTextNumberOfLines)
47
+ const styles = useMemo(
48
+ () => createListItemStyles(theme, lines, containerColor),
49
+ [theme, lines, containerColor],
50
+ )
51
+
52
+ const content = (
53
+ <>
54
+ {leadingContent != null && (
55
+ <View style={styles.leadingContent}>{leadingContent}</View>
56
+ )}
57
+ <View style={styles.textBlock}>
58
+ {overlineText != null && (
59
+ <Text style={styles.overlineText} numberOfLines={1}>
60
+ {overlineText}
61
+ </Text>
62
+ )}
63
+ <Text style={styles.headlineText} numberOfLines={1}>
64
+ {headlineText}
65
+ </Text>
66
+ {supportingText != null && (
67
+ <Text
68
+ style={styles.supportingText}
69
+ numberOfLines={supportingTextNumberOfLines}
70
+ >
71
+ {supportingText}
72
+ </Text>
73
+ )}
74
+ </View>
75
+ {(trailingContent != null || trailingSupportingText != null) && (
76
+ <View style={styles.trailingBlock}>
77
+ {trailingSupportingText != null && (
78
+ <Text style={styles.trailingSupportingText} numberOfLines={1}>
79
+ {trailingSupportingText}
80
+ </Text>
81
+ )}
82
+ {trailingContent}
83
+ </View>
84
+ )}
85
+ </>
86
+ )
87
+
88
+ if (!isInteractive) {
89
+ return (
90
+ <View {...props} style={[styles.container, style]}>
91
+ {content}
92
+ </View>
93
+ )
94
+ }
95
+
96
+ const resolvedStyle = (state: PressableState): StyleProp<ViewStyle> => [
97
+ styles.container,
98
+ styles.interactiveContainer,
99
+ state.hovered && !state.pressed && !isDisabled
100
+ ? styles.hoveredContainer
101
+ : undefined,
102
+ state.pressed && !isDisabled ? styles.pressedContainer : undefined,
103
+ isDisabled ? styles.disabledContainer : undefined,
104
+ typeof style === 'function'
105
+ ? (style as (state: PressableState) => ViewStyle)(state)
106
+ : style,
107
+ ]
108
+
109
+ return (
110
+ <Pressable
111
+ {...props}
112
+ role="button"
113
+ accessibilityState={{ disabled: isDisabled }}
114
+ hitSlop={Platform.OS === 'web' ? undefined : 4}
115
+ disabled={isDisabled}
116
+ onPress={onPress}
117
+ style={resolvedStyle}
118
+ >
119
+ {isDisabled ? (
120
+ <View style={styles.disabledContentWrapper}>
121
+ {content}
122
+ </View>
123
+ ) : (
124
+ content
125
+ )}
126
+ </Pressable>
127
+ )
128
+ }
@@ -0,0 +1,9 @@
1
+ export { List } from './List'
2
+ export { ListItem } from './ListItem'
3
+ export { ListDivider } from './ListDivider'
4
+ export type {
5
+ ListProps,
6
+ ListItemLines,
7
+ ListItemProps,
8
+ ListDividerProps,
9
+ } from './types'
@@ -0,0 +1,132 @@
1
+ import { StyleSheet } from 'react-native'
2
+ import type { Theme } from '@onlynative/core'
3
+
4
+ import type { ListItemLines } from './types'
5
+ import { alphaColor, blendColor } from '../utils/color'
6
+
7
+ const ITEM_PADDING_VERTICAL = 12
8
+ const INSET_START = 56
9
+
10
+ const MIN_HEIGHT: Record<ListItemLines, number> = {
11
+ 1: 56,
12
+ 2: 72,
13
+ 3: 88,
14
+ }
15
+
16
+ export function createListStyles(theme: Theme) {
17
+ return StyleSheet.create({
18
+ container: {
19
+ paddingVertical: theme.spacing.sm,
20
+ },
21
+ })
22
+ }
23
+
24
+ interface ItemColors {
25
+ backgroundColor: string
26
+ hoveredBackgroundColor: string
27
+ pressedBackgroundColor: string
28
+ }
29
+
30
+ function getItemColors(theme: Theme, containerColor?: string): ItemColors {
31
+ const base = containerColor ?? 'transparent'
32
+
33
+ if (containerColor) {
34
+ return {
35
+ backgroundColor: containerColor,
36
+ hoveredBackgroundColor: blendColor(
37
+ containerColor,
38
+ theme.colors.onSurface,
39
+ theme.stateLayer.hoveredOpacity,
40
+ ),
41
+ pressedBackgroundColor: blendColor(
42
+ containerColor,
43
+ theme.colors.onSurface,
44
+ theme.stateLayer.pressedOpacity,
45
+ ),
46
+ }
47
+ }
48
+
49
+ return {
50
+ backgroundColor: base,
51
+ hoveredBackgroundColor: alphaColor(
52
+ theme.colors.onSurface,
53
+ theme.stateLayer.hoveredOpacity,
54
+ ),
55
+ pressedBackgroundColor: alphaColor(
56
+ theme.colors.onSurface,
57
+ theme.stateLayer.pressedOpacity,
58
+ ),
59
+ }
60
+ }
61
+
62
+ export function createListItemStyles(
63
+ theme: Theme,
64
+ lines: ListItemLines,
65
+ containerColor?: string,
66
+ ) {
67
+ const colors = getItemColors(theme, containerColor)
68
+
69
+ return StyleSheet.create({
70
+ container: {
71
+ flexDirection: 'row',
72
+ alignItems: lines === 3 ? 'flex-start' : 'center',
73
+ minHeight: MIN_HEIGHT[lines],
74
+ paddingHorizontal: theme.spacing.md,
75
+ paddingVertical: ITEM_PADDING_VERTICAL,
76
+ backgroundColor: colors.backgroundColor,
77
+ },
78
+ interactiveContainer: {
79
+ cursor: 'pointer',
80
+ },
81
+ hoveredContainer: {
82
+ backgroundColor: colors.hoveredBackgroundColor,
83
+ },
84
+ pressedContainer: {
85
+ backgroundColor: colors.pressedBackgroundColor,
86
+ },
87
+ disabledContainer: {
88
+ cursor: 'auto',
89
+ },
90
+ disabledContentWrapper: {
91
+ flexDirection: 'row',
92
+ flex: 1,
93
+ opacity: theme.stateLayer.disabledOpacity,
94
+ },
95
+ leadingContent: {
96
+ marginEnd: theme.spacing.md,
97
+ },
98
+ textBlock: {
99
+ flex: 1,
100
+ },
101
+ overlineText: {
102
+ ...theme.typography.labelSmall,
103
+ color: theme.colors.onSurfaceVariant,
104
+ },
105
+ headlineText: {
106
+ ...theme.typography.bodyLarge,
107
+ color: theme.colors.onSurface,
108
+ },
109
+ supportingText: {
110
+ ...theme.typography.bodyMedium,
111
+ color: theme.colors.onSurfaceVariant,
112
+ },
113
+ trailingBlock: {
114
+ marginStart: theme.spacing.md,
115
+ alignItems: 'flex-end',
116
+ },
117
+ trailingSupportingText: {
118
+ ...theme.typography.labelSmall,
119
+ color: theme.colors.onSurfaceVariant,
120
+ },
121
+ })
122
+ }
123
+
124
+ export function createDividerStyles(theme: Theme, inset: boolean) {
125
+ return StyleSheet.create({
126
+ divider: {
127
+ height: 1,
128
+ backgroundColor: theme.colors.outlineVariant,
129
+ ...(inset ? { marginStart: INSET_START } : undefined),
130
+ },
131
+ })
132
+ }
@@ -0,0 +1,54 @@
1
+ import type { ReactNode } from 'react'
2
+ import type { StyleProp, ViewProps, ViewStyle } from 'react-native'
3
+
4
+ export interface ListProps extends ViewProps {
5
+ /** Content rendered inside the list container. */
6
+ children: ReactNode
7
+ style?: StyleProp<ViewStyle>
8
+ }
9
+
10
+ /** Number of text lines the item displays, used to determine minimum height. */
11
+ export type ListItemLines = 1 | 2 | 3
12
+
13
+ export interface ListItemProps extends ViewProps {
14
+ /** Primary text displayed on the list item. */
15
+ headlineText: string
16
+ /** Secondary text displayed below the headline. */
17
+ supportingText?: string
18
+ /** Text displayed above the headline (e.g. category label). */
19
+ overlineText?: string
20
+ /** Short text displayed at the trailing edge (e.g. "100+", timestamp). */
21
+ trailingSupportingText?: string
22
+ /** Content rendered before the text block (icon, avatar, image, checkbox). */
23
+ leadingContent?: ReactNode
24
+ /** Content rendered after the text block (icon, switch, checkbox). */
25
+ trailingContent?: ReactNode
26
+ /** When provided, the item becomes interactive (Pressable). Omit to render as a plain View. */
27
+ onPress?: () => void
28
+ /**
29
+ * Disables the press interaction and reduces opacity. Only effective when `onPress` is provided.
30
+ * @default false
31
+ */
32
+ disabled?: boolean
33
+ /**
34
+ * Override the container (background) color.
35
+ * State-layer colors (hover, press) are derived automatically.
36
+ */
37
+ containerColor?: string
38
+ /**
39
+ * Maximum number of lines for supportingText before truncating.
40
+ * @default 1
41
+ */
42
+ supportingTextNumberOfLines?: number
43
+ style?: StyleProp<ViewStyle>
44
+ }
45
+
46
+ export interface ListDividerProps extends ViewProps {
47
+ /**
48
+ * When true, adds a leading inset so the divider aligns with text
49
+ * that follows a leading icon (56dp from the start edge).
50
+ * @default false
51
+ */
52
+ inset?: boolean
53
+ style?: StyleProp<ViewStyle>
54
+ }
@@ -0,0 +1,103 @@
1
+ import { useMemo } from 'react'
2
+ import { Platform, Pressable, View } from 'react-native'
3
+ import type { StyleProp, ViewStyle } from 'react-native'
4
+ import { useTheme } from '@onlynative/core'
5
+
6
+ import { createStyles } from './styles'
7
+ import type { RadioProps } from './types'
8
+
9
+ interface PressableState {
10
+ pressed: boolean
11
+ hovered?: boolean
12
+ }
13
+
14
+ function resolveStyle(
15
+ containerStyle: StyleProp<ViewStyle>,
16
+ hoveredContainerStyle: StyleProp<ViewStyle>,
17
+ pressedContainerStyle: StyleProp<ViewStyle>,
18
+ disabledContainerStyle: StyleProp<ViewStyle>,
19
+ disabled: boolean,
20
+ style: RadioProps['style'],
21
+ ): (state: PressableState) => StyleProp<ViewStyle> {
22
+ if (typeof style === 'function') {
23
+ return (state) => [
24
+ containerStyle,
25
+ state.hovered && !state.pressed && !disabled
26
+ ? hoveredContainerStyle
27
+ : undefined,
28
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
29
+ disabled ? disabledContainerStyle : undefined,
30
+ style(state),
31
+ ]
32
+ }
33
+
34
+ return (state) => [
35
+ containerStyle,
36
+ state.hovered && !state.pressed && !disabled
37
+ ? hoveredContainerStyle
38
+ : undefined,
39
+ state.pressed && !disabled ? pressedContainerStyle : undefined,
40
+ disabled ? disabledContainerStyle : undefined,
41
+ style,
42
+ ]
43
+ }
44
+
45
+ export function Radio({
46
+ style,
47
+ value = false,
48
+ onValueChange,
49
+ containerColor,
50
+ contentColor,
51
+ disabled = false,
52
+ ...props
53
+ }: RadioProps) {
54
+ const isDisabled = Boolean(disabled)
55
+ const isSelected = Boolean(value)
56
+
57
+ const theme = useTheme()
58
+ const styles = useMemo(
59
+ () => createStyles(theme, isSelected, containerColor, contentColor),
60
+ [theme, isSelected, containerColor, contentColor],
61
+ )
62
+
63
+ const handlePress = () => {
64
+ if (!isDisabled) {
65
+ onValueChange?.(!isSelected)
66
+ }
67
+ }
68
+
69
+ return (
70
+ <Pressable
71
+ {...props}
72
+ accessibilityRole="radio"
73
+ accessibilityState={{
74
+ disabled: isDisabled,
75
+ checked: isSelected,
76
+ }}
77
+ hitSlop={Platform.OS === 'web' ? undefined : 4}
78
+ disabled={isDisabled}
79
+ onPress={handlePress}
80
+ style={resolveStyle(
81
+ styles.container,
82
+ styles.hoveredContainer,
83
+ styles.pressedContainer,
84
+ styles.disabledContainer,
85
+ isDisabled,
86
+ style,
87
+ )}
88
+ >
89
+ <View
90
+ style={[styles.outer, isDisabled ? styles.disabledOuter : undefined]}
91
+ >
92
+ {isSelected ? (
93
+ <View
94
+ style={[
95
+ styles.inner,
96
+ isDisabled ? styles.disabledInner : undefined,
97
+ ]}
98
+ />
99
+ ) : null}
100
+ </View>
101
+ </Pressable>
102
+ )
103
+ }
@@ -0,0 +1,2 @@
1
+ export { Radio } from './Radio'
2
+ export type { RadioProps } from './types'
@@ -0,0 +1,139 @@
1
+ import { StyleSheet } from 'react-native'
2
+ import type { Theme } from '@onlynative/core'
3
+
4
+ import { alphaColor, blendColor } from '../utils/color'
5
+
6
+ interface RadioColors {
7
+ borderColor: string
8
+ dotColor: string
9
+ hoveredBackgroundColor: string
10
+ pressedBackgroundColor: string
11
+ disabledBorderColor: string
12
+ disabledDotColor: string
13
+ }
14
+
15
+ function getColors(theme: Theme, selected: boolean): RadioColors {
16
+ const disabledOnSurface38 = alphaColor(theme.colors.onSurface, 0.38)
17
+
18
+ if (selected) {
19
+ return {
20
+ borderColor: theme.colors.primary,
21
+ dotColor: theme.colors.primary,
22
+ hoveredBackgroundColor: alphaColor(
23
+ theme.colors.primary,
24
+ theme.stateLayer.hoveredOpacity,
25
+ ),
26
+ pressedBackgroundColor: alphaColor(
27
+ theme.colors.primary,
28
+ theme.stateLayer.pressedOpacity,
29
+ ),
30
+ disabledBorderColor: disabledOnSurface38,
31
+ disabledDotColor: disabledOnSurface38,
32
+ }
33
+ }
34
+
35
+ return {
36
+ borderColor: theme.colors.onSurfaceVariant,
37
+ dotColor: 'transparent',
38
+ hoveredBackgroundColor: alphaColor(
39
+ theme.colors.onSurface,
40
+ theme.stateLayer.hoveredOpacity,
41
+ ),
42
+ pressedBackgroundColor: alphaColor(
43
+ theme.colors.onSurface,
44
+ theme.stateLayer.pressedOpacity,
45
+ ),
46
+ disabledBorderColor: disabledOnSurface38,
47
+ disabledDotColor: 'transparent',
48
+ }
49
+ }
50
+
51
+ function applyColorOverrides(
52
+ theme: Theme,
53
+ colors: RadioColors,
54
+ containerColor?: string,
55
+ contentColor?: string,
56
+ ): RadioColors {
57
+ if (!containerColor && !contentColor) return colors
58
+
59
+ const result = { ...colors }
60
+
61
+ if (containerColor) {
62
+ result.borderColor = containerColor
63
+ result.dotColor = containerColor
64
+ result.hoveredBackgroundColor = alphaColor(
65
+ containerColor,
66
+ theme.stateLayer.hoveredOpacity,
67
+ )
68
+ result.pressedBackgroundColor = alphaColor(
69
+ containerColor,
70
+ theme.stateLayer.pressedOpacity,
71
+ )
72
+ }
73
+
74
+ if (contentColor) {
75
+ result.borderColor = contentColor
76
+ }
77
+
78
+ return result
79
+ }
80
+
81
+ export function createStyles(
82
+ theme: Theme,
83
+ selected: boolean,
84
+ containerColor?: string,
85
+ contentColor?: string,
86
+ ) {
87
+ const colors = applyColorOverrides(
88
+ theme,
89
+ getColors(theme, selected),
90
+ containerColor,
91
+ contentColor,
92
+ )
93
+
94
+ const outerSize = 20
95
+ const innerSize = 10
96
+ const touchTarget = 48
97
+
98
+ return StyleSheet.create({
99
+ container: {
100
+ width: touchTarget,
101
+ height: touchTarget,
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ cursor: 'pointer',
105
+ },
106
+ hoveredContainer: {
107
+ borderRadius: touchTarget / 2,
108
+ backgroundColor: colors.hoveredBackgroundColor,
109
+ },
110
+ pressedContainer: {
111
+ borderRadius: touchTarget / 2,
112
+ backgroundColor: colors.pressedBackgroundColor,
113
+ },
114
+ disabledContainer: {
115
+ cursor: 'auto',
116
+ },
117
+ outer: {
118
+ width: outerSize,
119
+ height: outerSize,
120
+ borderRadius: outerSize / 2,
121
+ borderWidth: 2,
122
+ borderColor: colors.borderColor,
123
+ alignItems: 'center' as const,
124
+ justifyContent: 'center' as const,
125
+ },
126
+ disabledOuter: {
127
+ borderColor: colors.disabledBorderColor,
128
+ },
129
+ inner: {
130
+ width: innerSize,
131
+ height: innerSize,
132
+ borderRadius: innerSize / 2,
133
+ backgroundColor: colors.dotColor,
134
+ },
135
+ disabledInner: {
136
+ backgroundColor: colors.disabledDotColor,
137
+ },
138
+ })
139
+ }