@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
package/package.json
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onlynative/components",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "Material Design 3 UI components for React Native — Button, Card, Chip, TextField, and more.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"react-native": "src/index.ts",
|
|
11
|
+
"source": "src/index.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"react-native": "./src/index.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./typography": {
|
|
19
|
+
"types": "./dist/typography/index.d.ts",
|
|
20
|
+
"react-native": "./src/typography/index.ts",
|
|
21
|
+
"default": "./dist/typography/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./layout": {
|
|
24
|
+
"types": "./dist/layout/index.d.ts",
|
|
25
|
+
"react-native": "./src/layout/index.ts",
|
|
26
|
+
"default": "./dist/layout/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./button": {
|
|
29
|
+
"types": "./dist/button/index.d.ts",
|
|
30
|
+
"react-native": "./src/button/index.ts",
|
|
31
|
+
"default": "./dist/button/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./icon-button": {
|
|
34
|
+
"types": "./dist/icon-button/index.d.ts",
|
|
35
|
+
"react-native": "./src/icon-button/index.ts",
|
|
36
|
+
"default": "./dist/icon-button/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./appbar": {
|
|
39
|
+
"types": "./dist/appbar/index.d.ts",
|
|
40
|
+
"react-native": "./src/appbar/index.ts",
|
|
41
|
+
"default": "./dist/appbar/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./card": {
|
|
44
|
+
"types": "./dist/card/index.d.ts",
|
|
45
|
+
"react-native": "./src/card/index.ts",
|
|
46
|
+
"default": "./dist/card/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./chip": {
|
|
49
|
+
"types": "./dist/chip/index.d.ts",
|
|
50
|
+
"react-native": "./src/chip/index.ts",
|
|
51
|
+
"default": "./dist/chip/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./checkbox": {
|
|
54
|
+
"types": "./dist/checkbox/index.d.ts",
|
|
55
|
+
"react-native": "./src/checkbox/index.ts",
|
|
56
|
+
"default": "./dist/checkbox/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./radio": {
|
|
59
|
+
"types": "./dist/radio/index.d.ts",
|
|
60
|
+
"react-native": "./src/radio/index.ts",
|
|
61
|
+
"default": "./dist/radio/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./switch": {
|
|
64
|
+
"types": "./dist/switch/index.d.ts",
|
|
65
|
+
"react-native": "./src/switch/index.ts",
|
|
66
|
+
"default": "./dist/switch/index.js"
|
|
67
|
+
},
|
|
68
|
+
"./text-field": {
|
|
69
|
+
"types": "./dist/text-field/index.d.ts",
|
|
70
|
+
"react-native": "./src/text-field/index.ts",
|
|
71
|
+
"default": "./dist/text-field/index.js"
|
|
72
|
+
},
|
|
73
|
+
"./list": {
|
|
74
|
+
"types": "./dist/list/index.d.ts",
|
|
75
|
+
"react-native": "./src/list/index.ts",
|
|
76
|
+
"default": "./dist/list/index.js"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"typesVersions": {
|
|
80
|
+
"*": {
|
|
81
|
+
"typography": [
|
|
82
|
+
"dist/typography/index.d.ts"
|
|
83
|
+
],
|
|
84
|
+
"layout": [
|
|
85
|
+
"dist/layout/index.d.ts"
|
|
86
|
+
],
|
|
87
|
+
"button": [
|
|
88
|
+
"dist/button/index.d.ts"
|
|
89
|
+
],
|
|
90
|
+
"icon-button": [
|
|
91
|
+
"dist/icon-button/index.d.ts"
|
|
92
|
+
],
|
|
93
|
+
"appbar": [
|
|
94
|
+
"dist/appbar/index.d.ts"
|
|
95
|
+
],
|
|
96
|
+
"card": [
|
|
97
|
+
"dist/card/index.d.ts"
|
|
98
|
+
],
|
|
99
|
+
"chip": [
|
|
100
|
+
"dist/chip/index.d.ts"
|
|
101
|
+
],
|
|
102
|
+
"checkbox": [
|
|
103
|
+
"dist/checkbox/index.d.ts"
|
|
104
|
+
],
|
|
105
|
+
"radio": [
|
|
106
|
+
"dist/radio/index.d.ts"
|
|
107
|
+
],
|
|
108
|
+
"switch": [
|
|
109
|
+
"dist/switch/index.d.ts"
|
|
110
|
+
],
|
|
111
|
+
"text-field": [
|
|
112
|
+
"dist/text-field/index.d.ts"
|
|
113
|
+
],
|
|
114
|
+
"list": [
|
|
115
|
+
"dist/list/index.d.ts"
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"publishConfig": {
|
|
120
|
+
"access": "public"
|
|
121
|
+
},
|
|
122
|
+
"files": [
|
|
123
|
+
"dist",
|
|
124
|
+
"src",
|
|
125
|
+
"!src/__tests__"
|
|
126
|
+
],
|
|
127
|
+
"license": "MIT",
|
|
128
|
+
"repository": {
|
|
129
|
+
"type": "git",
|
|
130
|
+
"url": "https://github.com/onlynative/ui.git",
|
|
131
|
+
"directory": "packages/components"
|
|
132
|
+
},
|
|
133
|
+
"homepage": "https://github.com/onlynative/ui",
|
|
134
|
+
"keywords": [
|
|
135
|
+
"react-native",
|
|
136
|
+
"material-design-3",
|
|
137
|
+
"ui-components",
|
|
138
|
+
"md3",
|
|
139
|
+
"material-you"
|
|
140
|
+
],
|
|
141
|
+
"scripts": {
|
|
142
|
+
"build": "tsup src/index.ts src/typography/index.ts src/layout/index.ts src/button/index.ts src/icon-button/index.ts src/appbar/index.ts src/card/index.ts src/chip/index.ts src/checkbox/index.ts src/radio/index.ts src/switch/index.ts src/text-field/index.ts src/list/index.ts --dts --format cjs --outDir dist --clean",
|
|
143
|
+
"typecheck": "tsc --noEmit",
|
|
144
|
+
"test": "jest --passWithNoTests"
|
|
145
|
+
},
|
|
146
|
+
"peerDependencies": {
|
|
147
|
+
"@expo/vector-icons": ">=14.0.0",
|
|
148
|
+
"@onlynative/core": ">=0.1.0-alpha.0",
|
|
149
|
+
"react": ">=18.0.0",
|
|
150
|
+
"react-native": ">=0.72.0",
|
|
151
|
+
"react-native-safe-area-context": ">=4.0.0"
|
|
152
|
+
},
|
|
153
|
+
"devDependencies": {
|
|
154
|
+
"@react-native/babel-preset": "^0.81.5",
|
|
155
|
+
"@onlynative/core": "workspace:*",
|
|
156
|
+
"@testing-library/react-native": "^13.3.3",
|
|
157
|
+
"@types/jest": "^29.5.14",
|
|
158
|
+
"@types/react": "^19.0.0",
|
|
159
|
+
"jest": "^29.7.0",
|
|
160
|
+
"react": "19.1.0",
|
|
161
|
+
"react-native": "0.81.5",
|
|
162
|
+
"react-test-renderer": "19.1.0",
|
|
163
|
+
"tsup": "^8.0.0",
|
|
164
|
+
"typescript": "^5.0.0"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import type { LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native'
|
|
4
|
+
import { Platform, View } from 'react-native'
|
|
5
|
+
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
6
|
+
import { defaultTopAppBarTokens, useTheme } from '@onlynative/core'
|
|
7
|
+
|
|
8
|
+
import { IconButton } from '../icon-button'
|
|
9
|
+
import type { IconButtonProps } from '../icon-button'
|
|
10
|
+
import { Typography } from '../typography'
|
|
11
|
+
import type { TypographyVariant } from '../typography'
|
|
12
|
+
import { selectRTL } from '../utils/rtl'
|
|
13
|
+
import { createStyles } from './styles'
|
|
14
|
+
import type { AppBarProps } from './types'
|
|
15
|
+
|
|
16
|
+
type AppBarSize = 'small' | 'medium' | 'large'
|
|
17
|
+
function getBackIcon(): IconButtonProps['icon'] {
|
|
18
|
+
if (Platform.OS === 'ios') {
|
|
19
|
+
return selectRTL('chevron-left', 'chevron-right')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return selectRTL('arrow-left', 'arrow-right')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const titleVariantBySize: Record<AppBarSize, TypographyVariant> = {
|
|
26
|
+
small: 'titleLarge',
|
|
27
|
+
medium: 'headlineSmall',
|
|
28
|
+
large: 'headlineMedium',
|
|
29
|
+
}
|
|
30
|
+
const APP_BAR_TITLE_TEXT_PROPS = {
|
|
31
|
+
numberOfLines: 1,
|
|
32
|
+
ellipsizeMode: 'tail',
|
|
33
|
+
accessibilityRole: 'header',
|
|
34
|
+
} as const
|
|
35
|
+
|
|
36
|
+
function resolveSize(variant: AppBarProps['variant']): AppBarSize {
|
|
37
|
+
if (variant === 'medium' || variant === 'large') {
|
|
38
|
+
return variant
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 'small'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getSizeStyle(
|
|
45
|
+
styles: ReturnType<typeof createStyles>,
|
|
46
|
+
size: AppBarSize,
|
|
47
|
+
) {
|
|
48
|
+
if (size === 'large') {
|
|
49
|
+
return styles.largeContainer
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return styles.mediumContainer
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function withTopInset(
|
|
56
|
+
enabled: boolean,
|
|
57
|
+
content: ReactNode,
|
|
58
|
+
style: StyleProp<ViewStyle>,
|
|
59
|
+
) {
|
|
60
|
+
if (enabled) {
|
|
61
|
+
return (
|
|
62
|
+
<SafeAreaView edges={['top']} style={style}>
|
|
63
|
+
{content}
|
|
64
|
+
</SafeAreaView>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return <View style={style}>{content}</View>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function measureWidth(event: LayoutChangeEvent): number {
|
|
72
|
+
return Math.round(event.nativeEvent.layout.width)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function AppBar({
|
|
76
|
+
title,
|
|
77
|
+
variant = 'small',
|
|
78
|
+
canGoBack = false,
|
|
79
|
+
onBackPress,
|
|
80
|
+
insetTop = false,
|
|
81
|
+
elevated = false,
|
|
82
|
+
leading,
|
|
83
|
+
trailing,
|
|
84
|
+
actions,
|
|
85
|
+
containerColor,
|
|
86
|
+
contentColor,
|
|
87
|
+
titleStyle,
|
|
88
|
+
style,
|
|
89
|
+
}: AppBarProps) {
|
|
90
|
+
const theme = useTheme()
|
|
91
|
+
const topAppBar = theme.topAppBar ?? defaultTopAppBarTokens
|
|
92
|
+
const styles = useMemo(() => createStyles(theme), [theme])
|
|
93
|
+
const [leadingWidth, setLeadingWidth] = useState(0)
|
|
94
|
+
const [actionsWidth, setActionsWidth] = useState(0)
|
|
95
|
+
const titleColorStyle = useMemo(
|
|
96
|
+
() => ({ color: contentColor ?? theme.colors.onSurface }),
|
|
97
|
+
[contentColor, theme.colors.onSurface],
|
|
98
|
+
)
|
|
99
|
+
const size = resolveSize(variant)
|
|
100
|
+
const titleVariant = titleVariantBySize[size]
|
|
101
|
+
const isCenterAligned = variant === 'center-aligned'
|
|
102
|
+
const isExpanded = size !== 'small'
|
|
103
|
+
const titleStartInset =
|
|
104
|
+
topAppBar.horizontalPadding +
|
|
105
|
+
Math.max(topAppBar.titleStartInset, leadingWidth)
|
|
106
|
+
const compactTitleEndInset = topAppBar.horizontalPadding + actionsWidth
|
|
107
|
+
const centeredSideInset =
|
|
108
|
+
topAppBar.horizontalPadding + Math.max(leadingWidth, actionsWidth)
|
|
109
|
+
const expandedTitleInsetStyle = useMemo<ViewStyle>(
|
|
110
|
+
() => ({ paddingStart: titleStartInset }),
|
|
111
|
+
[titleStartInset],
|
|
112
|
+
)
|
|
113
|
+
const overlayTitleInsetStyle = useMemo<ViewStyle>(
|
|
114
|
+
() =>
|
|
115
|
+
isCenterAligned
|
|
116
|
+
? { start: centeredSideInset, end: centeredSideInset }
|
|
117
|
+
: { start: titleStartInset, end: compactTitleEndInset },
|
|
118
|
+
[centeredSideInset, compactTitleEndInset, isCenterAligned, titleStartInset],
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
const leadingContent = useMemo(() => {
|
|
122
|
+
if (leading) {
|
|
123
|
+
return leading
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!canGoBack) {
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<View style={styles.iconFrame}>
|
|
132
|
+
<IconButton
|
|
133
|
+
icon={getBackIcon()}
|
|
134
|
+
size="medium"
|
|
135
|
+
variant="standard"
|
|
136
|
+
iconColor={contentColor ?? theme.colors.onSurface}
|
|
137
|
+
accessibilityLabel="Go back"
|
|
138
|
+
onPress={onBackPress}
|
|
139
|
+
/>
|
|
140
|
+
</View>
|
|
141
|
+
)
|
|
142
|
+
}, [
|
|
143
|
+
canGoBack,
|
|
144
|
+
contentColor,
|
|
145
|
+
leading,
|
|
146
|
+
onBackPress,
|
|
147
|
+
styles.iconFrame,
|
|
148
|
+
theme.colors.onSurface,
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
const actionsContent = useMemo(() => {
|
|
152
|
+
if (trailing) {
|
|
153
|
+
return trailing
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!actions || actions.length === 0) {
|
|
157
|
+
return null
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<View style={styles.actionsRow}>
|
|
162
|
+
{actions.map((action, index) => (
|
|
163
|
+
<View
|
|
164
|
+
key={`${String(action.icon)}-${index}`}
|
|
165
|
+
style={styles.iconFrame}
|
|
166
|
+
>
|
|
167
|
+
<IconButton
|
|
168
|
+
icon={action.icon}
|
|
169
|
+
size="medium"
|
|
170
|
+
variant="standard"
|
|
171
|
+
iconColor={contentColor}
|
|
172
|
+
accessibilityLabel={action.accessibilityLabel}
|
|
173
|
+
onPress={action.onPress}
|
|
174
|
+
disabled={action.disabled}
|
|
175
|
+
/>
|
|
176
|
+
</View>
|
|
177
|
+
))}
|
|
178
|
+
</View>
|
|
179
|
+
)
|
|
180
|
+
}, [actions, contentColor, styles.actionsRow, styles.iconFrame, trailing])
|
|
181
|
+
|
|
182
|
+
const onLeadingLayout = useCallback((event: LayoutChangeEvent) => {
|
|
183
|
+
const nextWidth = measureWidth(event)
|
|
184
|
+
|
|
185
|
+
setLeadingWidth((currentWidth) => {
|
|
186
|
+
if (currentWidth === nextWidth) {
|
|
187
|
+
return currentWidth
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return nextWidth
|
|
191
|
+
})
|
|
192
|
+
}, [])
|
|
193
|
+
|
|
194
|
+
const onActionsLayout = useCallback((event: LayoutChangeEvent) => {
|
|
195
|
+
const nextWidth = measureWidth(event)
|
|
196
|
+
|
|
197
|
+
setActionsWidth((currentWidth) => {
|
|
198
|
+
if (currentWidth === nextWidth) {
|
|
199
|
+
return currentWidth
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return nextWidth
|
|
203
|
+
})
|
|
204
|
+
}, [])
|
|
205
|
+
|
|
206
|
+
const topRow = (
|
|
207
|
+
<View style={styles.topRow}>
|
|
208
|
+
<View
|
|
209
|
+
collapsable={false}
|
|
210
|
+
onLayout={onLeadingLayout}
|
|
211
|
+
style={styles.sideSlot}
|
|
212
|
+
>
|
|
213
|
+
{leadingContent}
|
|
214
|
+
</View>
|
|
215
|
+
<View style={styles.topRowSpacer} />
|
|
216
|
+
<View
|
|
217
|
+
collapsable={false}
|
|
218
|
+
onLayout={onActionsLayout}
|
|
219
|
+
style={styles.sideSlot}
|
|
220
|
+
>
|
|
221
|
+
{actionsContent}
|
|
222
|
+
</View>
|
|
223
|
+
</View>
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const containerOverride = containerColor
|
|
227
|
+
? ({ backgroundColor: containerColor } as ViewStyle)
|
|
228
|
+
: undefined
|
|
229
|
+
const rootStyle: StyleProp<ViewStyle> = [
|
|
230
|
+
styles.root,
|
|
231
|
+
elevated ? styles.elevatedRoot : undefined,
|
|
232
|
+
containerOverride,
|
|
233
|
+
style,
|
|
234
|
+
]
|
|
235
|
+
const safeAreaStyle: StyleProp<ViewStyle> = [
|
|
236
|
+
styles.safeArea,
|
|
237
|
+
elevated ? styles.elevatedSafeArea : undefined,
|
|
238
|
+
containerOverride,
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
if (isExpanded) {
|
|
242
|
+
const content = (
|
|
243
|
+
<View style={[styles.expandedContainer, getSizeStyle(styles, size)]}>
|
|
244
|
+
{topRow}
|
|
245
|
+
<View
|
|
246
|
+
style={[
|
|
247
|
+
styles.expandedTitleContainer,
|
|
248
|
+
size === 'large'
|
|
249
|
+
? styles.largeTitlePadding
|
|
250
|
+
: styles.mediumTitlePadding,
|
|
251
|
+
expandedTitleInsetStyle,
|
|
252
|
+
]}
|
|
253
|
+
>
|
|
254
|
+
<Typography
|
|
255
|
+
{...APP_BAR_TITLE_TEXT_PROPS}
|
|
256
|
+
variant={titleVariant}
|
|
257
|
+
style={[
|
|
258
|
+
styles.title,
|
|
259
|
+
titleColorStyle,
|
|
260
|
+
styles.startAlignedTitle,
|
|
261
|
+
titleStyle,
|
|
262
|
+
]}
|
|
263
|
+
>
|
|
264
|
+
{title}
|
|
265
|
+
</Typography>
|
|
266
|
+
</View>
|
|
267
|
+
</View>
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<View style={rootStyle}>
|
|
272
|
+
{withTopInset(insetTop, content, safeAreaStyle)}
|
|
273
|
+
</View>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const content = (
|
|
278
|
+
<View style={styles.smallContainer}>
|
|
279
|
+
{topRow}
|
|
280
|
+
<View style={[styles.overlayTitleContainer, overlayTitleInsetStyle]}>
|
|
281
|
+
<Typography
|
|
282
|
+
{...APP_BAR_TITLE_TEXT_PROPS}
|
|
283
|
+
variant={titleVariant}
|
|
284
|
+
style={[
|
|
285
|
+
styles.title,
|
|
286
|
+
titleColorStyle,
|
|
287
|
+
isCenterAligned ? styles.centeredTitle : styles.startAlignedTitle,
|
|
288
|
+
titleStyle,
|
|
289
|
+
]}
|
|
290
|
+
>
|
|
291
|
+
{title}
|
|
292
|
+
</Typography>
|
|
293
|
+
</View>
|
|
294
|
+
</View>
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<View style={rootStyle}>
|
|
299
|
+
{withTopInset(insetTop, content, safeAreaStyle)}
|
|
300
|
+
</View>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native'
|
|
2
|
+
import { defaultTopAppBarTokens } from '@onlynative/core'
|
|
3
|
+
import type { Theme } from '@onlynative/core'
|
|
4
|
+
|
|
5
|
+
export function createStyles(theme: Theme) {
|
|
6
|
+
const topAppBar = theme.topAppBar ?? defaultTopAppBarTokens
|
|
7
|
+
|
|
8
|
+
return StyleSheet.create({
|
|
9
|
+
root: {
|
|
10
|
+
backgroundColor: theme.colors.surface,
|
|
11
|
+
},
|
|
12
|
+
safeArea: {
|
|
13
|
+
backgroundColor: theme.colors.surface,
|
|
14
|
+
},
|
|
15
|
+
elevatedRoot: {
|
|
16
|
+
backgroundColor: theme.colors.surfaceContainer,
|
|
17
|
+
},
|
|
18
|
+
elevatedSafeArea: {
|
|
19
|
+
backgroundColor: theme.colors.surfaceContainer,
|
|
20
|
+
},
|
|
21
|
+
smallContainer: {
|
|
22
|
+
height: topAppBar.smallContainerHeight,
|
|
23
|
+
position: 'relative',
|
|
24
|
+
},
|
|
25
|
+
mediumContainer: {
|
|
26
|
+
height: topAppBar.mediumContainerHeight,
|
|
27
|
+
},
|
|
28
|
+
largeContainer: {
|
|
29
|
+
height: topAppBar.largeContainerHeight,
|
|
30
|
+
},
|
|
31
|
+
expandedContainer: {
|
|
32
|
+
position: 'relative',
|
|
33
|
+
},
|
|
34
|
+
topRow: {
|
|
35
|
+
height: topAppBar.topRowHeight,
|
|
36
|
+
paddingHorizontal: topAppBar.horizontalPadding,
|
|
37
|
+
flexDirection: 'row',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
},
|
|
40
|
+
expandedTitleContainer: {
|
|
41
|
+
flex: 1,
|
|
42
|
+
justifyContent: 'flex-end',
|
|
43
|
+
minWidth: 0,
|
|
44
|
+
paddingEnd: theme.spacing.md,
|
|
45
|
+
pointerEvents: 'none',
|
|
46
|
+
},
|
|
47
|
+
topRowSpacer: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
},
|
|
50
|
+
sideSlot: {
|
|
51
|
+
flexDirection: 'row',
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
minHeight: topAppBar.sideSlotMinHeight,
|
|
54
|
+
},
|
|
55
|
+
actionsRow: {
|
|
56
|
+
flexDirection: 'row',
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
},
|
|
59
|
+
iconFrame: {
|
|
60
|
+
width: topAppBar.iconFrameSize,
|
|
61
|
+
height: topAppBar.iconFrameSize,
|
|
62
|
+
alignItems: 'center',
|
|
63
|
+
justifyContent: 'center',
|
|
64
|
+
},
|
|
65
|
+
overlayTitleContainer: {
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
top: 0,
|
|
68
|
+
bottom: 0,
|
|
69
|
+
justifyContent: 'center',
|
|
70
|
+
minWidth: 0,
|
|
71
|
+
pointerEvents: 'none',
|
|
72
|
+
},
|
|
73
|
+
centeredTitle: {
|
|
74
|
+
textAlign: 'center',
|
|
75
|
+
},
|
|
76
|
+
startAlignedTitle: {
|
|
77
|
+
textAlign: 'auto',
|
|
78
|
+
},
|
|
79
|
+
mediumTitlePadding: {
|
|
80
|
+
paddingBottom: topAppBar.mediumTitleBottomPadding,
|
|
81
|
+
},
|
|
82
|
+
largeTitlePadding: {
|
|
83
|
+
paddingBottom: topAppBar.largeTitleBottomPadding,
|
|
84
|
+
},
|
|
85
|
+
title: {
|
|
86
|
+
flexShrink: 1,
|
|
87
|
+
maxWidth: '100%',
|
|
88
|
+
includeFontPadding: false,
|
|
89
|
+
textAlignVertical: 'center',
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { StyleProp, TextStyle, ViewStyle } from 'react-native'
|
|
3
|
+
import type { IconButtonProps } from '../icon-button'
|
|
4
|
+
|
|
5
|
+
/** Size/layout variant of the AppBar. */
|
|
6
|
+
export type AppBarVariant = 'small' | 'center-aligned' | 'medium' | 'large'
|
|
7
|
+
|
|
8
|
+
/** A single action item rendered in the AppBar trailing slot. */
|
|
9
|
+
export interface AppBarAction {
|
|
10
|
+
/** MaterialCommunityIcons icon name to render. */
|
|
11
|
+
icon: IconButtonProps['icon']
|
|
12
|
+
/** Accessibility label for screen readers (required). */
|
|
13
|
+
accessibilityLabel: string
|
|
14
|
+
/** Called when the action icon is pressed. */
|
|
15
|
+
onPress?: () => void
|
|
16
|
+
/**
|
|
17
|
+
* Disables the action icon.
|
|
18
|
+
* @default false
|
|
19
|
+
*/
|
|
20
|
+
disabled?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AppBarProps {
|
|
24
|
+
/** Title text displayed in the bar. */
|
|
25
|
+
title: string
|
|
26
|
+
/**
|
|
27
|
+
* Layout variant.
|
|
28
|
+
* @default 'small'
|
|
29
|
+
*/
|
|
30
|
+
variant?: AppBarVariant
|
|
31
|
+
/**
|
|
32
|
+
* When `true`, renders a back button in the leading slot.
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
canGoBack?: boolean
|
|
36
|
+
/** Called when the auto-rendered back button is pressed. */
|
|
37
|
+
onBackPress?: () => void
|
|
38
|
+
/**
|
|
39
|
+
* When `true`, wraps the bar in a SafeAreaView that handles the top inset.
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
insetTop?: boolean
|
|
43
|
+
/**
|
|
44
|
+
* When `true`, adds shadow/elevation to indicate the bar is scrolled.
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
elevated?: boolean
|
|
48
|
+
/** Custom leading content. When provided, overrides `canGoBack`. */
|
|
49
|
+
leading?: ReactNode
|
|
50
|
+
/** Custom trailing content. When provided, overrides `actions`. */
|
|
51
|
+
trailing?: ReactNode
|
|
52
|
+
/** Array of icon-button actions rendered in the trailing slot. */
|
|
53
|
+
actions?: AppBarAction[]
|
|
54
|
+
/**
|
|
55
|
+
* Override the container (background) color.
|
|
56
|
+
* Applied to both normal and elevated states.
|
|
57
|
+
*/
|
|
58
|
+
containerColor?: string
|
|
59
|
+
/**
|
|
60
|
+
* Override the content (title and icon) color.
|
|
61
|
+
*/
|
|
62
|
+
contentColor?: string
|
|
63
|
+
/** Additional style applied to the title text. */
|
|
64
|
+
titleStyle?: StyleProp<TextStyle>
|
|
65
|
+
/** Custom style applied to the root container. */
|
|
66
|
+
style?: StyleProp<ViewStyle>
|
|
67
|
+
}
|