@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
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A number with minus and plus buttons beside a label: SwiftUI's `Stepper` on iOS; on Android the label, the value and
|
|
5
|
+
* two icon buttons in a row. A native atom — it renders inside `<Screen>` or `<Island>`.
|
|
6
|
+
*/
|
|
3
7
|
export type StepperProps = {
|
|
8
|
+
/** The leading text. The value is shown too — before the control on iOS, between the buttons on Android. */
|
|
4
9
|
label: string
|
|
10
|
+
/** The highest value. When unset, Android has no upper limit and iOS stops at `100`. */
|
|
5
11
|
max?: number
|
|
12
|
+
/** The lowest value. When unset, Android has no lower limit and iOS stops at `0`. */
|
|
6
13
|
min?: number
|
|
14
|
+
/** A button was pressed; it passes the new value, kept within `min` and `max`. */
|
|
7
15
|
onValueChange: (value: number) => void
|
|
16
|
+
/** How much one press adds or takes away. `1` by default. */
|
|
8
17
|
step?: number
|
|
18
|
+
/** The current value. Update it from `onValueChange`. iOS's native stepper counts in whole numbers. */
|
|
9
19
|
value: number
|
|
10
20
|
}
|
|
11
21
|
|
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import {Text as ComposeText
|
|
1
|
+
import {Text as ComposeText} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {TooltipBox} from '@hzblj/kara-native/jetpack-compose'
|
|
2
3
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
+
import {useCallback} from 'react'
|
|
3
5
|
|
|
4
6
|
import {useHostGuard} from '../../host-context'
|
|
5
7
|
import type {TooltipComponent, TooltipProps} from './tooltip.types'
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Material's plain tooltip in kara's controlled `TooltipBox`: shown while `visible`, centred above the anchor and kept
|
|
11
|
+
* inside the window. Material's own would open on a long press and hide itself without a word to JS.
|
|
12
|
+
*/
|
|
13
|
+
export const Tooltip: TooltipComponent = ({children, onDismiss, text, visible}: TooltipProps) => {
|
|
8
14
|
useHostGuard('Tooltip')
|
|
9
|
-
|
|
10
15
|
const theme = useAppTheme()
|
|
16
|
+
const onDismissRequest = useCallback(() => onDismiss(), [onDismiss])
|
|
11
17
|
|
|
12
18
|
return (
|
|
13
|
-
<TooltipBox
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
<TooltipBox
|
|
20
|
+
containerColor={theme.color('surface.high.contrast')}
|
|
21
|
+
isPresented={visible}
|
|
22
|
+
onDismissRequest={onDismissRequest}
|
|
23
|
+
plain
|
|
24
|
+
>
|
|
25
|
+
<TooltipBox.Text>
|
|
18
26
|
<ComposeText color={theme.colorString('content.on.high.contrast')} style={theme.typography.footnote}>
|
|
19
27
|
{text}
|
|
20
28
|
</ComposeText>
|
|
21
|
-
</TooltipBox.
|
|
29
|
+
</TooltipBox.Text>
|
|
22
30
|
{children}
|
|
23
31
|
</TooltipBox>
|
|
24
32
|
)
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A short text tip on a native view, shown while `visible` — a SwiftUI popover from `children` on iOS, Material's plain
|
|
5
|
+
* tooltip centred above them on Android. A native atom — it renders inside `<Screen>` or `<Island>`; `RnTooltip`
|
|
6
|
+
* anchors to a React Native view.
|
|
7
|
+
*/
|
|
3
8
|
export type TooltipProps = {
|
|
9
|
+
/** The anchor — a native view. */
|
|
4
10
|
children: ReactNode
|
|
11
|
+
/** The tip. */
|
|
5
12
|
text: string
|
|
13
|
+
/** Shows the tip while true. Controlled: nothing opens it but this — wire a long press to it if you want one. */
|
|
6
14
|
visible: boolean
|
|
15
|
+
/**
|
|
16
|
+
* The user dismissed the tip — turn `visible` off. iOS: a tap outside the popover. Android: any touch outside it, the
|
|
17
|
+
* start of a scroll included, or the anchor scrolling out of the window.
|
|
18
|
+
*/
|
|
7
19
|
onDismiss: () => void
|
|
8
20
|
}
|
|
9
21
|
|
|
@@ -1,35 +1,53 @@
|
|
|
1
|
-
import {Divider} from '../../atoms/divider'
|
|
2
1
|
import {HStack} from '../../atoms/h-stack'
|
|
2
|
+
import {Icon} from '../../atoms/icon'
|
|
3
|
+
import {Island} from '../../atoms/island'
|
|
3
4
|
import {Text} from '../../atoms/text'
|
|
4
5
|
import {VStack} from '../../atoms/v-stack'
|
|
5
6
|
import {useHostGuard} from '../../host-context'
|
|
6
7
|
import {Sheet} from '../sheet'
|
|
7
8
|
import type {ActionSheetComponent, ActionSheetProps} from './action-sheet.types'
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Material's bottom-sheet menu: the title, then a full-width row per action, its icon leading. The sheet is the React
|
|
12
|
+
* Native tier, so the rows — Compose views — sit in an `Island` of their own; rendered straight into the sheet they had
|
|
13
|
+
* no host and threw.
|
|
14
|
+
*/
|
|
9
15
|
export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, visible}: ActionSheetProps) => {
|
|
10
16
|
useHostGuard('ActionSheet')
|
|
11
17
|
|
|
12
18
|
return (
|
|
13
19
|
<Sheet onDismiss={onDismiss} visible={visible}>
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<HStack
|
|
21
|
-
key={action.label}
|
|
22
|
-
onPress={() => {
|
|
23
|
-
onDismiss()
|
|
24
|
-
action.onPress()
|
|
25
|
-
}}
|
|
26
|
-
py="s12"
|
|
27
|
-
wide
|
|
28
|
-
>
|
|
29
|
-
<Text color={action.destructive ? 'content.destructive' : 'content.primary'}>{action.label}</Text>
|
|
20
|
+
<Island>
|
|
21
|
+
<VStack pb="s16" wide>
|
|
22
|
+
<HStack px="s24" py="s12" wide>
|
|
23
|
+
<Text color="content.secondary" variant="labelMedium">
|
|
24
|
+
{title}
|
|
25
|
+
</Text>
|
|
30
26
|
</HStack>
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
{actions.map(action => {
|
|
28
|
+
const color = action.destructive ? 'content.destructive' : 'content.primary'
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<HStack
|
|
32
|
+
key={action.label}
|
|
33
|
+
onPress={() => {
|
|
34
|
+
onDismiss()
|
|
35
|
+
action.onPress()
|
|
36
|
+
}}
|
|
37
|
+
px="s24"
|
|
38
|
+
py="s16"
|
|
39
|
+
spacing="s16"
|
|
40
|
+
wide
|
|
41
|
+
>
|
|
42
|
+
{action.icon ? <Icon color={color} name={action.icon} /> : null}
|
|
43
|
+
<Text color={color} variant="body">
|
|
44
|
+
{action.label}
|
|
45
|
+
</Text>
|
|
46
|
+
</HStack>
|
|
47
|
+
)
|
|
48
|
+
})}
|
|
49
|
+
</VStack>
|
|
50
|
+
</Island>
|
|
33
51
|
</Sheet>
|
|
34
52
|
)
|
|
35
53
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {Button, ConfirmationDialog} from '@expo/ui/swift-ui'
|
|
1
|
+
import {Button, ConfirmationDialog, Spacer} from '@expo/ui/swift-ui'
|
|
2
|
+
import {frame} from '@expo/ui/swift-ui/modifiers'
|
|
2
3
|
|
|
3
4
|
import {useHostGuard} from '../../host-context'
|
|
4
5
|
import type {ActionSheetComponent, ActionSheetProps} from './action-sheet.types'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* `@expo/ui` hangs the dialog on its `Trigger` and reads the buttons from its `Actions` slot. Without a trigger it
|
|
9
|
+
* attaches to an `EmptyView`, which SwiftUI never presents from — a spacer framed to nothing is a real view that takes
|
|
10
|
+
* no room.
|
|
11
|
+
*/
|
|
12
|
+
const ANCHOR = [frame({height: 0, width: 0})]
|
|
13
|
+
|
|
6
14
|
export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, visible}: ActionSheetProps) => {
|
|
7
15
|
useHostGuard('ActionSheet')
|
|
8
16
|
|
|
@@ -16,14 +24,19 @@ export const ActionSheet: ActionSheetComponent = ({actions, onDismiss, title, vi
|
|
|
16
24
|
}}
|
|
17
25
|
title={title}
|
|
18
26
|
>
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
<ConfirmationDialog.Trigger>
|
|
28
|
+
<Spacer modifiers={ANCHOR} />
|
|
29
|
+
</ConfirmationDialog.Trigger>
|
|
30
|
+
<ConfirmationDialog.Actions>
|
|
31
|
+
{actions.map(action => (
|
|
32
|
+
<Button
|
|
33
|
+
key={action.label}
|
|
34
|
+
label={action.label}
|
|
35
|
+
onPress={action.onPress}
|
|
36
|
+
role={action.destructive ? 'destructive' : 'default'}
|
|
37
|
+
/>
|
|
38
|
+
))}
|
|
39
|
+
</ConfirmationDialog.Actions>
|
|
27
40
|
</ConfirmationDialog>
|
|
28
41
|
)
|
|
29
42
|
}
|
|
@@ -2,10 +2,18 @@ import type {ReactNode} from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type {MenuAction} from '../menu'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A list of actions to pick one from: SwiftUI's confirmation dialog on iOS, a `Sheet` of Material rows — icon and label
|
|
7
|
+
* — under the title on Android. A native atom — it renders inside `<Screen>` or `<Island>`.
|
|
8
|
+
*/
|
|
5
9
|
export type ActionSheetProps = {
|
|
10
|
+
/** The choices, top to bottom. Picking one closes the sheet. */
|
|
6
11
|
actions: MenuAction[]
|
|
12
|
+
/** The sheet closed — after a pick too — so turn `visible` off. */
|
|
7
13
|
onDismiss: () => void
|
|
14
|
+
/** The heading above the actions. Android always shows it; on iOS the system decides, and may hide it. */
|
|
8
15
|
title: string
|
|
16
|
+
/** Presents the sheet while true. */
|
|
9
17
|
visible: boolean
|
|
10
18
|
}
|
|
11
19
|
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {Button, Alert as SwiftUIAlert, Text as SwiftUIText} from '@expo/ui/swift-ui'
|
|
1
|
+
import {Button, Spacer, Alert as SwiftUIAlert, Text as SwiftUIText} from '@expo/ui/swift-ui'
|
|
2
|
+
import {frame} from '@expo/ui/swift-ui/modifiers'
|
|
2
3
|
|
|
3
4
|
import {useHostGuard} from '../../host-context'
|
|
4
5
|
import type {AlertComponent, AlertProps} from './alert.types'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* `@expo/ui` hangs the alert on its `Alert.Trigger`, and on an `EmptyView` when there is none — which SwiftUI never
|
|
9
|
+
* presents from, so the alert would not appear at all. A spacer framed to nothing is a real view that takes no room.
|
|
10
|
+
*/
|
|
11
|
+
const ANCHOR = [frame({height: 0, width: 0})]
|
|
12
|
+
|
|
6
13
|
export const Alert: AlertComponent = ({
|
|
7
14
|
confirmLabel = 'OK',
|
|
8
15
|
destructive = false,
|
|
@@ -24,6 +31,9 @@ export const Alert: AlertComponent = ({
|
|
|
24
31
|
}}
|
|
25
32
|
title={title}
|
|
26
33
|
>
|
|
34
|
+
<SwiftUIAlert.Trigger>
|
|
35
|
+
<Spacer modifiers={ANCHOR} />
|
|
36
|
+
</SwiftUIAlert.Trigger>
|
|
27
37
|
<SwiftUIAlert.Actions>
|
|
28
38
|
<Button label={confirmLabel} onPress={onConfirm ?? onDismiss} role={destructive ? 'destructive' : 'default'} />
|
|
29
39
|
<Button label="Cancel" onPress={onDismiss} role="cancel" />
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A modal alert with a confirm button and Cancel: SwiftUI's alert on iOS, Material's `AlertDialog` on Android. A native
|
|
5
|
+
* atom — it renders inside `<Screen>` or `<Island>`.
|
|
6
|
+
*/
|
|
3
7
|
export type AlertProps = {
|
|
8
|
+
/** The confirm button's label. `OK` by default. */
|
|
4
9
|
confirmLabel?: string
|
|
10
|
+
/** Marks the confirm button destructive — the system's destructive role on iOS, the destructive colour on Android. */
|
|
5
11
|
destructive?: boolean
|
|
12
|
+
/** The body text, under the title. */
|
|
6
13
|
message?: string
|
|
14
|
+
/** The confirm button was pressed. `onDismiss` follows, as it does for every way the alert closes. */
|
|
7
15
|
onConfirm?: () => void
|
|
16
|
+
/** The alert closed — either button, or on Android a tap outside or back — so turn `visible` off. */
|
|
8
17
|
onDismiss: () => void
|
|
18
|
+
/** The alert's heading. */
|
|
9
19
|
title: string
|
|
20
|
+
/** Shows the alert while true. */
|
|
10
21
|
visible: boolean
|
|
11
22
|
}
|
|
12
23
|
|
|
@@ -10,6 +10,7 @@ import type {MenuAction} from '../menu'
|
|
|
10
10
|
* needs no `Screen` or `Island`.
|
|
11
11
|
*/
|
|
12
12
|
export type ContextMenuProps = ResponsiveViewSx & {
|
|
13
|
+
/** The menu's actions, top to bottom. */
|
|
13
14
|
actions: MenuAction[]
|
|
14
15
|
/** The trigger, laid out as usual. */
|
|
15
16
|
children: ReactNode
|
|
@@ -29,10 +30,13 @@ export type ContextMenuProps = ResponsiveViewSx & {
|
|
|
29
30
|
* `ZoomTransition.Source`, `pop` otherwise.
|
|
30
31
|
*/
|
|
31
32
|
previewTransition?: 'dismiss' | 'pop' | 'zoom'
|
|
33
|
+
/** Test identifier — on the native view around the trigger on iOS, on the trigger's `Pressable` on Android. */
|
|
32
34
|
testID?: string
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/** A region of a `ContextMenu`'s trigger that keeps its own long press — see `ContextMenu.Exclusion`. */
|
|
35
38
|
export type ContextMenuExclusionProps = {
|
|
39
|
+
/** The region — a button inside the card, say. */
|
|
36
40
|
children: ReactNode
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -9,9 +9,11 @@ import type {MenuAction} from '../menu'
|
|
|
9
9
|
* of a SwiftUI `Menu`. A native atom — it renders inside `<Screen>` or `<Island>`.
|
|
10
10
|
*/
|
|
11
11
|
export type FabMenuProps = {
|
|
12
|
+
/** The actions the button unfolds into. */
|
|
12
13
|
actions: MenuAction[]
|
|
13
14
|
/** The closed button's icon — one of the app's. A plus by default. */
|
|
14
15
|
icon?: IconName
|
|
16
|
+
/** Test identifier, forwarded to the SwiftUI `Menu`. iOS only — Android ignores it. */
|
|
15
17
|
testID?: string
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -4,11 +4,15 @@ import {VStack} from '../../atoms/v-stack'
|
|
|
4
4
|
import {useHostGuard} from '../../host-context'
|
|
5
5
|
import type {FormComponent, FormProps, FormSectionProps} from './form.types'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Inset from its container on every side, as SwiftUI's grouped form is — in a `Screen` the cards would otherwise run
|
|
9
|
+
* into the screen's edges — and the section titles and footers indented to the cards' content.
|
|
10
|
+
*/
|
|
7
11
|
const FormRoot = ({children, testID}: FormProps) => {
|
|
8
12
|
useHostGuard('Form')
|
|
9
13
|
|
|
10
14
|
return (
|
|
11
|
-
<VStack spacing="s16" testID={testID} wide>
|
|
15
|
+
<VStack p="s16" spacing="s16" testID={testID} wide>
|
|
12
16
|
{children}
|
|
13
17
|
</VStack>
|
|
14
18
|
)
|
|
@@ -17,15 +21,19 @@ const FormRoot = ({children, testID}: FormProps) => {
|
|
|
17
21
|
const FormSection = ({children, footer, title}: FormSectionProps) => (
|
|
18
22
|
<VStack spacing="s4" wide>
|
|
19
23
|
{title ? (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
<VStack px="s16" wide>
|
|
25
|
+
<Text color="content.secondary" transform="uppercase" variant="labelMedium">
|
|
26
|
+
{title}
|
|
27
|
+
</Text>
|
|
28
|
+
</VStack>
|
|
23
29
|
) : null}
|
|
24
30
|
<Card wide>{children}</Card>
|
|
25
31
|
{footer ? (
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
<VStack px="s16" wide>
|
|
33
|
+
<Text color="content.tertiary" variant="footnote">
|
|
34
|
+
{footer}
|
|
35
|
+
</Text>
|
|
36
|
+
</VStack>
|
|
29
37
|
) : null}
|
|
30
38
|
</VStack>
|
|
31
39
|
)
|
|
@@ -6,13 +6,19 @@ import type {ReactNode} from 'react'
|
|
|
6
6
|
* an island that sizes itself to its content it has no height and shows nothing.
|
|
7
7
|
*/
|
|
8
8
|
export type FormProps = {
|
|
9
|
+
/** The form's `Form.Section`s. */
|
|
9
10
|
children: ReactNode
|
|
11
|
+
/** Test identifier, forwarded to the native view. */
|
|
10
12
|
testID?: string
|
|
11
13
|
}
|
|
12
14
|
|
|
15
|
+
/** A group of rows in a `Form`: a SwiftUI `Section` on iOS, a `Card` under a heading on Android. */
|
|
13
16
|
export type FormSectionProps = {
|
|
17
|
+
/** The section's rows. */
|
|
14
18
|
children: ReactNode
|
|
19
|
+
/** Small print under the section. */
|
|
15
20
|
footer?: string
|
|
21
|
+
/** The section's heading. */
|
|
16
22
|
title?: string
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -2,15 +2,32 @@ import type {ReactNode} from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type {IconName} from '../../atoms/icon'
|
|
4
4
|
|
|
5
|
+
/** One action of a `Menu`, an `ActionSheet`, a `ContextMenu` or a `FabMenu`. */
|
|
5
6
|
export type MenuAction = {
|
|
7
|
+
/**
|
|
8
|
+
* Draws the action in the destructive colour — the system's destructive role on iOS. `FabMenu` ignores it on
|
|
9
|
+
* Android.
|
|
10
|
+
*/
|
|
6
11
|
destructive?: boolean
|
|
12
|
+
/**
|
|
13
|
+
* One of the app's icons, before the label. `ContextMenu` and `FabMenu` show it on both platforms, `Menu` and
|
|
14
|
+
* `ActionSheet` on Android only.
|
|
15
|
+
*/
|
|
7
16
|
icon?: IconName
|
|
17
|
+
/** The action's text. Rows are keyed by it, so keep labels unique within one list. */
|
|
8
18
|
label: string
|
|
19
|
+
/** Called when the action is chosen; the menu closes by itself. */
|
|
9
20
|
onPress: () => void
|
|
10
21
|
}
|
|
11
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A text button that opens a list of actions: a SwiftUI `Menu` on iOS, a Material dropdown anchored to it on Android.
|
|
25
|
+
* A native atom — it renders inside `<Screen>` or `<Island>`.
|
|
26
|
+
*/
|
|
12
27
|
export type MenuProps = {
|
|
28
|
+
/** The menu's actions, top to bottom. */
|
|
13
29
|
actions: MenuAction[]
|
|
30
|
+
/** The trigger's text. Android draws it in the accent colour, with a chevron. */
|
|
14
31
|
label: string
|
|
15
32
|
}
|
|
16
33
|
|
|
@@ -27,6 +27,7 @@ export type SheetHeaderProps = {
|
|
|
27
27
|
leadingItems?: NavigationBarItem[]
|
|
28
28
|
/** A search field in place of the title (iOS). */
|
|
29
29
|
search?: NavigationBarSearch
|
|
30
|
+
/** The title — in the bar, or above the content with a `large` `titleStyle`. */
|
|
30
31
|
title?: string
|
|
31
32
|
/** `center` by default. */
|
|
32
33
|
titleStyle?: SheetTitleStyle
|
|
@@ -34,7 +35,9 @@ export type SheetHeaderProps = {
|
|
|
34
35
|
trailingItems?: NavigationBarItem[]
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
/** Imperative control of a `Sheet`, through its `ref`. */
|
|
37
39
|
export type SheetRef = {
|
|
40
|
+
/** Dismisses it without calling `onDismiss`, which reports the user's dismissals only. */
|
|
38
41
|
dismiss: () => Promise<void>
|
|
39
42
|
/** Presents at a detent — the first by default. */
|
|
40
43
|
present: (detentIndex?: number) => Promise<void>
|
|
@@ -50,6 +53,7 @@ export type SheetRef = {
|
|
|
50
53
|
export type SheetProps = {
|
|
51
54
|
/** The sheet's colour. `surface.primary` by default. */
|
|
52
55
|
bg?: ColorToken
|
|
56
|
+
/** The content. A `<Sheet.Header>` among the direct children becomes the native header instead. */
|
|
53
57
|
children: ReactNode
|
|
54
58
|
/** Where the sheet can rest, smallest first. `['auto']` — fit the content — by default. */
|
|
55
59
|
detents?: SheetDetent[]
|
|
@@ -65,15 +69,20 @@ export type SheetProps = {
|
|
|
65
69
|
grabber?: boolean
|
|
66
70
|
/** The user dismissed it — `visible` going false does not call this. */
|
|
67
71
|
onDismiss: () => void
|
|
72
|
+
/** The sheet moved to another detent — its index in `detents`. */
|
|
68
73
|
onDetentChange?: (detentIndex: number) => void
|
|
74
|
+
/** The sheet finished presenting. */
|
|
69
75
|
onPresent?: () => void
|
|
76
|
+
/** `present`, `dismiss` and `resize`, for driving the sheet imperatively. */
|
|
70
77
|
ref?: Ref<SheetRef>
|
|
71
78
|
/**
|
|
72
79
|
* The content is a scroll view (`<Sheet.ScrollView>`) the sheet drives: dragging past its top resizes the sheet,
|
|
73
80
|
* and a header scrolls with the iOS 26 edge effect. Off by default — set it with a scrolling body.
|
|
74
81
|
*/
|
|
75
82
|
scrollable?: boolean
|
|
83
|
+
/** Test identifier, forwarded to the native sheet. */
|
|
76
84
|
testID?: string
|
|
85
|
+
/** Presents the sheet while true; going false dismisses it. */
|
|
77
86
|
visible: boolean
|
|
78
87
|
}
|
|
79
88
|
|
|
@@ -81,16 +90,26 @@ export type SheetScrollHandler = (event: NativeSyntheticEvent<NativeScrollEvent>
|
|
|
81
90
|
|
|
82
91
|
/** What a scroll view inside a sheet needs: the room the header takes, the large title, the scroll listener. */
|
|
83
92
|
export type SheetScrollContextValue = {
|
|
93
|
+
/** Render it first in the content. `null` unless the header's `titleStyle` is `large`. */
|
|
84
94
|
largeTitle: ReactNode
|
|
95
|
+
/** Forward the scroll view's scroll events to it — it tracks the large title. Unset when there is none. */
|
|
85
96
|
onScroll?: SheetScrollHandler
|
|
97
|
+
/** The room an overlaid header takes, to add to the content's top padding. `0` where the header sits above it. */
|
|
86
98
|
topInset: number
|
|
87
99
|
}
|
|
88
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The body of a `scrollable` sheet: its content starts below an overlaid header, it carries a large title, and it
|
|
103
|
+
* reports its scroll to the sheet.
|
|
104
|
+
*/
|
|
89
105
|
export type SheetScrollViewProps = Pick<ScrollViewProps, 'keyboardShouldPersistTaps'> & {
|
|
106
|
+
/** The scrolling content, after the large title if there is one. */
|
|
90
107
|
children: ReactNode
|
|
91
108
|
/** Styles the scrolling content — padding and gap belong here, as with `RnScrollView`. */
|
|
92
109
|
content?: ResponsiveViewSx
|
|
110
|
+
/** Called on every scroll event, after the sheet's own listener. */
|
|
93
111
|
onScroll?: SheetScrollHandler
|
|
112
|
+
/** Test identifier, forwarded to the scroll view. */
|
|
94
113
|
testID?: string
|
|
95
114
|
}
|
|
96
115
|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A short message laid over the bottom of its parent, with an optional action. The React Native tier, drawn the same on
|
|
5
|
+
* both platforms: it needs no host, and it never hides itself.
|
|
6
|
+
*/
|
|
3
7
|
export type SnackbarProps = {
|
|
8
|
+
/** The action button's text, on the trailing side. No button without it. */
|
|
4
9
|
actionLabel?: string
|
|
10
|
+
/** The message. */
|
|
5
11
|
message: string
|
|
12
|
+
/** The action was pressed. The snackbar stays up — turn `visible` off to hide it. */
|
|
6
13
|
onAction?: () => void
|
|
14
|
+
/** Shows the snackbar while true, fading it in and out. It sets no timer of its own. */
|
|
7
15
|
visible: boolean
|
|
8
16
|
}
|
|
9
17
|
|
|
@@ -1,14 +1,134 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import {FilledIconButton} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {size} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
+
import {SwipeActionsBox, type SwipeActionsBoxEdge} from '@hzblj/kara-native/jetpack-compose'
|
|
4
|
+
import {spacing, useAppTheme} from '@hzblj/kara-theme'
|
|
5
|
+
import {useCallback, useEffect, useId, useState, useSyncExternalStore} from 'react'
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
import {HStack} from '../../atoms/h-stack'
|
|
8
|
+
import {Icon} from '../../atoms/icon'
|
|
9
|
+
import {Text} from '../../atoms/text'
|
|
10
|
+
import {VStack} from '../../atoms/v-stack'
|
|
11
|
+
import {useHostGuard} from '../../host-context'
|
|
12
|
+
import type {SwipeAction, SwipeActionsComponent, SwipeActionsProps} from './swipe-actions.types'
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
/** A Material 3 Expressive wide icon button: wider than tall, a stadium, the label under it. */
|
|
15
|
+
const BUTTON = [size(52, 32)]
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Which row is open, app-wide — SwiftUI's list keeps one row open, and so does this: opening one closes the last.
|
|
19
|
+
* React state cannot be shared between rows that know nothing of each other, so it lives in the module.
|
|
20
|
+
*/
|
|
21
|
+
let openRow: string | null = null
|
|
22
|
+
|
|
23
|
+
const listeners = new Set<() => void>()
|
|
24
|
+
|
|
25
|
+
const setOpenRow = (row: string | null) => {
|
|
26
|
+
if (openRow === row) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
openRow = row
|
|
31
|
+
|
|
32
|
+
for (const listener of listeners) {
|
|
33
|
+
listener()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const subscribe = (listener: () => void) => {
|
|
38
|
+
listeners.add(listener)
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
listeners.delete(listener)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getOpenRow = () => openRow
|
|
46
|
+
|
|
47
|
+
type ActionButtonProps = {action: SwipeAction; onDone: () => void}
|
|
48
|
+
|
|
49
|
+
const ActionButton = ({action, onDone}: ActionButtonProps) => {
|
|
50
|
+
const theme = useAppTheme()
|
|
51
|
+
const fill = action.tint ?? (action.destructive ? 'fill.destructive.primary' : 'fill.accent.primary')
|
|
52
|
+
const content = !action.tint && action.destructive ? 'content.on.destructive' : 'content.on.bg'
|
|
53
|
+
|
|
54
|
+
const onClick = useCallback(() => {
|
|
55
|
+
onDone()
|
|
56
|
+
action.onPress()
|
|
57
|
+
}, [action, onDone])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<VStack align="center" spacing="s2">
|
|
61
|
+
<FilledIconButton
|
|
62
|
+
colors={{containerColor: theme.color(fill), contentColor: theme.color(content)}}
|
|
63
|
+
modifiers={BUTTON}
|
|
64
|
+
onClick={onClick}
|
|
65
|
+
>
|
|
66
|
+
<Icon accessibilityLabel={action.label} color={content} name={action.icon} size="s20" />
|
|
67
|
+
</FilledIconButton>
|
|
68
|
+
<Text color="content.secondary" variant="labelMedium">
|
|
69
|
+
{action.label}
|
|
70
|
+
</Text>
|
|
71
|
+
</VStack>
|
|
72
|
+
)
|
|
9
73
|
}
|
|
10
74
|
|
|
11
|
-
|
|
12
|
-
|
|
75
|
+
const Actions = ({actions, onDone}: {actions: SwipeAction[]; onDone: () => void}) => (
|
|
76
|
+
<HStack align="center" spacing="s8">
|
|
77
|
+
{actions.map(action => (
|
|
78
|
+
<ActionButton action={action} key={action.label} onDone={onDone} />
|
|
79
|
+
))}
|
|
80
|
+
</HStack>
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const close = () => setOpenRow(null)
|
|
13
84
|
|
|
14
|
-
|
|
85
|
+
/**
|
|
86
|
+
* kara's `SwipeActionsBox`: the row slides on an anchored drag and the buttons show in the strip it leaves, fading in.
|
|
87
|
+
* Controlled from here so one row is open at a time; a press closes the row before the action runs.
|
|
88
|
+
*/
|
|
89
|
+
export const SwipeActions: SwipeActionsComponent = ({children, leading = [], trailing = []}: SwipeActionsProps) => {
|
|
90
|
+
useHostGuard('SwipeActions')
|
|
91
|
+
const row = useId()
|
|
92
|
+
const isOpenRow = useSyncExternalStore(subscribe, getOpenRow) === row
|
|
93
|
+
const [edge, setEdge] = useState<SwipeActionsBoxEdge | null>(null)
|
|
94
|
+
|
|
95
|
+
const onOpenChange = useCallback(
|
|
96
|
+
(next: SwipeActionsBoxEdge | null) => {
|
|
97
|
+
setEdge(next)
|
|
98
|
+
|
|
99
|
+
if (next) {
|
|
100
|
+
setOpenRow(row)
|
|
101
|
+
} else if (openRow === row) {
|
|
102
|
+
setOpenRow(null)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[row]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// A row that goes away open must not keep the others from opening.
|
|
109
|
+
useEffect(
|
|
110
|
+
() => () => {
|
|
111
|
+
if (openRow === row) {
|
|
112
|
+
setOpenRow(null)
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
[row]
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<SwipeActionsBox actionsSpacing={spacing.s12} onOpenChange={onOpenChange} openEdge={isOpenRow ? edge : null}>
|
|
120
|
+
{leading.length > 0 ? (
|
|
121
|
+
<SwipeActionsBox.Leading>
|
|
122
|
+
<Actions actions={leading} onDone={close} />
|
|
123
|
+
</SwipeActionsBox.Leading>
|
|
124
|
+
) : null}
|
|
125
|
+
{trailing.length > 0 ? (
|
|
126
|
+
<SwipeActionsBox.Trailing>
|
|
127
|
+
{/* The first action sits outermost — at the right-hand end of a trailing row. */}
|
|
128
|
+
<Actions actions={[...trailing].reverse()} onDone={close} />
|
|
129
|
+
</SwipeActionsBox.Trailing>
|
|
130
|
+
) : null}
|
|
131
|
+
{children}
|
|
132
|
+
</SwipeActionsBox>
|
|
133
|
+
)
|
|
134
|
+
}
|