@hzblj/kara-components 0.6.0 → 0.8.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/CHANGELOG.md +91 -0
- package/README.md +31 -3
- package/lib/module/atoms/activity-indicator/activity-indicator.android.js +42 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.android.js.map +1 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.ios.js +19 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.ios.js.map +1 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.types.js +4 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.types.js.map +1 -0
- package/lib/module/atoms/activity-indicator/index.js +2 -1
- package/lib/module/atoms/activity-indicator/index.js.map +1 -1
- package/lib/module/atoms/avatar/avatar.js +2 -4
- package/lib/module/atoms/avatar/avatar.js.map +1 -1
- package/lib/module/atoms/blur-view/blur-view.android.js +100 -0
- package/lib/module/atoms/blur-view/blur-view.android.js.map +1 -0
- package/lib/module/atoms/blur-view/blur-view.ios.js +43 -0
- package/lib/module/atoms/blur-view/blur-view.ios.js.map +1 -0
- package/lib/module/atoms/blur-view/blur-view.types.js +4 -0
- package/lib/module/atoms/blur-view/blur-view.types.js.map +1 -0
- package/lib/module/atoms/blur-view/index.js +5 -0
- package/lib/module/atoms/blur-view/index.js.map +1 -0
- package/lib/module/atoms/checkbox/checkbox.android.js +7 -3
- package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
- package/lib/module/atoms/date-picker/date-picker.ios.js +32 -11
- package/lib/module/atoms/date-picker/date-picker.ios.js.map +1 -1
- package/lib/module/atoms/glass-view/glass-view.android.js +36 -5
- package/lib/module/atoms/glass-view/glass-view.android.js.map +1 -1
- package/lib/module/atoms/image/image.js +13 -6
- package/lib/module/atoms/image/image.js.map +1 -1
- package/lib/module/atoms/image/image.types.js +4 -0
- package/lib/module/atoms/image/image.types.js.map +1 -0
- package/lib/module/atoms/image/index.js +1 -0
- package/lib/module/atoms/image/index.js.map +1 -1
- package/lib/module/atoms/index.js +1 -1
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/atoms/island/island.android.js +6 -2
- package/lib/module/atoms/island/island.android.js.map +1 -1
- package/lib/module/atoms/island/island.ios.js +5 -2
- package/lib/module/atoms/island/island.ios.js.map +1 -1
- package/lib/module/atoms/pressable/pressable.js +38 -6
- package/lib/module/atoms/pressable/pressable.js.map +1 -1
- package/lib/module/atoms/rn-flat-list/rn-flat-list.js +10 -9
- package/lib/module/atoms/rn-flat-list/rn-flat-list.js.map +1 -1
- package/lib/module/atoms/rn-section-list/rn-section-list.js +5 -8
- package/lib/module/atoms/rn-section-list/rn-section-list.js.map +1 -1
- package/lib/module/atoms/rn-text/rn-text.js +5 -2
- package/lib/module/atoms/rn-text/rn-text.js.map +1 -1
- package/lib/module/atoms/segmented-control/index.js +1 -1
- package/lib/module/atoms/segmented-control/index.js.map +1 -1
- package/lib/module/atoms/segmented-control/segmented-control.android.js +74 -0
- package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -0
- package/lib/module/atoms/segmented-control/segmented-control.ios.js +44 -0
- package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -0
- package/lib/module/atoms/skeleton/skeleton.js +5 -5
- package/lib/module/atoms/skeleton/skeleton.js.map +1 -1
- package/lib/module/atoms/switch/switch.android.js +4 -1
- package/lib/module/atoms/switch/switch.android.js.map +1 -1
- package/lib/module/atoms/text/text.android.js +5 -2
- package/lib/module/atoms/text/text.android.js.map +1 -1
- package/lib/module/atoms/text/text.ios.js +14 -3
- package/lib/module/atoms/text/text.ios.js.map +1 -1
- package/lib/module/molecules/collapsible/collapsible.android.js +37 -7
- package/lib/module/molecules/collapsible/collapsible.android.js.map +1 -1
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js +2 -8
- package/lib/module/molecules/navigation-bar/navigation-bar.android.js.map +1 -1
- package/lib/module/molecules/pager/pager.android.js +5 -4
- package/lib/module/molecules/pager/pager.android.js.map +1 -1
- package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js +114 -11
- package/lib/module/molecules/scroll-edge-container/scroll-edge-container.android.js.map +1 -1
- package/lib/module/molecules/search-bar/search-bar.js +15 -2
- package/lib/module/molecules/search-bar/search-bar.js.map +1 -1
- package/lib/module/molecules/stepper/stepper.ios.js +45 -8
- package/lib/module/molecules/stepper/stepper.ios.js.map +1 -1
- package/lib/module/molecules/tooltip/tooltip.android.js +16 -5
- package/lib/module/molecules/tooltip/tooltip.android.js.map +1 -1
- package/lib/module/organisms/action-sheet/action-sheet.android.js +41 -20
- package/lib/module/organisms/action-sheet/action-sheet.android.js.map +1 -1
- package/lib/module/organisms/action-sheet/action-sheet.ios.js +24 -8
- package/lib/module/organisms/action-sheet/action-sheet.ios.js.map +1 -1
- package/lib/module/organisms/alert/alert.ios.js +15 -2
- package/lib/module/organisms/alert/alert.ios.js.map +1 -1
- package/lib/module/organisms/form/form.android.js +22 -9
- package/lib/module/organisms/form/form.android.js.map +1 -1
- package/lib/module/organisms/swipe-actions/swipe-actions.android.js +125 -11
- package/lib/module/organisms/swipe-actions/swipe-actions.android.js.map +1 -1
- package/lib/module/organisms/swipe-actions/swipe-actions.ios.js +46 -18
- package/lib/module/organisms/swipe-actions/swipe-actions.ios.js.map +1 -1
- package/lib/module/ripple-clip.js +9 -1
- package/lib/module/ripple-clip.js.map +1 -1
- package/lib/module/templates/screen/screen.android.js +31 -10
- package/lib/module/templates/screen/screen.android.js.map +1 -1
- package/lib/typescript/atoms/activity-indicator/activity-indicator.d.ts +1 -3
- package/lib/typescript/atoms/activity-indicator/activity-indicator.ios.d.ts +3 -0
- package/lib/typescript/atoms/activity-indicator/activity-indicator.types.d.ts +17 -0
- package/lib/typescript/atoms/activity-indicator/index.d.ts +1 -0
- package/lib/typescript/atoms/avatar/avatar.types.d.ts +5 -0
- package/lib/typescript/atoms/badge/badge.types.d.ts +3 -0
- package/lib/typescript/atoms/blur-view/blur-view.d.ts +1 -0
- package/lib/typescript/atoms/blur-view/blur-view.ios.d.ts +3 -0
- package/lib/typescript/atoms/blur-view/blur-view.types.d.ts +31 -0
- package/lib/typescript/atoms/blur-view/index.d.ts +2 -0
- package/lib/typescript/atoms/box/a-box.d.ts +1 -1
- package/lib/typescript/atoms/button/button.types.d.ts +12 -0
- package/lib/typescript/atoms/card/card.types.d.ts +6 -0
- package/lib/typescript/atoms/checkbox/checkbox.types.d.ts +5 -0
- package/lib/typescript/atoms/chip/chip.types.d.ts +5 -0
- package/lib/typescript/atoms/color-picker/color-picker.types.d.ts +4 -0
- package/lib/typescript/atoms/date-picker/date-picker.ios.d.ts +6 -1
- package/lib/typescript/atoms/date-picker/date-picker.types.d.ts +14 -0
- package/lib/typescript/atoms/divider/divider.types.d.ts +3 -0
- package/lib/typescript/atoms/fab/fab.types.d.ts +7 -0
- package/lib/typescript/atoms/glass-view/glass-view.types.d.ts +13 -5
- package/lib/typescript/atoms/h-stack/h-stack.types.d.ts +6 -0
- package/lib/typescript/atoms/icon/icon.types.d.ts +8 -0
- package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +5 -0
- package/lib/typescript/atoms/image/image.d.ts +12 -4
- package/lib/typescript/atoms/image/image.types.d.ts +4 -0
- package/lib/typescript/atoms/image/index.d.ts +1 -0
- package/lib/typescript/atoms/index.d.ts +1 -1
- package/lib/typescript/atoms/island/island.types.d.ts +10 -2
- package/lib/typescript/atoms/list/list.types.d.ts +2 -0
- package/lib/typescript/atoms/picker/picker.types.d.ts +12 -0
- package/lib/typescript/atoms/pressable/pressable.types.d.ts +10 -0
- package/lib/typescript/atoms/progress/progress.types.d.ts +6 -0
- package/lib/typescript/atoms/rn-flat-list/rn-flat-list.d.ts +5 -8
- package/lib/typescript/atoms/rn-flat-list/rn-flat-list.types.d.ts +3 -1
- package/lib/typescript/atoms/rn-scroll-view/rn-scroll-view.types.d.ts +4 -0
- package/lib/typescript/atoms/rn-section-list/rn-section-list.d.ts +2 -8
- package/lib/typescript/atoms/rn-section-list/rn-section-list.types.d.ts +3 -1
- package/lib/typescript/atoms/rn-text/rn-text.types.d.ts +10 -1
- package/lib/typescript/atoms/rn-view/rn-view.types.d.ts +2 -0
- package/lib/typescript/atoms/scroll-view/scroll-view.types.d.ts +4 -0
- package/lib/typescript/atoms/segmented-control/segmented-control.d.ts +1 -6
- package/lib/typescript/atoms/segmented-control/segmented-control.ios.d.ts +7 -0
- package/lib/typescript/atoms/segmented-control/segmented-control.types.d.ts +13 -0
- package/lib/typescript/atoms/skeleton/skeleton.types.d.ts +11 -2
- package/lib/typescript/atoms/slider/slider.types.d.ts +7 -0
- package/lib/typescript/atoms/spacer/spacer.types.d.ts +3 -0
- package/lib/typescript/atoms/surface/surface.types.d.ts +7 -0
- package/lib/typescript/atoms/switch/switch.types.d.ts +8 -0
- package/lib/typescript/atoms/text/text.types.d.ts +18 -1
- package/lib/typescript/atoms/text-field/text-field.types.d.ts +12 -0
- package/lib/typescript/atoms/v-stack/v-stack.types.d.ts +9 -0
- package/lib/typescript/atoms/z-stack/z-stack.types.d.ts +8 -0
- package/lib/typescript/molecules/collapsible/collapsible.types.d.ts +8 -0
- package/lib/typescript/molecules/empty-state/empty-state.types.d.ts +5 -0
- package/lib/typescript/molecules/label/label.types.d.ts +6 -0
- package/lib/typescript/molecules/labeled-content/labeled-content.types.d.ts +6 -0
- package/lib/typescript/molecules/link/link.types.d.ts +6 -0
- package/lib/typescript/molecules/list-item/list-item.types.d.ts +9 -0
- package/lib/typescript/molecules/navigation-bar/navigation-bar.types.d.ts +8 -0
- package/lib/typescript/molecules/pager/pager.types.d.ts +2 -0
- package/lib/typescript/molecules/radio-group/radio-group.types.d.ts +10 -0
- package/lib/typescript/molecules/rn-list-item/rn-list-item.types.d.ts +1 -1
- package/lib/typescript/molecules/rn-tooltip/rn-tooltip.types.d.ts +11 -0
- package/lib/typescript/molecules/scroll-edge-container/scroll-edge-container.types.d.ts +7 -2
- package/lib/typescript/molecules/search-bar/search-bar.types.d.ts +8 -0
- package/lib/typescript/molecules/stepper/stepper.ios.d.ts +5 -0
- package/lib/typescript/molecules/stepper/stepper.types.d.ts +10 -0
- package/lib/typescript/molecules/tooltip/tooltip.types.d.ts +12 -0
- package/lib/typescript/organisms/action-sheet/action-sheet.types.d.ts +8 -0
- package/lib/typescript/organisms/alert/alert.types.d.ts +11 -0
- package/lib/typescript/organisms/context-menu/context-menu.types.d.ts +4 -0
- package/lib/typescript/organisms/fab-menu/fab-menu.types.d.ts +2 -0
- package/lib/typescript/organisms/form/form.types.d.ts +6 -0
- package/lib/typescript/organisms/menu/menu.types.d.ts +17 -0
- package/lib/typescript/organisms/sheet/sheet.types.d.ts +19 -0
- package/lib/typescript/organisms/snackbar/snackbar.types.d.ts +8 -0
- package/lib/typescript/organisms/swipe-actions/swipe-actions.types.d.ts +36 -13
- package/lib/typescript/templates/screen/screen.types.d.ts +9 -0
- package/package.json +5 -7
- package/src/atoms/activity-indicator/activity-indicator.android.tsx +32 -0
- package/src/atoms/activity-indicator/activity-indicator.ios.tsx +15 -0
- package/src/atoms/activity-indicator/activity-indicator.types.ts +20 -0
- package/src/atoms/activity-indicator/index.ts +1 -0
- package/src/atoms/avatar/avatar.tsx +1 -1
- package/src/atoms/avatar/avatar.types.ts +5 -0
- package/src/atoms/badge/badge.types.ts +3 -0
- package/src/atoms/blur-view/blur-view.android.tsx +68 -0
- package/src/atoms/blur-view/blur-view.ios.tsx +37 -0
- package/src/atoms/blur-view/blur-view.types.ts +34 -0
- package/src/atoms/blur-view/index.ts +2 -0
- package/src/atoms/button/button.types.ts +12 -0
- package/src/atoms/card/card.types.ts +6 -0
- package/src/atoms/checkbox/checkbox.android.tsx +7 -3
- package/src/atoms/checkbox/checkbox.types.ts +5 -0
- package/src/atoms/chip/chip.types.ts +5 -0
- package/src/atoms/color-picker/color-picker.types.ts +4 -0
- package/src/atoms/date-picker/date-picker.ios.tsx +33 -13
- package/src/atoms/date-picker/date-picker.types.ts +14 -0
- package/src/atoms/divider/divider.types.ts +3 -0
- package/src/atoms/fab/fab.types.ts +7 -0
- package/src/atoms/glass-view/glass-view.android.tsx +41 -3
- package/src/atoms/glass-view/glass-view.types.ts +13 -5
- package/src/atoms/h-stack/h-stack.types.ts +6 -0
- package/src/atoms/icon/icon.types.ts +8 -0
- package/src/atoms/icon-button/icon-button.types.ts +5 -0
- package/src/atoms/image/image.tsx +11 -12
- package/src/atoms/image/image.types.ts +6 -0
- package/src/atoms/image/index.ts +1 -0
- package/src/atoms/index.ts +1 -1
- package/src/atoms/island/island.android.tsx +10 -2
- package/src/atoms/island/island.ios.tsx +4 -2
- package/src/atoms/island/island.types.ts +10 -2
- package/src/atoms/list/list.types.ts +2 -0
- package/src/atoms/picker/picker.types.ts +12 -0
- package/src/atoms/pressable/pressable.tsx +46 -6
- package/src/atoms/pressable/pressable.types.ts +10 -0
- package/src/atoms/progress/progress.types.ts +6 -0
- package/src/atoms/rn-flat-list/rn-flat-list.tsx +8 -8
- package/src/atoms/rn-flat-list/rn-flat-list.types.ts +3 -1
- package/src/atoms/rn-scroll-view/rn-scroll-view.types.ts +4 -0
- package/src/atoms/rn-section-list/rn-section-list.tsx +5 -8
- package/src/atoms/rn-section-list/rn-section-list.types.ts +3 -1
- package/src/atoms/rn-text/rn-text.tsx +6 -3
- package/src/atoms/rn-text/rn-text.types.ts +15 -5
- package/src/atoms/rn-view/rn-view.types.ts +2 -0
- package/src/atoms/scroll-view/scroll-view.types.ts +4 -0
- package/src/atoms/segmented-control/segmented-control.android.tsx +79 -0
- package/src/atoms/segmented-control/segmented-control.ios.tsx +47 -0
- package/src/atoms/segmented-control/segmented-control.types.ts +13 -0
- package/src/atoms/skeleton/skeleton.tsx +4 -4
- package/src/atoms/skeleton/skeleton.types.ts +11 -2
- package/src/atoms/slider/slider.types.ts +7 -0
- package/src/atoms/spacer/spacer.types.ts +3 -0
- package/src/atoms/surface/surface.types.ts +7 -0
- package/src/atoms/switch/switch.android.tsx +4 -1
- package/src/atoms/switch/switch.types.ts +8 -0
- package/src/atoms/text/text.android.tsx +7 -1
- package/src/atoms/text/text.ios.tsx +14 -3
- package/src/atoms/text/text.types.ts +18 -1
- package/src/atoms/text-field/text-field.types.ts +12 -0
- package/src/atoms/v-stack/v-stack.types.ts +9 -0
- package/src/atoms/z-stack/z-stack.types.ts +8 -0
- package/src/molecules/collapsible/collapsible.android.tsx +47 -7
- package/src/molecules/collapsible/collapsible.types.ts +8 -0
- package/src/molecules/empty-state/empty-state.types.ts +5 -0
- package/src/molecules/label/label.types.ts +6 -0
- package/src/molecules/labeled-content/labeled-content.types.ts +6 -0
- package/src/molecules/link/link.types.ts +6 -0
- package/src/molecules/list-item/list-item.types.ts +9 -0
- package/src/molecules/navigation-bar/navigation-bar.android.tsx +1 -6
- package/src/molecules/navigation-bar/navigation-bar.types.ts +8 -0
- package/src/molecules/pager/pager.android.tsx +4 -1
- package/src/molecules/pager/pager.types.ts +2 -0
- package/src/molecules/radio-group/radio-group.types.ts +10 -0
- package/src/molecules/rn-list-item/rn-list-item.types.ts +1 -1
- package/src/molecules/rn-tooltip/rn-tooltip.types.ts +11 -0
- package/src/molecules/scroll-edge-container/scroll-edge-container.android.tsx +103 -10
- package/src/molecules/scroll-edge-container/scroll-edge-container.types.ts +7 -2
- package/src/molecules/search-bar/search-bar.tsx +9 -2
- package/src/molecules/search-bar/search-bar.types.ts +8 -0
- package/src/molecules/stepper/stepper.ios.tsx +37 -1
- package/src/molecules/stepper/stepper.types.ts +10 -0
- package/src/molecules/tooltip/tooltip.android.tsx +17 -9
- package/src/molecules/tooltip/tooltip.types.ts +12 -0
- package/src/organisms/action-sheet/action-sheet.android.tsx +37 -19
- package/src/organisms/action-sheet/action-sheet.ios.tsx +22 -9
- package/src/organisms/action-sheet/action-sheet.types.ts +8 -0
- package/src/organisms/alert/alert.ios.tsx +11 -1
- package/src/organisms/alert/alert.types.ts +11 -0
- package/src/organisms/context-menu/context-menu.types.ts +4 -0
- package/src/organisms/fab-menu/fab-menu.types.ts +2 -0
- package/src/organisms/form/form.android.tsx +15 -7
- package/src/organisms/form/form.types.ts +6 -0
- package/src/organisms/menu/menu.types.ts +17 -0
- package/src/organisms/sheet/sheet.types.ts +19 -0
- package/src/organisms/snackbar/snackbar.types.ts +8 -0
- package/src/organisms/swipe-actions/swipe-actions.android.tsx +129 -9
- package/src/organisms/swipe-actions/swipe-actions.ios.tsx +45 -11
- package/src/organisms/swipe-actions/swipe-actions.types.ts +37 -14
- package/src/ripple-clip.ts +10 -1
- package/src/templates/screen/screen.android.tsx +33 -12
- package/src/templates/screen/screen.types.ts +9 -0
- package/lib/module/atoms/activity-indicator/activity-indicator.js +0 -14
- package/lib/module/atoms/activity-indicator/activity-indicator.js.map +0 -1
- package/lib/module/atoms/flash-list/flash-list.js +0 -31
- package/lib/module/atoms/flash-list/flash-list.js.map +0 -1
- package/lib/module/atoms/flash-list/flash-list.types.js +0 -4
- package/lib/module/atoms/flash-list/flash-list.types.js.map +0 -1
- package/lib/module/atoms/flash-list/index.js +0 -5
- package/lib/module/atoms/flash-list/index.js.map +0 -1
- package/lib/module/atoms/segmented-control/segmented-control.js +0 -40
- package/lib/module/atoms/segmented-control/segmented-control.js.map +0 -1
- package/lib/typescript/atoms/flash-list/flash-list.d.ts +0 -8
- package/lib/typescript/atoms/flash-list/flash-list.types.d.ts +0 -18
- package/lib/typescript/atoms/flash-list/index.d.ts +0 -2
- package/src/atoms/activity-indicator/activity-indicator.tsx +0 -9
- package/src/atoms/flash-list/flash-list.tsx +0 -28
- package/src/atoms/flash-list/flash-list.types.ts +0 -20
- package/src/atoms/flash-list/index.ts +0 -2
- package/src/atoms/segmented-control/segmented-control.tsx +0 -47
|
@@ -3,10 +3,18 @@ import type {ReactNode} from 'react'
|
|
|
3
3
|
|
|
4
4
|
export type ZStackAlign = 'bottom' | 'center' | 'top'
|
|
5
5
|
|
|
6
|
+
/** Layers its children on one another — SwiftUI `ZStack` / Compose `Box`. */
|
|
6
7
|
export type ZStackProps = ResponsiveSx & {
|
|
8
|
+
/** Where the children sit vertically; they are always centred horizontally. `center` by default. */
|
|
7
9
|
align?: ZStackAlign
|
|
10
|
+
/** Drawn in order, the last on top. */
|
|
8
11
|
children?: ReactNode
|
|
12
|
+
/**
|
|
13
|
+
* Makes the whole stack pressable. iOS adds a tap gesture with no press feedback; Android a `clickable` whose ripple
|
|
14
|
+
* fills the stack's shape.
|
|
15
|
+
*/
|
|
9
16
|
onPress?: () => void
|
|
17
|
+
/** Test identifier, forwarded to the native view. */
|
|
10
18
|
testID?: string
|
|
11
19
|
}
|
|
12
20
|
|
|
@@ -1,25 +1,65 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import {
|
|
2
|
+
AnimatedVisibility,
|
|
3
|
+
Box,
|
|
4
|
+
Column,
|
|
5
|
+
Text as ComposeText,
|
|
6
|
+
EnterTransition,
|
|
7
|
+
ExitTransition,
|
|
8
|
+
Row,
|
|
9
|
+
} from '@expo/ui/jetpack-compose'
|
|
10
|
+
import {
|
|
11
|
+
animated,
|
|
12
|
+
clickable,
|
|
13
|
+
defaultMinSize,
|
|
14
|
+
fillMaxWidth,
|
|
15
|
+
graphicsLayer,
|
|
16
|
+
tween,
|
|
17
|
+
} from '@expo/ui/jetpack-compose/modifiers'
|
|
18
|
+
import {duration, useAppTheme} from '@hzblj/kara-theme'
|
|
4
19
|
import {useMemo} from 'react'
|
|
5
20
|
import {Glyph} from '../../glyph'
|
|
6
21
|
import {useHostGuard} from '../../host-context'
|
|
7
22
|
import type {CollapsibleComponent, CollapsibleProps} from './collapsible.types'
|
|
8
23
|
|
|
24
|
+
/** Material's standard easing, at the length a disclosure takes. */
|
|
25
|
+
const TURN = tween({durationMillis: duration.d200, easing: 'fastOutSlowIn'})
|
|
26
|
+
|
|
27
|
+
/** Down from the row, fading in — Compose's default grows from a corner, sideways as well as down. */
|
|
28
|
+
const ENTER = EnterTransition.expandVertically().plus(EnterTransition.fadeIn())
|
|
29
|
+
const EXIT = ExitTransition.shrinkVertically().plus(ExitTransition.fadeOut())
|
|
30
|
+
|
|
31
|
+
const WIDE = [fillMaxWidth()]
|
|
32
|
+
|
|
33
|
+
/** Material's least touch target. */
|
|
34
|
+
const ROW_HEIGHT = 48
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A pressable row and an animated reveal: the content expands down from the row, and the chevron turns a quarter
|
|
38
|
+
* toward it, as a Material disclosure does — it used to jump between two glyphs. The row spans its parent, the chevron
|
|
39
|
+
* at the far end, and is never shorter than a touch target.
|
|
40
|
+
*/
|
|
9
41
|
export const Collapsible: CollapsibleComponent = ({children, expanded, label, onExpandedChange}: CollapsibleProps) => {
|
|
10
42
|
useHostGuard('Collapsible')
|
|
11
43
|
const theme = useAppTheme()
|
|
12
|
-
const modifiers = useMemo(
|
|
44
|
+
const modifiers = useMemo(
|
|
45
|
+
() => [fillMaxWidth(), defaultMinSize({minHeight: ROW_HEIGHT}), clickable(() => onExpandedChange(!expanded))],
|
|
46
|
+
[expanded, onExpandedChange]
|
|
47
|
+
)
|
|
48
|
+
const turn = useMemo(() => [graphicsLayer({rotationZ: animated(expanded ? 90 : 0, TURN)})], [expanded])
|
|
13
49
|
|
|
14
50
|
return (
|
|
15
|
-
<Column>
|
|
51
|
+
<Column modifiers={WIDE}>
|
|
16
52
|
<Row horizontalArrangement="spaceBetween" modifiers={modifiers} verticalAlignment="center">
|
|
17
53
|
<ComposeText color={theme.colorString('content.primary')} style={theme.typography.body}>
|
|
18
54
|
{label}
|
|
19
55
|
</ComposeText>
|
|
20
|
-
<
|
|
56
|
+
<Box modifiers={turn}>
|
|
57
|
+
<Glyph name="chevronRight" size="s20" />
|
|
58
|
+
</Box>
|
|
21
59
|
</Row>
|
|
22
|
-
<AnimatedVisibility visible={expanded}>
|
|
60
|
+
<AnimatedVisibility enterTransition={ENTER} exitTransition={EXIT} visible={expanded}>
|
|
61
|
+
{children}
|
|
62
|
+
</AnimatedVisibility>
|
|
23
63
|
</Column>
|
|
24
64
|
)
|
|
25
65
|
}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A row that shows and hides its content: SwiftUI's `DisclosureGroup` on iOS; on Android a pressable row with a
|
|
5
|
+
* chevron over an animated reveal. A native atom — it renders inside `<Screen>` or `<Island>`.
|
|
6
|
+
*/
|
|
3
7
|
export type CollapsibleProps = {
|
|
8
|
+
/** Shown while `expanded`. */
|
|
4
9
|
children: ReactNode
|
|
10
|
+
/** Whether the content is shown. Update it from `onExpandedChange`. */
|
|
5
11
|
expanded: boolean
|
|
12
|
+
/** The row's text. */
|
|
6
13
|
label: string
|
|
14
|
+
/** The user pressed the row; it passes the state the row asks for. */
|
|
7
15
|
onExpandedChange: (expanded: boolean) => void
|
|
8
16
|
}
|
|
9
17
|
|
|
@@ -6,10 +6,15 @@ import type {IconName} from '../../atoms/icon'
|
|
|
6
6
|
* `action` takes a `Button` so the state can offer a way out.
|
|
7
7
|
*/
|
|
8
8
|
export type EmptyStateProps = {
|
|
9
|
+
/** Under the text — a `Button`, say. */
|
|
9
10
|
action?: ReactNode
|
|
11
|
+
/** A second line under the title, in the secondary colour. */
|
|
10
12
|
description?: string
|
|
13
|
+
/** One of the app's icons, large and muted above the title. */
|
|
11
14
|
icon?: IconName
|
|
15
|
+
/** Test identifier, forwarded to the native view. */
|
|
12
16
|
testID?: string
|
|
17
|
+
/** The headline. */
|
|
13
18
|
title: string
|
|
14
19
|
}
|
|
15
20
|
|
|
@@ -8,11 +8,17 @@ import type {IconName} from '../../atoms/icon'
|
|
|
8
8
|
* header. For a full tappable row with a subtitle and chevron, use `ListItem`.
|
|
9
9
|
*/
|
|
10
10
|
export type LabelProps = {
|
|
11
|
+
/** Colours the icon and the title alike. `content.primary` by default. */
|
|
11
12
|
color?: ColorToken
|
|
13
|
+
/** One of the app's icons, before the title. */
|
|
12
14
|
icon: IconName
|
|
15
|
+
/** The icon's size. `s20` by default. */
|
|
13
16
|
iconSize?: SpaceSize
|
|
17
|
+
/** Test identifier, forwarded to the native view. */
|
|
14
18
|
testID?: string
|
|
19
|
+
/** The text beside the icon. */
|
|
15
20
|
title: string
|
|
21
|
+
/** The title's text style. `body` by default. */
|
|
16
22
|
variant?: TextVariant
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -7,13 +7,19 @@ import type {IconName} from '../../atoms/icon'
|
|
|
7
7
|
* Pass `children` instead of `value` when the trailing side is a control rather than text.
|
|
8
8
|
*/
|
|
9
9
|
export type LabeledContentProps = {
|
|
10
|
+
/** The trailing side when it is a control, in place of `value`. */
|
|
10
11
|
children?: ReactNode
|
|
12
|
+
/** One of the app's icons, before the label, in the secondary colour. */
|
|
11
13
|
icon?: IconName
|
|
14
|
+
/** The leading text. */
|
|
12
15
|
label: string
|
|
16
|
+
/** Makes the row pressable and adds a chevron on the trailing edge. */
|
|
13
17
|
onPress?: () => void
|
|
14
18
|
/** Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. */
|
|
15
19
|
px?: SpaceSize
|
|
20
|
+
/** Test identifier, forwarded to the native view. */
|
|
16
21
|
testID?: string
|
|
22
|
+
/** The trailing text, in the secondary colour. Ignored when `children` is given. */
|
|
17
23
|
value?: string
|
|
18
24
|
}
|
|
19
25
|
|
|
@@ -5,11 +5,17 @@ import type {IconName} from '../../atoms/icon'
|
|
|
5
5
|
|
|
6
6
|
/** Opens an external URL in the platform browser or the app that claims the scheme. */
|
|
7
7
|
export type LinkProps = {
|
|
8
|
+
/** Colours the label and the icon. `content.accent` by default. */
|
|
8
9
|
color?: ColorToken
|
|
10
|
+
/** One of the app's icons, before the label. */
|
|
9
11
|
icon?: IconName
|
|
12
|
+
/** The link's text. */
|
|
10
13
|
label: string
|
|
14
|
+
/** Test identifier, forwarded to the native view. */
|
|
11
15
|
testID?: string
|
|
16
|
+
/** Opened through React Native's `Linking` on press. A URL nothing can open fails silently. */
|
|
12
17
|
url: string
|
|
18
|
+
/** The label's text style. `body` by default. */
|
|
13
19
|
variant?: TextVariant
|
|
14
20
|
}
|
|
15
21
|
|
|
@@ -3,16 +3,25 @@ import type {ReactNode} from 'react'
|
|
|
3
3
|
|
|
4
4
|
import type {IconName} from '../../atoms/icon'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A row with an optional icon, a title and subtitle, and a chevron when it can be pressed. A native atom — it renders
|
|
8
|
+
* inside `<Screen>` or `<Island>`; `RnListItem` is the same row without a host.
|
|
9
|
+
*/
|
|
6
10
|
export type ListItemProps = {
|
|
11
|
+
/** One of the app's icons, before the title, in the secondary colour. */
|
|
7
12
|
icon?: IconName
|
|
13
|
+
/** Makes the row pressable and adds a chevron on the trailing edge. */
|
|
8
14
|
onPress?: () => void
|
|
9
15
|
/**
|
|
10
16
|
* Horizontal padding inside the row — inside its press area too, so Android's ripple reaches the card's edges. For
|
|
11
17
|
* rows in a card of your own; a `List` insets its rows by itself.
|
|
12
18
|
*/
|
|
13
19
|
px?: SpaceSize
|
|
20
|
+
/** A second line under the title, smaller and in the secondary colour. */
|
|
14
21
|
subtitle?: string
|
|
22
|
+
/** Test identifier, forwarded to the row's view. */
|
|
15
23
|
testID?: string
|
|
24
|
+
/** The row's main text. */
|
|
16
25
|
title: string
|
|
17
26
|
}
|
|
18
27
|
|
|
@@ -2,17 +2,12 @@ import {iconResourceName} from '@hzblj/kara-native'
|
|
|
2
2
|
import {TopAppBar, type TopAppBarItem} from '@hzblj/kara-native/jetpack-compose'
|
|
3
3
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
4
|
import {useMemo} from 'react'
|
|
5
|
-
import {StyleSheet} from 'react-native'
|
|
6
5
|
import {Island} from '../../atoms/island'
|
|
7
6
|
import type {NavigationBarComponent, NavigationBarItem, NavigationBarProps} from './navigation-bar.types'
|
|
8
7
|
|
|
9
8
|
/** Material's small top app bar. */
|
|
10
9
|
const BAR_HEIGHT = 64
|
|
11
10
|
|
|
12
|
-
const styles = StyleSheet.create({
|
|
13
|
-
bar: {height: BAR_HEIGHT, width: '100%'},
|
|
14
|
-
})
|
|
15
|
-
|
|
16
11
|
const DEFAULT_LABELS = {back: 'Back', close: 'Close'} as const
|
|
17
12
|
|
|
18
13
|
const toNativeItem = ({onPress, testID, ...item}: NavigationBarItem): TopAppBarItem =>
|
|
@@ -38,7 +33,7 @@ export const NavigationBar: NavigationBarComponent = ({
|
|
|
38
33
|
const nativeTrailingItems = useMemo(() => trailingItems?.map(toNativeItem), [trailingItems])
|
|
39
34
|
|
|
40
35
|
return (
|
|
41
|
-
<Island
|
|
36
|
+
<Island h={BAR_HEIGHT} matchContents={false} testID={testID} w="100%">
|
|
42
37
|
<TopAppBar
|
|
43
38
|
fontFamily={font.fontFamily}
|
|
44
39
|
fontSize={font.fontSize}
|
|
@@ -3,6 +3,7 @@ import type {ReactNode, Ref} from 'react'
|
|
|
3
3
|
import type {IconName} from '../../atoms/icon'
|
|
4
4
|
|
|
5
5
|
type NavigationBarItemBase = {
|
|
6
|
+
/** Called when the item is pressed. */
|
|
6
7
|
onPress: () => void
|
|
7
8
|
/**
|
|
8
9
|
* iOS 26: neighbouring items on one side share a glass capsule; this gives the item its own. A close next to
|
|
@@ -29,10 +30,15 @@ export type NavigationBarSearchRef = {
|
|
|
29
30
|
|
|
30
31
|
/** A search field in place of the title — iOS only for now; Android keeps the title. */
|
|
31
32
|
export type NavigationBarSearch = {
|
|
33
|
+
/** Every edit of the field's text. */
|
|
32
34
|
onChangeText: (text: string) => void
|
|
35
|
+
/** The field gained or lost focus. */
|
|
33
36
|
onFocusChange?: (focused: boolean) => void
|
|
37
|
+
/** Shown while the field is empty. */
|
|
34
38
|
placeholder: string
|
|
39
|
+
/** Imperative `clear`. */
|
|
35
40
|
ref?: Ref<NavigationBarSearchRef>
|
|
41
|
+
/** Test identifier, forwarded to the native search field. */
|
|
36
42
|
testID?: string
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -52,7 +58,9 @@ export type NavigationBarProps = {
|
|
|
52
58
|
leadingItems?: NavigationBarItem[]
|
|
53
59
|
/** Replaces the title with a search field (iOS). */
|
|
54
60
|
search?: NavigationBarSearch
|
|
61
|
+
/** Test identifier, forwarded to the native bar. iOS only — Android ignores it. */
|
|
55
62
|
testID?: string
|
|
63
|
+
/** The bar's title. */
|
|
56
64
|
title?: string
|
|
57
65
|
/** `center` by default — iOS's own; `leading` is Material's top app bar. */
|
|
58
66
|
titleAlignment?: NavigationBarTitleAlignment
|
|
@@ -4,15 +4,17 @@ import {spacing} from '@hzblj/kara-theme'
|
|
|
4
4
|
import {Children, useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
|
5
5
|
|
|
6
6
|
import {HStack} from '../../atoms/h-stack'
|
|
7
|
+
import {Spacer} from '../../atoms/spacer'
|
|
7
8
|
import {VStack} from '../../atoms/v-stack'
|
|
8
9
|
import {useHostGuard} from '../../host-context'
|
|
9
10
|
import type {PagerComponent, PagerProps} from './pager.types'
|
|
10
11
|
|
|
11
12
|
const DOT_SIZE = 6
|
|
12
13
|
|
|
13
|
-
/** Compose ships no page indicator, so the dots are composed out of atoms. */
|
|
14
|
+
/** Compose ships no page indicator, so the dots are composed out of atoms — centred between two spacers. */
|
|
14
15
|
const PagerDots = ({count, current}: {count: number; current: number}) => (
|
|
15
16
|
<HStack align="center" py="s12" spacing="s6" wide>
|
|
17
|
+
<Spacer />
|
|
16
18
|
{Array.from({length: count}, (_, index) => (
|
|
17
19
|
<VStack
|
|
18
20
|
bg={index === current ? 'content.primary' : 'fill.tertiary'}
|
|
@@ -22,6 +24,7 @@ const PagerDots = ({count, current}: {count: number; current: number}) => (
|
|
|
22
24
|
w={DOT_SIZE}
|
|
23
25
|
/>
|
|
24
26
|
))}
|
|
27
|
+
<Spacer />
|
|
25
28
|
</HStack>
|
|
26
29
|
)
|
|
27
30
|
|
|
@@ -11,6 +11,7 @@ import type {ReactNode} from 'react'
|
|
|
11
11
|
* ceremony would buy nothing.
|
|
12
12
|
*/
|
|
13
13
|
export type PagerProps = {
|
|
14
|
+
/** The pages, one per child, in order. */
|
|
14
15
|
children?: ReactNode
|
|
15
16
|
/**
|
|
16
17
|
* Page dots. iOS draws SwiftUI's own, over the bottom edge; Compose has no built-in indicator, so
|
|
@@ -28,6 +29,7 @@ export type PagerProps = {
|
|
|
28
29
|
* content down with the swipe, so iOS always stays swipeable.
|
|
29
30
|
*/
|
|
30
31
|
scrollEnabled?: boolean
|
|
32
|
+
/** Test identifier, forwarded to the native view. */
|
|
31
33
|
testID?: string
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/** One choice of a `RadioGroup`. */
|
|
3
4
|
export type RadioOption = {
|
|
5
|
+
/** The option's text. */
|
|
4
6
|
label: string
|
|
7
|
+
/** Identifies the option to `value` and `onValueChange`, and keys its row — keep it unique. */
|
|
5
8
|
value: string
|
|
6
9
|
}
|
|
7
10
|
|
|
11
|
+
/**
|
|
12
|
+
* A single choice from a list: rows with a checkmark on the selected one on iOS, Material radio buttons on Android. A
|
|
13
|
+
* native atom — it renders inside `<Screen>` or `<Island>`.
|
|
14
|
+
*/
|
|
8
15
|
export type RadioGroupProps = {
|
|
16
|
+
/** The user pressed an option; it passes that option's `value`. */
|
|
9
17
|
onValueChange?: (value: string) => void
|
|
18
|
+
/** The choices, top to bottom. */
|
|
10
19
|
options: RadioOption[]
|
|
20
|
+
/** The selected option's `value`. Update it from `onValueChange`. */
|
|
11
21
|
value: string
|
|
12
22
|
}
|
|
13
23
|
|
|
@@ -4,7 +4,7 @@ import type {ListItemProps} from '../list-item'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* `ListItem`'s row in the React Native tier — plain views, no host. For lists long enough that a native host per row
|
|
7
|
-
* would cost too much to mount (a navigation list,
|
|
7
|
+
* would cost too much to mount (a navigation list, an `RnFlatList` of rows), with the platform's own press feedback: a
|
|
8
8
|
* ripple across the whole row on Android, a highlight on iOS. In a rounded card, give the card `overflow="hidden"`:
|
|
9
9
|
* Android draws the ripple as the row's foreground, which only a parent's clip keeps inside rounded corners.
|
|
10
10
|
*/
|
|
@@ -2,8 +2,11 @@ import type {ReactElement, ReactNode} from 'react'
|
|
|
2
2
|
|
|
3
3
|
export type RnTooltipActionVariant = 'link' | 'primary' | 'secondary'
|
|
4
4
|
|
|
5
|
+
/** A button in an `RnTooltip`. */
|
|
5
6
|
export type RnTooltipAction = {
|
|
7
|
+
/** The button's text. */
|
|
6
8
|
label: string
|
|
9
|
+
/** Called on press. The tooltip stays up — turn `visible` off here if the action should close it. */
|
|
7
10
|
onPress: () => void
|
|
8
11
|
/** `link` by default — an underlined text. `primary`/`secondary` are small filled/outlined buttons. */
|
|
9
12
|
variant?: RnTooltipActionVariant
|
|
@@ -18,6 +21,7 @@ export type RnTooltipDismissReason = 'anchorHidden' | 'outside'
|
|
|
18
21
|
* Native tier, unlike `Tooltip`, whose anchor is a native view inside a host.
|
|
19
22
|
*/
|
|
20
23
|
export type RnTooltipProps = {
|
|
24
|
+
/** A row of buttons under the text, in order. */
|
|
21
25
|
actions?: RnTooltipAction[]
|
|
22
26
|
/** Which end of the bubble the actions sit at. `end` by default. */
|
|
23
27
|
actionsAlignment?: 'end' | 'start'
|
|
@@ -28,11 +32,18 @@ export type RnTooltipProps = {
|
|
|
28
32
|
boundaryInsets?: {bottom?: number; top?: number}
|
|
29
33
|
/** The anchor — a single React Native view. */
|
|
30
34
|
children: ReactElement
|
|
35
|
+
/** The body text, under the title. */
|
|
31
36
|
message?: string
|
|
37
|
+
/**
|
|
38
|
+
* Asks for the tooltip to close, with the reason — turn `visible` off. On Android any touch outside asks, the start
|
|
39
|
+
* of a scroll included; iOS keeps the tooltip through a scroll.
|
|
40
|
+
*/
|
|
32
41
|
onDismiss: (reason: RnTooltipDismissReason) => void
|
|
33
42
|
/** The appear animation finished. */
|
|
34
43
|
onShown?: () => void
|
|
44
|
+
/** The heading line, above the message. */
|
|
35
45
|
title?: string
|
|
46
|
+
/** Shows the tooltip while true. It never hides itself. */
|
|
36
47
|
visible: boolean
|
|
37
48
|
}
|
|
38
49
|
|
|
@@ -1,9 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {duration, opacity} from '@hzblj/kara-theme'
|
|
2
|
+
import {cloneElement, type ReactNode, useCallback, useRef} from 'react'
|
|
3
|
+
import type {LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent} from 'react-native'
|
|
4
|
+
import {type SharedValue, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'
|
|
5
|
+
|
|
6
|
+
import {ABox, Box} from '../../atoms/box'
|
|
2
7
|
import type {ScrollEdgeContainerComponent, ScrollEdgeContainerProps} from './scroll-edge-container.types'
|
|
3
8
|
|
|
9
|
+
/** Less than a pixel scrolled is not scrolled — a fling settles a fraction away from the edge. */
|
|
10
|
+
const EDGE_SLOP = 0.5
|
|
11
|
+
|
|
12
|
+
const FILL = {duration: duration.d200}
|
|
13
|
+
|
|
14
|
+
type Bar = {children: ReactNode; filled: SharedValue<number>}
|
|
15
|
+
|
|
16
|
+
/** A bar, and behind it the container colour it fills with — faded in and out, not switched. */
|
|
17
|
+
const EdgeBar = ({children, filled}: Bar) => {
|
|
18
|
+
const fill = useAnimatedStyle(() => ({opacity: filled.value}), [])
|
|
19
|
+
|
|
20
|
+
if (!children) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Box>
|
|
26
|
+
<ABox
|
|
27
|
+
bg="surface.secondary"
|
|
28
|
+
bottom={0}
|
|
29
|
+
left={0}
|
|
30
|
+
pointerEvents="none"
|
|
31
|
+
position="absolute"
|
|
32
|
+
right={0}
|
|
33
|
+
style={fill}
|
|
34
|
+
top={0}
|
|
35
|
+
/>
|
|
36
|
+
{children}
|
|
37
|
+
</Box>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
4
41
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
42
|
+
* Material's take on the scroll edge: a React Native scroll view has no `contentInset` to slide content under a bar on
|
|
43
|
+
* Android, so the bars stack above and below it, and do what a Material top app bar does on scroll — transparent at
|
|
44
|
+
* rest, filled with `surface.secondary` (the scrolled container colour, `surfaceContainer`) once content has scrolled
|
|
45
|
+
* under it. The bottom bar mirrors it: filled while there is more content below. The scroll view keeps its own
|
|
46
|
+
* `onScroll`, `onLayout` and `onContentSizeChange`.
|
|
7
47
|
*/
|
|
8
48
|
export const ScrollEdgeContainer: ScrollEdgeContainerComponent = ({
|
|
9
49
|
automaticInsets: _automaticInsets,
|
|
@@ -13,10 +53,63 @@ export const ScrollEdgeContainer: ScrollEdgeContainerComponent = ({
|
|
|
13
53
|
testID,
|
|
14
54
|
topBar,
|
|
15
55
|
...sx
|
|
16
|
-
}: ScrollEdgeContainerProps) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
56
|
+
}: ScrollEdgeContainerProps) => {
|
|
57
|
+
const topFilled = useSharedValue<number>(opacity.o0)
|
|
58
|
+
const bottomFilled = useSharedValue<number>(opacity.o0)
|
|
59
|
+
const metrics = useRef({content: 0, offset: 0, viewport: 0})
|
|
60
|
+
const state = useRef({bottom: false, top: false})
|
|
61
|
+
const {flex, onContentSizeChange, onLayout, onScroll, scrollEventThrottle} = children.props as {
|
|
62
|
+
flex?: number
|
|
63
|
+
onContentSizeChange?: (width: number, height: number) => void
|
|
64
|
+
onLayout?: (event: LayoutChangeEvent) => void
|
|
65
|
+
onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
|
|
66
|
+
scrollEventThrottle?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Only a change of state animates; a scroll event restating the same one would restart the fade.
|
|
70
|
+
const update = useCallback(() => {
|
|
71
|
+
const {content, offset, viewport} = metrics.current
|
|
72
|
+
const top = offset > EDGE_SLOP
|
|
73
|
+
const bottom = viewport > 0 && offset + viewport < content - EDGE_SLOP
|
|
74
|
+
|
|
75
|
+
if (top !== state.current.top) {
|
|
76
|
+
state.current.top = top
|
|
77
|
+
topFilled.value = withTiming(top ? opacity.o100 : opacity.o0, FILL)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (bottom !== state.current.bottom) {
|
|
81
|
+
state.current.bottom = bottom
|
|
82
|
+
bottomFilled.value = withTiming(bottom ? opacity.o100 : opacity.o0, FILL)
|
|
83
|
+
}
|
|
84
|
+
}, [bottomFilled, topFilled])
|
|
85
|
+
|
|
86
|
+
// `flex` so the scroll view takes the room between the bars — left to its content, it grew to the content's height,
|
|
87
|
+
// ran under the bottom bar and had nothing to scroll.
|
|
88
|
+
const scrollable = cloneElement(children, {
|
|
89
|
+
flex: flex ?? 1,
|
|
90
|
+
onContentSizeChange: (width: number, height: number) => {
|
|
91
|
+
metrics.current.content = height
|
|
92
|
+
update()
|
|
93
|
+
onContentSizeChange?.(width, height)
|
|
94
|
+
},
|
|
95
|
+
onLayout: (event: LayoutChangeEvent) => {
|
|
96
|
+
metrics.current.viewport = event.nativeEvent.layout.height
|
|
97
|
+
update()
|
|
98
|
+
onLayout?.(event)
|
|
99
|
+
},
|
|
100
|
+
onScroll: (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
101
|
+
metrics.current.offset = event.nativeEvent.contentOffset.y
|
|
102
|
+
update()
|
|
103
|
+
onScroll?.(event)
|
|
104
|
+
},
|
|
105
|
+
scrollEventThrottle: scrollEventThrottle ?? 16,
|
|
106
|
+
} as Partial<typeof children.props>)
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<Box {...sx} testID={testID}>
|
|
110
|
+
<EdgeBar filled={topFilled}>{topBar}</EdgeBar>
|
|
111
|
+
<Box flex={1}>{scrollable}</Box>
|
|
112
|
+
<EdgeBar filled={bottomFilled}>{bottomBar}</EdgeBar>
|
|
113
|
+
</Box>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -5,8 +5,10 @@ import type {ReactElement, ReactNode} from 'react'
|
|
|
5
5
|
export type {ScrollEdgeEffect, ScrollEdgeScrollableProps} from '@hzblj/kara-native/swift-ui'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Bars over one React Native scroll view, with
|
|
9
|
-
* blur where content scrolls past a bar
|
|
8
|
+
* Bars over one React Native scroll view, with each platform's scroll edge under them — on iOS 26 the system's
|
|
9
|
+
* progressive blur where content scrolls past a bar; on Android a Material top app bar's behaviour, the bars stacked
|
|
10
|
+
* around the scroll view and filling with `surface.secondary` once content scrolls under them. The React Native tier:
|
|
11
|
+
* it needs no host.
|
|
10
12
|
*
|
|
11
13
|
* A screen-level container: wrap the screen's scroll view with it. Mounted inside a cell of a virtualized list on iOS
|
|
12
14
|
* it draws only its bottom bar — the hosted scroll view and the top bar stay blank.
|
|
@@ -17,12 +19,15 @@ export type ScrollEdgeContainerProps = ResponsiveViewSx & {
|
|
|
17
19
|
* content begins below the top bar and scrolls under it. On by default.
|
|
18
20
|
*/
|
|
19
21
|
automaticInsets?: boolean
|
|
22
|
+
/** Laid over the scroll view's bottom edge on iOS; stacked below it on Android, filled while content continues below. */
|
|
20
23
|
bottomBar?: ReactNode
|
|
21
24
|
/** Exactly one React Native scroll view — `RnScrollView`, `RnFlatList`, `RnSectionList`. */
|
|
22
25
|
children: ReactElement<ScrollEdgeScrollableProps>
|
|
23
26
|
/** iOS 26: how hard the edge cuts. `soft` by default. */
|
|
24
27
|
effect?: ScrollEdgeEffect
|
|
28
|
+
/** Test identifier, forwarded to the container view. */
|
|
25
29
|
testID?: string
|
|
30
|
+
/** Laid over the scroll view's top edge on iOS; stacked above it on Android, filled once content scrolls under it. */
|
|
26
31
|
topBar?: ReactNode
|
|
27
32
|
}
|
|
28
33
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
2
|
-
import {TextInput} from 'react-native'
|
|
2
|
+
import {Platform, TextInput} from 'react-native'
|
|
3
3
|
|
|
4
4
|
import {Box} from '../../atoms/box'
|
|
5
5
|
import {Pressable} from '../../atoms/pressable'
|
|
@@ -13,6 +13,13 @@ export const SearchBar: SearchBarComponent = ({
|
|
|
13
13
|
value,
|
|
14
14
|
}: SearchBarProps) => {
|
|
15
15
|
const theme = useAppTheme()
|
|
16
|
+
const {lineHeight, ...face} = theme.typography.body
|
|
17
|
+
/**
|
|
18
|
+
* A single-line `UITextField` sets its text against the bottom of a line taller than the font, so the variant's
|
|
19
|
+
* `lineHeight` pushed the text below the magnifier beside it. iOS takes the face without it, in a field as tall as
|
|
20
|
+
* the line would have been; Android centres the line and keeps it.
|
|
21
|
+
*/
|
|
22
|
+
const text = Platform.OS === 'ios' ? {...face, height: lineHeight} : theme.typography.body
|
|
16
23
|
|
|
17
24
|
return (
|
|
18
25
|
<Box
|
|
@@ -31,7 +38,7 @@ export const SearchBar: SearchBarComponent = ({
|
|
|
31
38
|
onChangeText={onChangeText}
|
|
32
39
|
placeholder={placeholder}
|
|
33
40
|
placeholderTextColor={theme.color('content.quaternary')}
|
|
34
|
-
style={[
|
|
41
|
+
style={[text, {color: theme.color('content.primary'), flex: 1, padding: 0}]}
|
|
35
42
|
value={value}
|
|
36
43
|
/>
|
|
37
44
|
{value.length > 0 ? (
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A search field drawn the same on both platforms — a rounded box with a magnifier, a `TextInput` and a clear button
|
|
5
|
+
* while there is text. The React Native tier: it needs no host.
|
|
6
|
+
*/
|
|
3
7
|
export type SearchBarProps = {
|
|
8
|
+
/** Every edit, and `''` when the clear button is pressed. */
|
|
4
9
|
onChangeText?: (text: string) => void
|
|
10
|
+
/** Shown while the field is empty. `Search` by default. */
|
|
5
11
|
placeholder?: string
|
|
12
|
+
/** Test identifier, put on the field's container view rather than on the `TextInput`. */
|
|
6
13
|
testID?: string
|
|
14
|
+
/** The field's text. Controlled — pass back what `onChangeText` reports. */
|
|
7
15
|
value: string
|
|
8
16
|
}
|
|
9
17
|
|
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
import {Stepper as SwiftUIStepper} from '@expo/ui/swift-ui'
|
|
2
|
+
import {labelsHidden} from '@expo/ui/swift-ui/modifiers'
|
|
3
|
+
import {useRef} from 'react'
|
|
2
4
|
|
|
5
|
+
import {HStack} from '../../atoms/h-stack'
|
|
6
|
+
import {Spacer} from '../../atoms/spacer'
|
|
7
|
+
import {Text} from '../../atoms/text'
|
|
3
8
|
import {useHostGuard} from '../../host-context'
|
|
4
9
|
import type {StepperComponent, StepperProps} from './stepper.types'
|
|
5
10
|
|
|
11
|
+
/** The label stays VoiceOver's, on the control; on screen it is the row's own text. */
|
|
12
|
+
const LABEL_HIDDEN = [labelsHidden()]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* SwiftUI's `Stepper` at the end of a row, the label leading and the value just before the control — the way iOS's own
|
|
16
|
+
* settings show a stepped value. The value rolls to the next one (`numericText`), up or down with the change, in
|
|
17
|
+
* monospaced digits so the row does not jump.
|
|
18
|
+
*/
|
|
6
19
|
export const Stepper: StepperComponent = ({label, max, min, onValueChange, step = 1, value}: StepperProps) => {
|
|
7
20
|
useHostGuard('Stepper')
|
|
21
|
+
const previous = useRef(value)
|
|
22
|
+
const countsDown = value < previous.current
|
|
23
|
+
previous.current = value
|
|
8
24
|
|
|
9
|
-
return
|
|
25
|
+
return (
|
|
26
|
+
<HStack align="center" spacing="s12" wide>
|
|
27
|
+
<Text>{label}</Text>
|
|
28
|
+
<Spacer />
|
|
29
|
+
<Text
|
|
30
|
+
color="content.secondary"
|
|
31
|
+
ios={{animation: {value}, contentTransition: {countsDown, type: 'numericText'}, monospacedDigit: true}}
|
|
32
|
+
>
|
|
33
|
+
{String(value)}
|
|
34
|
+
</Text>
|
|
35
|
+
<SwiftUIStepper
|
|
36
|
+
label={label}
|
|
37
|
+
max={max}
|
|
38
|
+
min={min}
|
|
39
|
+
modifiers={LABEL_HIDDEN}
|
|
40
|
+
onValueChange={onValueChange}
|
|
41
|
+
step={step}
|
|
42
|
+
value={value}
|
|
43
|
+
/>
|
|
44
|
+
</HStack>
|
|
45
|
+
)
|
|
10
46
|
}
|