@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.
- package/README.md +99 -0
- package/dist/appbar/index.d.ts +71 -0
- package/dist/appbar/index.js +952 -0
- package/dist/button/index.d.ts +41 -0
- package/dist/button/index.js +454 -0
- package/dist/card/index.d.ts +31 -0
- package/dist/card/index.js +264 -0
- package/dist/checkbox/index.d.ts +25 -0
- package/dist/checkbox/index.js +291 -0
- package/dist/chip/index.d.ts +62 -0
- package/dist/chip/index.js +452 -0
- package/dist/icon-button/index.d.ts +10 -0
- package/dist/icon-button/index.js +575 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +3374 -0
- package/dist/layout/index.d.ts +98 -0
- package/dist/layout/index.js +282 -0
- package/dist/list/index.d.ts +60 -0
- package/dist/list/index.js +300 -0
- package/dist/radio/index.d.ts +25 -0
- package/dist/radio/index.js +250 -0
- package/dist/switch/index.d.ts +37 -0
- package/dist/switch/index.js +315 -0
- package/dist/text-field/index.d.ts +52 -0
- package/dist/text-field/index.js +496 -0
- package/dist/types-D3hlyvz-.d.ts +51 -0
- package/dist/typography/index.d.ts +28 -0
- package/dist/typography/index.js +69 -0
- package/package.json +166 -0
- package/src/appbar/AppBar.tsx +302 -0
- package/src/appbar/index.ts +2 -0
- package/src/appbar/styles.ts +92 -0
- package/src/appbar/types.ts +67 -0
- package/src/button/Button.tsx +130 -0
- package/src/button/index.ts +2 -0
- package/src/button/styles.ts +288 -0
- package/src/button/types.ts +42 -0
- package/src/card/Card.tsx +69 -0
- package/src/card/index.ts +2 -0
- package/src/card/styles.ts +151 -0
- package/src/card/types.ts +27 -0
- package/src/checkbox/Checkbox.tsx +109 -0
- package/src/checkbox/index.ts +2 -0
- package/src/checkbox/styles.ts +155 -0
- package/src/checkbox/types.ts +20 -0
- package/src/chip/Chip.tsx +182 -0
- package/src/chip/index.ts +2 -0
- package/src/chip/styles.ts +240 -0
- package/src/chip/types.ts +58 -0
- package/src/icon-button/IconButton.tsx +358 -0
- package/src/icon-button/index.ts +6 -0
- package/src/icon-button/styles.ts +259 -0
- package/src/icon-button/types.ts +55 -0
- package/src/index.ts +51 -0
- package/src/layout/Box.tsx +99 -0
- package/src/layout/Column.tsx +16 -0
- package/src/layout/Grid.tsx +49 -0
- package/src/layout/Layout.tsx +81 -0
- package/src/layout/Row.tsx +22 -0
- package/src/layout/index.ts +13 -0
- package/src/layout/resolveSpacing.ts +11 -0
- package/src/layout/types.ts +82 -0
- package/src/list/List.tsx +17 -0
- package/src/list/ListDivider.tsx +20 -0
- package/src/list/ListItem.tsx +128 -0
- package/src/list/index.ts +9 -0
- package/src/list/styles.ts +132 -0
- package/src/list/types.ts +54 -0
- package/src/radio/Radio.tsx +103 -0
- package/src/radio/index.ts +2 -0
- package/src/radio/styles.ts +139 -0
- package/src/radio/types.ts +20 -0
- package/src/switch/Switch.tsx +118 -0
- package/src/switch/index.ts +2 -0
- package/src/switch/styles.ts +172 -0
- package/src/switch/types.ts +32 -0
- package/src/test-utils/render-with-theme.tsx +13 -0
- package/src/text-field/TextField.tsx +298 -0
- package/src/text-field/index.ts +2 -0
- package/src/text-field/styles.ts +240 -0
- package/src/text-field/types.ts +49 -0
- package/src/typography/Typography.tsx +65 -0
- package/src/typography/index.ts +3 -0
- package/src/typography/types.ts +17 -0
- package/src/utils/color.ts +64 -0
- package/src/utils/elevation.ts +33 -0
- package/src/utils/rtl.ts +19 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native'
|
|
2
|
+
import type { Theme } from '@onlynative/core'
|
|
3
|
+
|
|
4
|
+
import type { ChipVariant } from './types'
|
|
5
|
+
import { alphaColor, blendColor } from '../utils/color'
|
|
6
|
+
import { elevationStyle } from '../utils/elevation'
|
|
7
|
+
|
|
8
|
+
interface VariantColors {
|
|
9
|
+
backgroundColor: string
|
|
10
|
+
textColor: string
|
|
11
|
+
borderColor: string
|
|
12
|
+
borderWidth: number
|
|
13
|
+
hoveredBackgroundColor: string
|
|
14
|
+
pressedBackgroundColor: string
|
|
15
|
+
disabledBackgroundColor: string
|
|
16
|
+
disabledTextColor: string
|
|
17
|
+
disabledBorderColor: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getVariantColors(
|
|
21
|
+
theme: Theme,
|
|
22
|
+
variant: ChipVariant,
|
|
23
|
+
elevated: boolean,
|
|
24
|
+
selected: boolean,
|
|
25
|
+
): VariantColors {
|
|
26
|
+
const disabledContainerColor = alphaColor(theme.colors.onSurface, 0.12)
|
|
27
|
+
const disabledLabelColor = alphaColor(theme.colors.onSurface, 0.38)
|
|
28
|
+
const disabledOutlineColor = alphaColor(theme.colors.onSurface, 0.12)
|
|
29
|
+
|
|
30
|
+
// Filter chip — selected state
|
|
31
|
+
if (variant === 'filter' && selected) {
|
|
32
|
+
return {
|
|
33
|
+
backgroundColor: theme.colors.secondaryContainer,
|
|
34
|
+
textColor: theme.colors.onSecondaryContainer,
|
|
35
|
+
borderColor: 'transparent',
|
|
36
|
+
borderWidth: 0,
|
|
37
|
+
hoveredBackgroundColor: blendColor(
|
|
38
|
+
theme.colors.secondaryContainer,
|
|
39
|
+
theme.colors.onSecondaryContainer,
|
|
40
|
+
theme.stateLayer.hoveredOpacity,
|
|
41
|
+
),
|
|
42
|
+
pressedBackgroundColor: blendColor(
|
|
43
|
+
theme.colors.secondaryContainer,
|
|
44
|
+
theme.colors.onSecondaryContainer,
|
|
45
|
+
theme.stateLayer.pressedOpacity,
|
|
46
|
+
),
|
|
47
|
+
disabledBackgroundColor: disabledContainerColor,
|
|
48
|
+
disabledTextColor: disabledLabelColor,
|
|
49
|
+
disabledBorderColor: 'transparent',
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Elevated variants (assist, filter unselected, suggestion)
|
|
54
|
+
// Input variant ignores elevated — always outlined
|
|
55
|
+
if (elevated && variant !== 'input') {
|
|
56
|
+
const textColor =
|
|
57
|
+
variant === 'assist'
|
|
58
|
+
? theme.colors.onSurface
|
|
59
|
+
: theme.colors.onSurfaceVariant
|
|
60
|
+
return {
|
|
61
|
+
backgroundColor: theme.colors.surfaceContainerLow,
|
|
62
|
+
textColor,
|
|
63
|
+
borderColor: 'transparent',
|
|
64
|
+
borderWidth: 0,
|
|
65
|
+
hoveredBackgroundColor: blendColor(
|
|
66
|
+
theme.colors.surfaceContainerLow,
|
|
67
|
+
textColor,
|
|
68
|
+
theme.stateLayer.hoveredOpacity,
|
|
69
|
+
),
|
|
70
|
+
pressedBackgroundColor: blendColor(
|
|
71
|
+
theme.colors.surfaceContainerLow,
|
|
72
|
+
textColor,
|
|
73
|
+
theme.stateLayer.pressedOpacity,
|
|
74
|
+
),
|
|
75
|
+
disabledBackgroundColor: disabledContainerColor,
|
|
76
|
+
disabledTextColor: disabledLabelColor,
|
|
77
|
+
disabledBorderColor: 'transparent',
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Flat (outlined) variants
|
|
82
|
+
const textColor =
|
|
83
|
+
variant === 'assist'
|
|
84
|
+
? theme.colors.onSurface
|
|
85
|
+
: theme.colors.onSurfaceVariant
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
backgroundColor: theme.colors.surface,
|
|
89
|
+
textColor,
|
|
90
|
+
borderColor: theme.colors.outline,
|
|
91
|
+
borderWidth: 1,
|
|
92
|
+
hoveredBackgroundColor: blendColor(
|
|
93
|
+
theme.colors.surface,
|
|
94
|
+
textColor,
|
|
95
|
+
theme.stateLayer.hoveredOpacity,
|
|
96
|
+
),
|
|
97
|
+
pressedBackgroundColor: blendColor(
|
|
98
|
+
theme.colors.surface,
|
|
99
|
+
textColor,
|
|
100
|
+
theme.stateLayer.pressedOpacity,
|
|
101
|
+
),
|
|
102
|
+
disabledBackgroundColor: disabledContainerColor,
|
|
103
|
+
disabledTextColor: disabledLabelColor,
|
|
104
|
+
disabledBorderColor: disabledOutlineColor,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function applyColorOverrides(
|
|
109
|
+
theme: Theme,
|
|
110
|
+
colors: VariantColors,
|
|
111
|
+
containerColor?: string,
|
|
112
|
+
contentColor?: string,
|
|
113
|
+
): VariantColors {
|
|
114
|
+
if (!containerColor && !contentColor) return colors
|
|
115
|
+
|
|
116
|
+
const result = { ...colors }
|
|
117
|
+
|
|
118
|
+
if (contentColor) {
|
|
119
|
+
result.textColor = contentColor
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (containerColor) {
|
|
123
|
+
const overlay = contentColor ?? colors.textColor
|
|
124
|
+
result.backgroundColor = containerColor
|
|
125
|
+
result.borderColor = containerColor
|
|
126
|
+
result.hoveredBackgroundColor = blendColor(
|
|
127
|
+
containerColor,
|
|
128
|
+
overlay,
|
|
129
|
+
theme.stateLayer.hoveredOpacity,
|
|
130
|
+
)
|
|
131
|
+
result.pressedBackgroundColor = blendColor(
|
|
132
|
+
containerColor,
|
|
133
|
+
overlay,
|
|
134
|
+
theme.stateLayer.pressedOpacity,
|
|
135
|
+
)
|
|
136
|
+
} else if (contentColor) {
|
|
137
|
+
if (colors.backgroundColor === 'transparent') {
|
|
138
|
+
result.hoveredBackgroundColor = alphaColor(
|
|
139
|
+
contentColor,
|
|
140
|
+
theme.stateLayer.hoveredOpacity,
|
|
141
|
+
)
|
|
142
|
+
result.pressedBackgroundColor = alphaColor(
|
|
143
|
+
contentColor,
|
|
144
|
+
theme.stateLayer.pressedOpacity,
|
|
145
|
+
)
|
|
146
|
+
} else {
|
|
147
|
+
result.hoveredBackgroundColor = blendColor(
|
|
148
|
+
colors.backgroundColor,
|
|
149
|
+
contentColor,
|
|
150
|
+
theme.stateLayer.hoveredOpacity,
|
|
151
|
+
)
|
|
152
|
+
result.pressedBackgroundColor = blendColor(
|
|
153
|
+
colors.backgroundColor,
|
|
154
|
+
contentColor,
|
|
155
|
+
theme.stateLayer.pressedOpacity,
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createStyles(
|
|
164
|
+
theme: Theme,
|
|
165
|
+
variant: ChipVariant,
|
|
166
|
+
elevated: boolean,
|
|
167
|
+
selected: boolean,
|
|
168
|
+
hasLeadingContent: boolean,
|
|
169
|
+
hasTrailingContent: boolean,
|
|
170
|
+
containerColor?: string,
|
|
171
|
+
contentColor?: string,
|
|
172
|
+
) {
|
|
173
|
+
const baseColors = getVariantColors(theme, variant, elevated, selected)
|
|
174
|
+
const colors = applyColorOverrides(
|
|
175
|
+
theme,
|
|
176
|
+
baseColors,
|
|
177
|
+
containerColor,
|
|
178
|
+
contentColor,
|
|
179
|
+
)
|
|
180
|
+
const labelStyle = theme.typography.labelLarge
|
|
181
|
+
const elevationLevel0 = elevationStyle(theme.elevation.level0)
|
|
182
|
+
const elevationLevel1 = elevationStyle(theme.elevation.level1)
|
|
183
|
+
const elevationLevel2 = elevationStyle(theme.elevation.level2)
|
|
184
|
+
const isElevated = elevated && variant !== 'input'
|
|
185
|
+
const baseElevation = isElevated ? elevationLevel1 : elevationLevel0
|
|
186
|
+
|
|
187
|
+
return StyleSheet.create({
|
|
188
|
+
container: {
|
|
189
|
+
alignSelf: 'flex-start',
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
flexDirection: 'row',
|
|
192
|
+
height: 32,
|
|
193
|
+
paddingStart: hasLeadingContent ? 8 : 16,
|
|
194
|
+
paddingEnd: hasTrailingContent ? 8 : 16,
|
|
195
|
+
borderRadius: theme.shape.cornerSmall,
|
|
196
|
+
backgroundColor: colors.backgroundColor,
|
|
197
|
+
borderColor: colors.borderColor,
|
|
198
|
+
borderWidth: colors.borderWidth,
|
|
199
|
+
cursor: 'pointer',
|
|
200
|
+
...baseElevation,
|
|
201
|
+
},
|
|
202
|
+
hoveredContainer: {
|
|
203
|
+
backgroundColor: colors.hoveredBackgroundColor,
|
|
204
|
+
...(isElevated ? elevationLevel2 : undefined),
|
|
205
|
+
},
|
|
206
|
+
pressedContainer: {
|
|
207
|
+
backgroundColor: colors.pressedBackgroundColor,
|
|
208
|
+
},
|
|
209
|
+
disabledContainer: {
|
|
210
|
+
backgroundColor: colors.disabledBackgroundColor,
|
|
211
|
+
borderColor: colors.disabledBorderColor,
|
|
212
|
+
cursor: 'auto',
|
|
213
|
+
...elevationLevel0,
|
|
214
|
+
},
|
|
215
|
+
label: {
|
|
216
|
+
fontFamily: labelStyle.fontFamily,
|
|
217
|
+
fontSize: labelStyle.fontSize,
|
|
218
|
+
lineHeight: labelStyle.lineHeight,
|
|
219
|
+
fontWeight: labelStyle.fontWeight,
|
|
220
|
+
letterSpacing: labelStyle.letterSpacing,
|
|
221
|
+
color: colors.textColor,
|
|
222
|
+
},
|
|
223
|
+
disabledLabel: {
|
|
224
|
+
color: colors.disabledTextColor,
|
|
225
|
+
},
|
|
226
|
+
leadingIcon: {
|
|
227
|
+
marginEnd: theme.spacing.sm,
|
|
228
|
+
},
|
|
229
|
+
avatar: {
|
|
230
|
+
marginEnd: theme.spacing.sm,
|
|
231
|
+
width: 24,
|
|
232
|
+
height: 24,
|
|
233
|
+
borderRadius: 12,
|
|
234
|
+
overflow: 'hidden' as const,
|
|
235
|
+
},
|
|
236
|
+
closeButton: {
|
|
237
|
+
marginStart: theme.spacing.sm,
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
|
|
2
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
3
|
+
import type { PressableProps, StyleProp, TextStyle } from 'react-native'
|
|
4
|
+
|
|
5
|
+
/** Visual variant of the chip following Material Design 3 chip types. */
|
|
6
|
+
export type ChipVariant = 'assist' | 'filter' | 'input' | 'suggestion'
|
|
7
|
+
|
|
8
|
+
export interface ChipProps extends Omit<PressableProps, 'children'> {
|
|
9
|
+
/** Text label rendered inside the chip. */
|
|
10
|
+
children: string
|
|
11
|
+
/**
|
|
12
|
+
* Chip type variant. Controls appearance, allowed interactions, and icon behavior.
|
|
13
|
+
* @default 'assist'
|
|
14
|
+
*/
|
|
15
|
+
variant?: ChipVariant
|
|
16
|
+
/**
|
|
17
|
+
* Whether the chip uses an elevated surface instead of an outline border.
|
|
18
|
+
* Available on `assist`, `filter`, and `suggestion` variants.
|
|
19
|
+
* Ignored on `input` variant (always outlined).
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
elevated?: boolean
|
|
23
|
+
/**
|
|
24
|
+
* Whether the chip is in a selected (toggled-on) state.
|
|
25
|
+
* Only meaningful for the `filter` variant. Ignored by other variants.
|
|
26
|
+
*/
|
|
27
|
+
selected?: boolean
|
|
28
|
+
/** Name of a MaterialCommunityIcons icon to show before the label. */
|
|
29
|
+
leadingIcon?: ComponentProps<typeof MaterialCommunityIcons>['name']
|
|
30
|
+
/**
|
|
31
|
+
* Size of the leading icon in dp.
|
|
32
|
+
* @default 18
|
|
33
|
+
*/
|
|
34
|
+
iconSize?: number
|
|
35
|
+
/**
|
|
36
|
+
* Custom avatar content (e.g. a small Image or View) to render before the label.
|
|
37
|
+
* Only applicable to the `input` variant. Takes precedence over `leadingIcon`.
|
|
38
|
+
*/
|
|
39
|
+
avatar?: ReactNode
|
|
40
|
+
/**
|
|
41
|
+
* Callback fired when the close/remove icon is pressed.
|
|
42
|
+
* When provided, renders a trailing close icon.
|
|
43
|
+
* Only renders on `input` and `filter` (when selected) variants.
|
|
44
|
+
*/
|
|
45
|
+
onClose?: () => void
|
|
46
|
+
/**
|
|
47
|
+
* Override the container (background) color.
|
|
48
|
+
* State-layer colors (hover, press) are derived automatically.
|
|
49
|
+
*/
|
|
50
|
+
containerColor?: string
|
|
51
|
+
/**
|
|
52
|
+
* Override the content (label and icon) color.
|
|
53
|
+
* State-layer colors are derived automatically when no containerColor is set.
|
|
54
|
+
*/
|
|
55
|
+
contentColor?: string
|
|
56
|
+
/** Additional style applied to the label text. */
|
|
57
|
+
labelStyle?: StyleProp<TextStyle>
|
|
58
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'
|
|
2
|
+
import { useMemo } from 'react'
|
|
3
|
+
import { Pressable } 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 {
|
|
9
|
+
IconButtonProps,
|
|
10
|
+
IconButtonSize,
|
|
11
|
+
IconButtonVariant,
|
|
12
|
+
} from './types'
|
|
13
|
+
import { alphaColor, blendColor } from '../utils/color'
|
|
14
|
+
|
|
15
|
+
function getIconColor(
|
|
16
|
+
variant: IconButtonVariant,
|
|
17
|
+
theme: ReturnType<typeof useTheme>,
|
|
18
|
+
disabled: boolean,
|
|
19
|
+
isToggle: boolean,
|
|
20
|
+
selected: boolean,
|
|
21
|
+
): string {
|
|
22
|
+
if (disabled) {
|
|
23
|
+
return alphaColor(theme.colors.onSurface, 0.38)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (isToggle) {
|
|
27
|
+
if (variant === 'filled') {
|
|
28
|
+
return selected ? theme.colors.onPrimary : theme.colors.primary
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (variant === 'tonal') {
|
|
32
|
+
return selected
|
|
33
|
+
? theme.colors.onSecondaryContainer
|
|
34
|
+
: theme.colors.onSurfaceVariant
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (variant === 'outlined') {
|
|
38
|
+
return selected
|
|
39
|
+
? theme.colors.inverseOnSurface
|
|
40
|
+
: theme.colors.onSurfaceVariant
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return selected ? theme.colors.primary : theme.colors.onSurfaceVariant
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (variant === 'filled') {
|
|
47
|
+
return theme.colors.onPrimary
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (variant === 'tonal') {
|
|
51
|
+
return theme.colors.onSecondaryContainer
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return theme.colors.onSurfaceVariant
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getColorStyle(
|
|
58
|
+
styles: ReturnType<typeof createStyles>,
|
|
59
|
+
variant: IconButtonVariant,
|
|
60
|
+
isToggle: boolean,
|
|
61
|
+
selected: boolean,
|
|
62
|
+
) {
|
|
63
|
+
if (isToggle) {
|
|
64
|
+
if (variant === 'tonal') {
|
|
65
|
+
return selected
|
|
66
|
+
? styles.colorTonalToggleSelected
|
|
67
|
+
: styles.colorTonalToggleUnselected
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (variant === 'outlined') {
|
|
71
|
+
return selected
|
|
72
|
+
? styles.colorOutlinedToggleSelected
|
|
73
|
+
: styles.colorOutlined
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (variant === 'standard') {
|
|
77
|
+
return selected
|
|
78
|
+
? styles.colorStandardToggleSelected
|
|
79
|
+
: styles.colorStandard
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return selected
|
|
83
|
+
? styles.colorFilledToggleSelected
|
|
84
|
+
: styles.colorFilledToggleUnselected
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (variant === 'tonal') {
|
|
88
|
+
return styles.colorTonal
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (variant === 'outlined') {
|
|
92
|
+
return styles.colorOutlined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (variant === 'standard') {
|
|
96
|
+
return styles.colorStandard
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return styles.colorFilled
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getSizeStyle(
|
|
103
|
+
styles: ReturnType<typeof createStyles>,
|
|
104
|
+
size: IconButtonSize,
|
|
105
|
+
) {
|
|
106
|
+
if (size === 'small') {
|
|
107
|
+
return styles.sizeSmall
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (size === 'large') {
|
|
111
|
+
return styles.sizeLarge
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return styles.sizeMedium
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getIconPixelSize(size: IconButtonSize): number {
|
|
118
|
+
if (size === 'small') {
|
|
119
|
+
return 18
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (size === 'large') {
|
|
123
|
+
return 28
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return 24
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getDefaultHitSlop(size: IconButtonSize): number {
|
|
130
|
+
if (size === 'small') {
|
|
131
|
+
return 8
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (size === 'large') {
|
|
135
|
+
return 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return 4
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getHoveredStyle(
|
|
142
|
+
styles: ReturnType<typeof createStyles>,
|
|
143
|
+
variant: IconButtonVariant,
|
|
144
|
+
isToggle: boolean,
|
|
145
|
+
selected: boolean,
|
|
146
|
+
) {
|
|
147
|
+
if (isToggle) {
|
|
148
|
+
if (variant === 'tonal') {
|
|
149
|
+
return selected
|
|
150
|
+
? styles.hoveredTonalToggleSelected
|
|
151
|
+
: styles.hoveredTonalToggleUnselected
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (variant === 'outlined') {
|
|
155
|
+
return selected
|
|
156
|
+
? styles.hoveredOutlinedToggleSelected
|
|
157
|
+
: styles.hoveredOutlinedToggleUnselected
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (variant === 'standard') {
|
|
161
|
+
return selected
|
|
162
|
+
? styles.hoveredStandardToggleSelected
|
|
163
|
+
: styles.hoveredStandardToggleUnselected
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return selected
|
|
167
|
+
? styles.hoveredFilledToggleSelected
|
|
168
|
+
: styles.hoveredFilledToggleUnselected
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (variant === 'tonal') {
|
|
172
|
+
return styles.hoveredTonal
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (variant === 'outlined') {
|
|
176
|
+
return styles.hoveredOutlined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (variant === 'standard') {
|
|
180
|
+
return styles.hoveredStandard
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return styles.hoveredFilled
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getPressedStyle(
|
|
187
|
+
styles: ReturnType<typeof createStyles>,
|
|
188
|
+
variant: IconButtonVariant,
|
|
189
|
+
isToggle: boolean,
|
|
190
|
+
selected: boolean,
|
|
191
|
+
) {
|
|
192
|
+
if (isToggle) {
|
|
193
|
+
if (variant === 'tonal') {
|
|
194
|
+
return selected
|
|
195
|
+
? styles.pressedTonalToggleSelected
|
|
196
|
+
: styles.pressedTonalToggleUnselected
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (variant === 'outlined') {
|
|
200
|
+
return selected
|
|
201
|
+
? styles.pressedOutlinedToggleSelected
|
|
202
|
+
: styles.pressedOutlinedToggleUnselected
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (variant === 'standard') {
|
|
206
|
+
return selected
|
|
207
|
+
? styles.pressedStandardToggleSelected
|
|
208
|
+
: styles.pressedStandardToggleUnselected
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return selected
|
|
212
|
+
? styles.pressedFilledToggleSelected
|
|
213
|
+
: styles.pressedFilledToggleUnselected
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (variant === 'tonal') {
|
|
217
|
+
return styles.pressedTonal
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (variant === 'outlined') {
|
|
221
|
+
return styles.pressedOutlined
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (variant === 'standard') {
|
|
225
|
+
return styles.pressedStandard
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return styles.pressedFilled
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getDisabledStyle(
|
|
232
|
+
styles: ReturnType<typeof createStyles>,
|
|
233
|
+
variant: IconButtonVariant,
|
|
234
|
+
) {
|
|
235
|
+
if (variant === 'tonal') {
|
|
236
|
+
return styles.disabledTonal
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (variant === 'outlined') {
|
|
240
|
+
return styles.disabledOutlined
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (variant === 'standard') {
|
|
244
|
+
return styles.disabledStandard
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return styles.disabledFilled
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function IconButton({
|
|
251
|
+
icon,
|
|
252
|
+
selectedIcon,
|
|
253
|
+
iconColor,
|
|
254
|
+
contentColor,
|
|
255
|
+
containerColor,
|
|
256
|
+
style,
|
|
257
|
+
onPress,
|
|
258
|
+
disabled = false,
|
|
259
|
+
variant = 'filled',
|
|
260
|
+
selected,
|
|
261
|
+
size = 'medium',
|
|
262
|
+
hitSlop,
|
|
263
|
+
accessibilityLabel,
|
|
264
|
+
...props
|
|
265
|
+
}: IconButtonProps) {
|
|
266
|
+
const theme = useTheme()
|
|
267
|
+
const styles = useMemo(() => createStyles(theme), [theme])
|
|
268
|
+
const isDisabled = Boolean(disabled)
|
|
269
|
+
const isToggle = selected !== undefined
|
|
270
|
+
const isSelected = Boolean(selected)
|
|
271
|
+
const resolvedIconColor =
|
|
272
|
+
contentColor ??
|
|
273
|
+
iconColor ??
|
|
274
|
+
getIconColor(variant, theme, isDisabled, isToggle, isSelected)
|
|
275
|
+
const displayIcon =
|
|
276
|
+
isToggle && isSelected && selectedIcon ? selectedIcon : icon
|
|
277
|
+
const iconPixelSize = getIconPixelSize(size)
|
|
278
|
+
const accessibilityState = isToggle
|
|
279
|
+
? { disabled: isDisabled, selected: isSelected }
|
|
280
|
+
: { disabled: isDisabled }
|
|
281
|
+
|
|
282
|
+
const containerOverrides = useMemo(() => {
|
|
283
|
+
if (!containerColor) return null
|
|
284
|
+
const overlay = resolvedIconColor
|
|
285
|
+
return {
|
|
286
|
+
base: {
|
|
287
|
+
backgroundColor: containerColor,
|
|
288
|
+
borderColor: containerColor,
|
|
289
|
+
borderWidth: 0,
|
|
290
|
+
} as ViewStyle,
|
|
291
|
+
hovered: {
|
|
292
|
+
backgroundColor: blendColor(
|
|
293
|
+
containerColor,
|
|
294
|
+
overlay,
|
|
295
|
+
theme.stateLayer.hoveredOpacity,
|
|
296
|
+
),
|
|
297
|
+
} as ViewStyle,
|
|
298
|
+
pressed: {
|
|
299
|
+
backgroundColor: blendColor(
|
|
300
|
+
containerColor,
|
|
301
|
+
overlay,
|
|
302
|
+
theme.stateLayer.pressedOpacity,
|
|
303
|
+
),
|
|
304
|
+
} as ViewStyle,
|
|
305
|
+
}
|
|
306
|
+
}, [containerColor, resolvedIconColor, theme.stateLayer])
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<Pressable
|
|
310
|
+
{...props}
|
|
311
|
+
accessibilityRole="button"
|
|
312
|
+
accessibilityLabel={accessibilityLabel}
|
|
313
|
+
accessibilityState={accessibilityState}
|
|
314
|
+
disabled={isDisabled}
|
|
315
|
+
hitSlop={hitSlop ?? getDefaultHitSlop(size)}
|
|
316
|
+
onPress={onPress}
|
|
317
|
+
style={({
|
|
318
|
+
pressed,
|
|
319
|
+
hovered,
|
|
320
|
+
}: {
|
|
321
|
+
pressed: boolean
|
|
322
|
+
hovered?: boolean
|
|
323
|
+
}) => {
|
|
324
|
+
const base: StyleProp<ViewStyle>[] = [
|
|
325
|
+
styles.container,
|
|
326
|
+
getSizeStyle(styles, size),
|
|
327
|
+
getColorStyle(styles, variant, isToggle, isSelected),
|
|
328
|
+
containerOverrides?.base,
|
|
329
|
+
hovered && !pressed && !isDisabled
|
|
330
|
+
? containerOverrides
|
|
331
|
+
? containerOverrides.hovered
|
|
332
|
+
: getHoveredStyle(styles, variant, isToggle, isSelected)
|
|
333
|
+
: undefined,
|
|
334
|
+
pressed && !isDisabled
|
|
335
|
+
? containerOverrides
|
|
336
|
+
? containerOverrides.pressed
|
|
337
|
+
: getPressedStyle(styles, variant, isToggle, isSelected)
|
|
338
|
+
: undefined,
|
|
339
|
+
isDisabled ? getDisabledStyle(styles, variant) : undefined,
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
if (typeof style === 'function') {
|
|
343
|
+
base.push(style({ pressed }))
|
|
344
|
+
} else if (style) {
|
|
345
|
+
base.push(style)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return base
|
|
349
|
+
}}
|
|
350
|
+
>
|
|
351
|
+
<MaterialCommunityIcons
|
|
352
|
+
name={displayIcon}
|
|
353
|
+
size={iconPixelSize}
|
|
354
|
+
color={resolvedIconColor}
|
|
355
|
+
/>
|
|
356
|
+
</Pressable>
|
|
357
|
+
)
|
|
358
|
+
}
|