@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/lib/module/atoms/activity-indicator/activity-indicator.android.js +3 -4
  3. package/lib/module/atoms/activity-indicator/activity-indicator.android.js.map +1 -1
  4. package/lib/module/atoms/date-picker/date-picker.android.js +3 -4
  5. package/lib/module/atoms/date-picker/date-picker.android.js.map +1 -1
  6. package/lib/module/atoms/island/island.android.js +2 -6
  7. package/lib/module/atoms/island/island.android.js.map +1 -1
  8. package/lib/module/atoms/picker/picker.android.js +3 -4
  9. package/lib/module/atoms/picker/picker.android.js.map +1 -1
  10. package/lib/module/atoms/pressable/pressable.js +5 -4
  11. package/lib/module/atoms/pressable/pressable.js.map +1 -1
  12. package/lib/module/atoms/segmented-control/segmented-control.android.js +3 -4
  13. package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -1
  14. package/lib/module/hooks/compose-host.js +27 -0
  15. package/lib/module/hooks/compose-host.js.map +1 -0
  16. package/lib/module/molecules/navigation-bar/bar-search-field.js +80 -0
  17. package/lib/module/molecules/navigation-bar/bar-search-field.js.map +1 -0
  18. package/lib/module/molecules/navigation-bar/navigation-bar.android.js +94 -22
  19. package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
  20. package/lib/module/molecules/rn-list-item/rn-list-item.js +3 -2
  21. package/lib/module/molecules/rn-list-item/rn-list-item.js.map +1 -1
  22. package/lib/module/molecules/rn-tooltip/rn-tooltip.android.js +3 -4
  23. package/lib/module/molecules/rn-tooltip/rn-tooltip.android.js.map +1 -1
  24. package/lib/module/organisms/context-menu/context-menu.android.js +4 -5
  25. package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
  26. package/lib/module/organisms/header/header-options.android.js +2 -6
  27. package/lib/module/organisms/header/header-options.android.js.map +1 -1
  28. package/lib/module/organisms/header/header-search-accessory.android.js +29 -2
  29. package/lib/module/organisms/header/header-search-accessory.android.js.map +1 -1
  30. package/lib/module/organisms/header/header-search-input.android.js +11 -51
  31. package/lib/module/organisms/header/header-search-input.android.js.map +1 -1
  32. package/lib/module/organisms/header/use-header-search.js +19 -11
  33. package/lib/module/organisms/header/use-header-search.js.map +1 -1
  34. package/lib/module/organisms/sheet/sheet.js +17 -1
  35. package/lib/module/organisms/sheet/sheet.js.map +1 -1
  36. package/lib/module/organisms/sheet/use-sheet-header.js +7 -1
  37. package/lib/module/organisms/sheet/use-sheet-header.js.map +1 -1
  38. package/lib/module/templates/screen/screen.android.js +3 -6
  39. package/lib/module/templates/screen/screen.android.js.map +1 -1
  40. package/lib/typescript/hooks/compose-host.d.ts +8 -0
  41. package/lib/typescript/molecules/navigation-bar/bar-search-field.d.ts +29 -0
  42. package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -6
  43. package/lib/typescript/organisms/header/header.types.d.ts +20 -10
  44. package/lib/typescript/organisms/header/use-header-search.d.ts +5 -3
  45. package/lib/typescript/organisms/sheet/sheet.types.d.ts +1 -1
  46. package/package.json +4 -4
  47. package/src/atoms/activity-indicator/activity-indicator.android.tsx +3 -6
  48. package/src/atoms/date-picker/date-picker.android.tsx +3 -12
  49. package/src/atoms/island/island.android.tsx +3 -7
  50. package/src/atoms/picker/picker.android.tsx +3 -3
  51. package/src/atoms/pressable/pressable.tsx +5 -7
  52. package/src/atoms/segmented-control/segmented-control.android.tsx +3 -6
  53. package/src/hooks/compose-host.tsx +20 -0
  54. package/src/molecules/navigation-bar/bar-search-field.tsx +94 -0
  55. package/src/molecules/navigation-bar/navigation-bar.android.tsx +119 -19
  56. package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -6
  57. package/src/molecules/rn-list-item/rn-list-item.tsx +2 -1
  58. package/src/molecules/rn-tooltip/rn-tooltip.android.tsx +4 -3
  59. package/src/organisms/context-menu/context-menu.android.tsx +5 -9
  60. package/src/organisms/header/header-options.android.tsx +3 -7
  61. package/src/organisms/header/header-search-accessory.android.tsx +37 -3
  62. package/src/organisms/header/header-search-input.android.tsx +14 -53
  63. package/src/organisms/header/header.types.ts +20 -10
  64. package/src/organisms/header/use-header-search.tsx +18 -10
  65. package/src/organisms/sheet/sheet.tsx +19 -2
  66. package/src/organisms/sheet/sheet.types.ts +1 -1
  67. package/src/organisms/sheet/use-sheet-header.tsx +7 -1
  68. 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 cancel button,
23
- * as a row for the screen to put right under the top app bar (`searchInput`). Both report the `query`; `scrim` washes
24
- * the content out while the field is focused and empty and, on iOS, carries the field's glyphs and voice.
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 || Platform.OS !== 'ios') {
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 || Platform.OS !== 'android') {
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="s16">
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, Host} from '@expo/ui/jetpack-compose'
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
- <Host
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
- </Host>
44
+ </ComposeHost>
48
45
  </View>
49
46
  )
50
47
  }