@hzblj/kara-components 0.26.2 → 0.27.1
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/CHANGELOG.md +32 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.android.js +3 -4
- package/lib/module/atoms/activity-indicator/activity-indicator.android.js.map +1 -1
- package/lib/module/atoms/date-picker/date-picker.android.js +3 -4
- package/lib/module/atoms/date-picker/date-picker.android.js.map +1 -1
- package/lib/module/atoms/island/island.android.js +2 -6
- package/lib/module/atoms/island/island.android.js.map +1 -1
- package/lib/module/atoms/picker/picker.android.js +3 -4
- package/lib/module/atoms/picker/picker.android.js.map +1 -1
- package/lib/module/atoms/pressable/pressable.js +5 -4
- package/lib/module/atoms/pressable/pressable.js.map +1 -1
- package/lib/module/atoms/segmented-control/segmented-control.android.js +3 -4
- package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -1
- package/lib/module/hooks/compose-host.js +27 -0
- package/lib/module/hooks/compose-host.js.map +1 -0
- package/lib/module/molecules/navigation-bar/bar-search-field.js +80 -0
- package/lib/module/molecules/navigation-bar/bar-search-field.js.map +1 -0
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js +94 -22
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
- package/lib/module/molecules/rn-list-item/rn-list-item.js +3 -2
- package/lib/module/molecules/rn-list-item/rn-list-item.js.map +1 -1
- package/lib/module/molecules/rn-tooltip/rn-tooltip.android.js +3 -4
- package/lib/module/molecules/rn-tooltip/rn-tooltip.android.js.map +1 -1
- package/lib/module/organisms/context-menu/context-menu.android.js +4 -5
- package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
- package/lib/module/organisms/header/header-options.android.js +2 -6
- package/lib/module/organisms/header/header-options.android.js.map +1 -1
- package/lib/module/organisms/header/header-search-accessory.android.js +29 -2
- package/lib/module/organisms/header/header-search-accessory.android.js.map +1 -1
- package/lib/module/organisms/header/header-search-input.android.js +11 -51
- package/lib/module/organisms/header/header-search-input.android.js.map +1 -1
- package/lib/module/organisms/header/use-header-search.js +19 -11
- package/lib/module/organisms/header/use-header-search.js.map +1 -1
- package/lib/module/organisms/sheet/sheet.js +17 -1
- package/lib/module/organisms/sheet/sheet.js.map +1 -1
- package/lib/module/organisms/sheet/use-sheet-header.js +7 -1
- package/lib/module/organisms/sheet/use-sheet-header.js.map +1 -1
- package/lib/module/templates/screen/screen.android.js +3 -6
- package/lib/module/templates/screen/screen.android.js.map +1 -1
- package/lib/typescript/hooks/compose-host.d.ts +8 -0
- package/lib/typescript/molecules/navigation-bar/bar-search-field.d.ts +29 -0
- package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -6
- package/lib/typescript/organisms/header/header.types.d.ts +20 -10
- package/lib/typescript/organisms/header/use-header-search.d.ts +5 -3
- package/lib/typescript/organisms/sheet/sheet.types.d.ts +1 -1
- package/package.json +4 -4
- package/src/atoms/activity-indicator/activity-indicator.android.tsx +3 -6
- package/src/atoms/date-picker/date-picker.android.tsx +3 -12
- package/src/atoms/island/island.android.tsx +3 -7
- package/src/atoms/picker/picker.android.tsx +3 -3
- package/src/atoms/pressable/pressable.tsx +5 -7
- package/src/atoms/segmented-control/segmented-control.android.tsx +3 -6
- package/src/hooks/compose-host.tsx +20 -0
- package/src/molecules/navigation-bar/bar-search-field.tsx +94 -0
- package/src/molecules/navigation-bar/navigation-bar.android.tsx +119 -19
- package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -6
- package/src/molecules/rn-list-item/rn-list-item.tsx +2 -1
- package/src/molecules/rn-tooltip/rn-tooltip.android.tsx +4 -3
- package/src/organisms/context-menu/context-menu.android.tsx +5 -9
- package/src/organisms/header/header-options.android.tsx +3 -7
- package/src/organisms/header/header-search-accessory.android.tsx +37 -3
- package/src/organisms/header/header-search-input.android.tsx +14 -53
- package/src/organisms/header/header.types.ts +20 -10
- package/src/organisms/header/use-header-search.tsx +18 -10
- package/src/organisms/sheet/sheet.tsx +19 -2
- package/src/organisms/sheet/sheet.types.ts +1 -1
- package/src/organisms/sheet/use-sheet-header.tsx +7 -1
- package/src/templates/screen/screen.android.tsx +4 -7
|
@@ -19,9 +19,11 @@ const PLACEMENT_OPTIONS: Record<HeaderSearchPlacement, Partial<HeaderSearchBarOp
|
|
|
19
19
|
/**
|
|
20
20
|
* A search field for a screen's header. iOS: UIKit's search bar stacked under the title, with the title and items kept
|
|
21
21
|
* and nothing dimmed by the system, so a list can filter live, or behind a search button in the bar's corner
|
|
22
|
-
* (`placement`); pass `searchBar` to `useHeader` as `search`. Android: Material's search field with its
|
|
23
|
-
* as a row for the screen to put right under the top app bar (`searchInput`)
|
|
24
|
-
*
|
|
22
|
+
* (`placement`); pass `searchBar` to `useHeader` as `search`. Android: `stacked` is Material's search field with its
|
|
23
|
+
* cancel button, as a row for the screen to put right under the top app bar (`searchInput`); `integratedButton` is the
|
|
24
|
+
* toolbar's own search, a magnifier in the bar's corner that expands over the title (`searchBar`, as on iOS). Both
|
|
25
|
+
* report the `query`; `scrim` washes the content out while the field is focused and empty and carries the header
|
|
26
|
+
* field's glyphs and voice.
|
|
25
27
|
*
|
|
26
28
|
* With `voice` the field has a microphone; the app runs the dictation and writes what it hears with `setText`. Typing
|
|
27
29
|
* over it, or ending the search, calls `voice.onCancel`.
|
|
@@ -41,6 +43,7 @@ export const useHeaderSearch = ({
|
|
|
41
43
|
const inputRef = useRef<HeaderSearchInputRef>(null)
|
|
42
44
|
const lastSetText = useRef<string | null>(null)
|
|
43
45
|
const voiceRef = useRef(voice)
|
|
46
|
+
const headerField = Platform.OS === 'ios' || placement === 'integratedButton'
|
|
44
47
|
|
|
45
48
|
voiceRef.current = voice
|
|
46
49
|
|
|
@@ -68,28 +71,33 @@ export const useHeaderSearch = ({
|
|
|
68
71
|
}, [cancelVoice])
|
|
69
72
|
|
|
70
73
|
const searchBar = useMemo((): HeaderSearchBarOptions | undefined => {
|
|
71
|
-
if (!enabled ||
|
|
74
|
+
if (!enabled || !headerField) {
|
|
72
75
|
return undefined
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
return {
|
|
76
79
|
...PLACEMENT_OPTIONS[placement],
|
|
77
80
|
autoCapitalize: 'none',
|
|
81
|
+
headerIconColor: headerColor(theme, 'content.primary'),
|
|
78
82
|
hideWhenScrolling: false,
|
|
83
|
+
hintTextColor: headerColor(theme, 'content.tertiary'),
|
|
79
84
|
obscureBackground: false,
|
|
80
85
|
onBlur: () => setFocused(false),
|
|
81
86
|
onCancelButtonPress: reset,
|
|
82
87
|
onChangeText: event => changeQuery(event.nativeEvent.text),
|
|
88
|
+
onClose: reset,
|
|
83
89
|
onFocus: () => setFocused(true),
|
|
90
|
+
onOpen: () => setFocused(true),
|
|
84
91
|
placeholder,
|
|
85
92
|
placement,
|
|
86
93
|
ref: searchBarRef,
|
|
94
|
+
textColor: Platform.OS === 'android' ? headerColor(theme, 'content.primary') : undefined,
|
|
87
95
|
tintColor: headerColor(theme, 'content.primary'),
|
|
88
96
|
}
|
|
89
|
-
}, [changeQuery, enabled, placeholder, placement, reset, theme])
|
|
97
|
+
}, [changeQuery, enabled, headerField, placeholder, placement, reset, theme])
|
|
90
98
|
|
|
91
99
|
const searchInput = useMemo(() => {
|
|
92
|
-
if (!enabled ||
|
|
100
|
+
if (!enabled || headerField) {
|
|
93
101
|
return null
|
|
94
102
|
}
|
|
95
103
|
|
|
@@ -103,7 +111,7 @@ export const useHeaderSearch = ({
|
|
|
103
111
|
voice={voice}
|
|
104
112
|
/>
|
|
105
113
|
)
|
|
106
|
-
}, [changeQuery, clearIcon, enabled, placeholder, voice])
|
|
114
|
+
}, [changeQuery, clearIcon, enabled, headerField, placeholder, voice])
|
|
107
115
|
|
|
108
116
|
const clear = useCallback(() => {
|
|
109
117
|
reset()
|
|
@@ -128,11 +136,11 @@ export const useHeaderSearch = ({
|
|
|
128
136
|
|
|
129
137
|
return (
|
|
130
138
|
<>
|
|
131
|
-
{dimmed ? <HeaderSearchScrim onPress={clear} topOffset={HEADER_SEARCH_ROW_HEIGHT} /> : null}
|
|
132
|
-
<HeaderSearchAccessory cancelIcon={cancelIcon} clearIcon={clearIcon} voice={voice} />
|
|
139
|
+
{dimmed ? <HeaderSearchScrim onPress={clear} topOffset={headerField ? 0 : HEADER_SEARCH_ROW_HEIGHT} /> : null}
|
|
140
|
+
{headerField ? <HeaderSearchAccessory cancelIcon={cancelIcon} clearIcon={clearIcon} voice={voice} /> : null}
|
|
133
141
|
</>
|
|
134
142
|
)
|
|
135
|
-
}, [cancelIcon, clear, clearIcon, dimmed, enabled, voice])
|
|
143
|
+
}, [cancelIcon, clear, clearIcon, dimmed, enabled, headerField, voice])
|
|
136
144
|
|
|
137
145
|
return {clear, query, scrim, searchBar, searchInput, setText}
|
|
138
146
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {useAppTheme} from '@hzblj/kara-theme'
|
|
2
|
-
import {type DetentChangeEvent, TrueSheet} from '@lodev09/react-native-true-sheet'
|
|
1
|
+
import {useAppTheme, withOpacity} from '@hzblj/kara-theme'
|
|
2
|
+
import {type DetentChangeEvent, type GrabberOptions, TrueSheet} from '@lodev09/react-native-true-sheet'
|
|
3
3
|
import {useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef} from 'react'
|
|
4
4
|
import {Platform, StyleSheet, View} from 'react-native'
|
|
5
5
|
import type {SheetComponent, SheetDetent, SheetProps} from './sheet.types'
|
|
@@ -39,6 +39,22 @@ const SheetRoot = ({
|
|
|
39
39
|
const theme = useAppTheme()
|
|
40
40
|
const {content, header: headerProps} = useMemo(() => splitSheetChildren(children), [children])
|
|
41
41
|
const sheetHeader = useSheetHeader({header: headerProps, scrollable})
|
|
42
|
+
const inverted = headerProps?.inverted ?? false
|
|
43
|
+
/**
|
|
44
|
+
* Material's drag handle, `onSurfaceVariant` at 40 %, in the app's theme. TrueSheet's own Android grabber picks black
|
|
45
|
+
* or white by the system's night mode, so an app themed apart from the device drew it in the sheet's own colour.
|
|
46
|
+
*/
|
|
47
|
+
const grabberOptions = useMemo<GrabberOptions | undefined>(() => {
|
|
48
|
+
if (Platform.OS !== 'android') {
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const color = inverted
|
|
53
|
+
? theme.color('content.on.accent:o40')
|
|
54
|
+
: withOpacity(String(theme.color('content.secondary')), 'o40')
|
|
55
|
+
|
|
56
|
+
return {adaptive: false, color}
|
|
57
|
+
}, [inverted, theme])
|
|
42
58
|
const sheetRef = useRef<TrueSheet>(null)
|
|
43
59
|
const presented = useRef(false)
|
|
44
60
|
/** A dismissal we asked for: `onDismiss` reports the user's, never ours. */
|
|
@@ -114,6 +130,7 @@ const SheetRoot = ({
|
|
|
114
130
|
draggable={draggable}
|
|
115
131
|
footer={footer}
|
|
116
132
|
grabber={grabber}
|
|
133
|
+
grabberOptions={grabberOptions}
|
|
117
134
|
header={sheetHeader.header}
|
|
118
135
|
headerStyle={sheetHeader.headerStyle}
|
|
119
136
|
onDetentChange={handleDetentChange}
|
|
@@ -65,7 +65,7 @@ export type SheetProps = {
|
|
|
65
65
|
draggable?: boolean
|
|
66
66
|
/** Pinned to the bottom, above the content and the keyboard. */
|
|
67
67
|
footer?: ReactElement
|
|
68
|
-
/** The drag handle. On by default. */
|
|
68
|
+
/** The drag handle. On by default. On Android it is Material's handle, coloured by the app's theme. */
|
|
69
69
|
grabber?: boolean
|
|
70
70
|
/** The user dismissed it; `visible` going false does not call this. */
|
|
71
71
|
onDismiss: () => void
|
|
@@ -17,6 +17,12 @@ type UseSheetHeaderParams = {
|
|
|
17
17
|
/** The bar's own height (`NavigationBar`) plus the room above it, before the header has been measured. */
|
|
18
18
|
const ESTIMATED_HEADER_HEIGHT = (Platform.OS === 'ios' ? 56 : 64) + spacing.s16
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Above a header with no bar. Android's grabber is drawn over the sheet's content, 16 dp from the top and 4 dp tall, so
|
|
22
|
+
* there the header starts 8 dp under it. iOS's grabber ends 10 pt from the top, inside the usual 16 pt.
|
|
23
|
+
*/
|
|
24
|
+
const HEADER_TOP_WITHOUT_BAR = Platform.OS === 'android' ? 's28' : 's16'
|
|
25
|
+
|
|
20
26
|
/** Where a large title's bar title appears once it has scrolled away: the platform's own place for it. */
|
|
21
27
|
const COLLAPSED_TITLE_ALIGNMENT: NavigationBarTitleAlignment = Platform.OS === 'ios' ? 'center' : 'leading'
|
|
22
28
|
|
|
@@ -83,7 +89,7 @@ export const useSheetHeader = ({header: headerProps, scrollable}: UseSheetHeader
|
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
return (
|
|
86
|
-
<Box onLayout={isOverlay ? handleHeaderLayout : undefined} pt=
|
|
92
|
+
<Box onLayout={isOverlay ? handleHeaderLayout : undefined} pt={hasBar ? 's16' : HEADER_TOP_WITHOUT_BAR}>
|
|
87
93
|
{hasBar ? (
|
|
88
94
|
<NavigationBar
|
|
89
95
|
inverted={inverted}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {Column
|
|
1
|
+
import {Column} from '@expo/ui/jetpack-compose'
|
|
2
2
|
import {padding} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
3
|
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
4
|
-
import {useAppTheme} from '@hzblj/kara-theme'
|
|
5
4
|
import {useCallback, useMemo, useRef, useState} from 'react'
|
|
6
5
|
import {StatusBar, StyleSheet, View} from 'react-native'
|
|
6
|
+
import {ComposeHost} from '../../hooks/compose-host'
|
|
7
7
|
import {HostBoundary} from '../../hooks/host-context'
|
|
8
8
|
import type {ScreenComponent, ScreenProps} from './screen.types'
|
|
9
9
|
|
|
@@ -18,7 +18,6 @@ export const Screen: ScreenComponent = ({
|
|
|
18
18
|
ignoreSafeArea = 'none',
|
|
19
19
|
testID,
|
|
20
20
|
}: ScreenProps) => {
|
|
21
|
-
const theme = useAppTheme()
|
|
22
21
|
const frame = useRef<View>(null)
|
|
23
22
|
const [statusBarOverlap, setStatusBarOverlap] = useState(0)
|
|
24
23
|
const sx = useSxModifiers({bg, tall: true, wide: true})
|
|
@@ -35,16 +34,14 @@ export const Screen: ScreenComponent = ({
|
|
|
35
34
|
|
|
36
35
|
return (
|
|
37
36
|
<View onLayout={onLayout} ref={frame} style={StyleSheet.absoluteFill} testID={testID}>
|
|
38
|
-
<
|
|
39
|
-
colorScheme={theme.dark ? 'dark' : 'light'}
|
|
37
|
+
<ComposeHost
|
|
40
38
|
ignoreSafeAreaKeyboardInsets={ignoreSafeArea === 'all' || ignoreSafeArea === 'keyboard'}
|
|
41
|
-
seedColor={theme.materialSeed}
|
|
42
39
|
style={StyleSheet.absoluteFill}
|
|
43
40
|
>
|
|
44
41
|
<Column modifiers={modifiers}>
|
|
45
42
|
<HostBoundary>{children}</HostBoundary>
|
|
46
43
|
</Column>
|
|
47
|
-
</
|
|
44
|
+
</ComposeHost>
|
|
48
45
|
</View>
|
|
49
46
|
)
|
|
50
47
|
}
|