@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
|
@@ -7,14 +7,11 @@ import type {RnFlatListProps} from './rn-flat-list.types'
|
|
|
7
7
|
const NO_SX = {}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ends up drawn where the layout no longer is. `VirtualizedList` renders its cells in normal flow
|
|
16
|
-
* inside a scroll view, using blank spacers for the range it has not rendered, so a hosted view stays
|
|
17
|
-
* exactly where the layout put it. This is `SectionList`'s reason to exist too, without the sections.
|
|
10
|
+
* kara's list for React Native content: React Native's own `VirtualizedList`, styled through `sx`. It renders its
|
|
11
|
+
* cells in normal flow inside a scroll view, with blank spacers for the range it has not rendered, so a row that hosts
|
|
12
|
+
* native views keeps them where the layout put it. A recycling list (absolutely positioned, reused cells) cannot
|
|
13
|
+
* promise that — an `@expo/ui` host survives neither, and draws where the layout no longer is — which is why kara
|
|
14
|
+
* ships none.
|
|
18
15
|
*/
|
|
19
16
|
export const RnFlatList = <TItem,>({content, data, renderItem, ...props}: RnFlatListProps<TItem>) => {
|
|
20
17
|
const [style, rest] = useSplitSx(props as Record<string, unknown>)
|
|
@@ -23,6 +20,9 @@ export const RnFlatList = <TItem,>({content, data, renderItem, ...props}: RnFlat
|
|
|
23
20
|
return (
|
|
24
21
|
<RNFlatList<TItem>
|
|
25
22
|
data={data}
|
|
23
|
+
// Android clips by default, and a host is measured after its first layout — at zero height it counts as
|
|
24
|
+
// off-screen, is detached, and its native view never comes back.
|
|
25
|
+
removeClippedSubviews={false}
|
|
26
26
|
renderItem={renderItem}
|
|
27
27
|
{...rest}
|
|
28
28
|
contentContainerStyle={content === undefined ? undefined : contentStyle}
|
|
@@ -3,9 +3,11 @@ import type {FlatListProps as RNFlatListProps} from 'react-native'
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Top-level `sx` styles the viewport; `content` styles the scrolling content container, so padding and
|
|
6
|
-
* gap belong there — the same split as `RnScrollView`.
|
|
6
|
+
* gap belong there — the same split as `RnScrollView`. `removeClippedSubviews` is off unless you turn it on:
|
|
7
|
+
* Android's default would detach a row's host before it has measured itself, and the row would stay empty.
|
|
7
8
|
*/
|
|
8
9
|
export type RnFlatListProps<TItem> = ResponsiveViewSx &
|
|
9
10
|
Omit<RNFlatListProps<TItem>, 'contentContainerStyle' | 'style'> & {
|
|
11
|
+
/** Styles the scrolling content container — padding and gap belong here. */
|
|
10
12
|
content?: ResponsiveViewSx
|
|
11
13
|
}
|
|
@@ -13,9 +13,13 @@ import type {ScrollViewAxis} from '../scroll-view'
|
|
|
13
13
|
*/
|
|
14
14
|
export type RnScrollViewProps = ResponsiveViewSx &
|
|
15
15
|
Omit<RNScrollViewProps, 'contentContainerStyle' | 'horizontal' | 'style'> & {
|
|
16
|
+
/** The scroll direction. `vertical` by default. */
|
|
16
17
|
axis?: ScrollViewAxis
|
|
18
|
+
/** The scrolling content. */
|
|
17
19
|
children?: ReactNode
|
|
20
|
+
/** Styles the scrolling content container — padding and gap belong here. */
|
|
18
21
|
content?: ResponsiveViewSx
|
|
22
|
+
/** Shows the scroll indicators. On by default. */
|
|
19
23
|
showsIndicators?: boolean
|
|
20
24
|
}
|
|
21
25
|
|
|
@@ -7,14 +7,8 @@ import type {RnSectionListProps} from './rn-section-list.types'
|
|
|
7
7
|
const NO_SX = {}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* FlashList is the default for lists in this codebase and should stay that way for data rows. This
|
|
13
|
-
* exists for the case it cannot serve: content that hosts native views. FlashList positions its rows
|
|
14
|
-
* absolutely and recycles their cells, and an `@expo/ui` host does not survive either — the hosted
|
|
15
|
-
* view ends up drawn where the layout no longer is. `VirtualizedList` renders its cells in normal flow
|
|
16
|
-
* inside a scroll view, using blank spacers for the range it has not rendered, so a hosted view stays
|
|
17
|
-
* exactly where the layout put it.
|
|
10
|
+
* `RnFlatList` with sections: React Native's own `VirtualizedList`, cells in normal flow inside a scroll view, so a
|
|
11
|
+
* row that hosts native views keeps them where the layout put it.
|
|
18
12
|
*
|
|
19
13
|
* Sticky headers default off, unlike React Native, where they are on for vertical lists. A header that
|
|
20
14
|
* detaches and floats over the content is a deliberate effect, not something a list should do unasked.
|
|
@@ -30,6 +24,9 @@ export const RnSectionList = <TItem, TSection = DefaultSectionT>({
|
|
|
30
24
|
|
|
31
25
|
return (
|
|
32
26
|
<RNSectionList<TItem, TSection>
|
|
27
|
+
// Android clips by default, and a host is measured after its first layout — at zero height it counts as
|
|
28
|
+
// off-screen, is detached, and its native view never comes back.
|
|
29
|
+
removeClippedSubviews={false}
|
|
33
30
|
sections={sections}
|
|
34
31
|
{...rest}
|
|
35
32
|
contentContainerStyle={content === undefined ? undefined : contentStyle}
|
|
@@ -3,9 +3,11 @@ import type {DefaultSectionT, SectionListProps as RNSectionListProps} from 'reac
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Top-level `sx` styles the viewport; `content` styles the scrolling content container, so padding and
|
|
6
|
-
* gap belong there — the same split as `RnScrollView` and `RnFlatList`.
|
|
6
|
+
* gap belong there — the same split as `RnScrollView` and `RnFlatList`. `removeClippedSubviews` is off unless
|
|
7
|
+
* you turn it on, as in `RnFlatList`: Android's default would leave rows with hosts in them empty.
|
|
7
8
|
*/
|
|
8
9
|
export type RnSectionListProps<TItem, TSection = DefaultSectionT> = ResponsiveViewSx &
|
|
9
10
|
Omit<RNSectionListProps<TItem, TSection>, 'contentContainerStyle' | 'style'> & {
|
|
11
|
+
/** Styles the scrolling content container — padding and gap belong here. */
|
|
10
12
|
content?: ResponsiveViewSx
|
|
11
13
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import {useSplitSx} from '@hzblj/kara-sx'
|
|
1
2
|
import {MAX_FONT_SCALE, useAppTheme} from '@hzblj/kara-theme'
|
|
2
|
-
import {Text as RNText, useWindowDimensions} from 'react-native'
|
|
3
|
+
import {Text as RNText, type TextStyle, useWindowDimensions} from 'react-native'
|
|
3
4
|
|
|
4
5
|
import type {RnTextComponent, RnTextProps} from './rn-text.types'
|
|
5
6
|
|
|
@@ -10,6 +11,8 @@ export const RnText: RnTextComponent = ({
|
|
|
10
11
|
...props
|
|
11
12
|
}: RnTextProps) => {
|
|
12
13
|
const theme = useAppTheme()
|
|
14
|
+
// A view style, which a text accepts whole — only React Native's types disagree, on `userSelect`'s width.
|
|
15
|
+
const [sx, rest] = useSplitSx(props) as [TextStyle, Record<string, unknown>]
|
|
13
16
|
const {fontScale} = useWindowDimensions()
|
|
14
17
|
const style = theme.typography[variant]
|
|
15
18
|
|
|
@@ -26,8 +29,8 @@ export const RnText: RnTextComponent = ({
|
|
|
26
29
|
return (
|
|
27
30
|
<RNText
|
|
28
31
|
maxFontSizeMultiplier={MAX_FONT_SCALE}
|
|
29
|
-
style={[style, {color: theme.color(color), lineHeight, textAlign: align}]}
|
|
30
|
-
{...
|
|
32
|
+
style={[style, sx, {color: theme.color(color), lineHeight, textAlign: align}]}
|
|
33
|
+
{...rest}
|
|
31
34
|
/>
|
|
32
35
|
)
|
|
33
36
|
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import type {ResponsiveViewSx} from '@hzblj/kara-sx'
|
|
1
2
|
import type {ColorToken, TextVariant} from '@hzblj/kara-theme'
|
|
2
3
|
import type {ReactNode} from 'react'
|
|
3
4
|
import type {TextProps as RNTextProps} from 'react-native'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* React Native's `Text` in a theme typography variant — the React Native tier, for text outside a native host. It takes
|
|
8
|
+
* `Box`'s `sx` surface for its own box — margins, flex, padding, a background — all but `align`, which here aligns the
|
|
9
|
+
* lines.
|
|
10
|
+
*/
|
|
11
|
+
export type RnTextProps = Omit<RNTextProps, 'style'> &
|
|
12
|
+
Omit<ResponsiveViewSx, 'align'> & {
|
|
13
|
+
/** Horizontal alignment, as React Native's `textAlign`. */
|
|
14
|
+
align?: 'center' | 'left' | 'right'
|
|
15
|
+
/** The text's colour. `content.primary` by default. */
|
|
16
|
+
color?: ColorToken
|
|
17
|
+
/** The typography variant. `body` by default. */
|
|
18
|
+
variant?: TextVariant
|
|
19
|
+
}
|
|
10
20
|
|
|
11
21
|
export type RnTextComponent = (props: RnTextProps) => ReactNode
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {ReactElement, ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/** Carries a React Native view into a SwiftUI or Compose tree — `@expo/ui`'s `RNHostView` on both platforms. */
|
|
3
4
|
export type RnViewProps = {
|
|
4
5
|
/**
|
|
5
6
|
* A single React Native *view*. It may not contain React Native text: a bare string becomes a
|
|
@@ -15,6 +16,7 @@ export type RnViewProps = {
|
|
|
15
16
|
* intrinsic size and would otherwise stretch to fill the SwiftUI parent.
|
|
16
17
|
*/
|
|
17
18
|
matchContents?: boolean
|
|
19
|
+
/** Not forwarded on either platform — give the child its own `testID`. */
|
|
18
20
|
testID?: string
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -9,7 +9,9 @@ export type ScrollViewAxis = 'horizontal' | 'vertical'
|
|
|
9
9
|
* like React Native's `contentContainerStyle` padding and scrolls with the content.
|
|
10
10
|
*/
|
|
11
11
|
export type ScrollViewProps = ResponsiveSx & {
|
|
12
|
+
/** The scroll direction. `vertical` by default. */
|
|
12
13
|
axis?: ScrollViewAxis
|
|
14
|
+
/** Laid out in a stack along `axis`. */
|
|
13
15
|
children?: ReactNode
|
|
14
16
|
/**
|
|
15
17
|
* Enables pull-to-refresh. Async on purpose: the spinner runs until the promise settles, which is
|
|
@@ -21,7 +23,9 @@ export type ScrollViewProps = ResponsiveSx & {
|
|
|
21
23
|
onRefresh?: () => Promise<void>
|
|
22
24
|
/** iOS only — Compose exposes no indicator toggle, so Android always shows the native one. */
|
|
23
25
|
showsIndicators?: boolean
|
|
26
|
+
/** The space between children. */
|
|
24
27
|
spacing?: SpaceSize
|
|
28
|
+
/** Test identifier, forwarded to the native view. */
|
|
25
29
|
testID?: string
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {Host, SegmentedButton, SingleChoiceSegmentedButtonRow, Text} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {fillMaxWidth, testID as testIdModifier, weight} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
+
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
+
import {useCallback, useMemo} from 'react'
|
|
5
|
+
|
|
6
|
+
import {haptics} from '../../haptics'
|
|
7
|
+
import type {SegmentedControlComponent, SegmentedControlProps} from './segmented-control.types'
|
|
8
|
+
|
|
9
|
+
const MATCH_HEIGHT_ONLY = {horizontal: false, vertical: true}
|
|
10
|
+
|
|
11
|
+
/** Every segment the same width, like UIKit's: Material's would size each to its label. */
|
|
12
|
+
const SEGMENT = [weight(1)]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Material's single-choice segmented buttons, the selected one filled with the accent like a checked `Switch`. Its own
|
|
16
|
+
* host, themed like an `Island`, so it drops into React Native layout the way the iOS control does. Material plays
|
|
17
|
+
* nothing on a new segment, so this plays `selection`.
|
|
18
|
+
*/
|
|
19
|
+
export const SegmentedControl: SegmentedControlComponent = ({
|
|
20
|
+
disabled = false,
|
|
21
|
+
onChange,
|
|
22
|
+
selectedIndex,
|
|
23
|
+
testID,
|
|
24
|
+
values,
|
|
25
|
+
}: SegmentedControlProps) => {
|
|
26
|
+
const theme = useAppTheme()
|
|
27
|
+
|
|
28
|
+
const colors = useMemo(
|
|
29
|
+
() => ({
|
|
30
|
+
activeBorderColor: theme.color('fill.accent.primary'),
|
|
31
|
+
activeContainerColor: theme.color('fill.accent.primary'),
|
|
32
|
+
activeContentColor: theme.color('content.on.bg'),
|
|
33
|
+
disabledActiveBorderColor: theme.color('fill.accent.disabled'),
|
|
34
|
+
disabledActiveContainerColor: theme.color('fill.accent.disabled'),
|
|
35
|
+
disabledActiveContentColor: theme.color('btn.content.disabled'),
|
|
36
|
+
disabledInactiveBorderColor: theme.color('border.tertiary'),
|
|
37
|
+
disabledInactiveContentColor: theme.color('btn.content.disabled'),
|
|
38
|
+
inactiveBorderColor: theme.color('border.secondary.base'),
|
|
39
|
+
inactiveContentColor: theme.color('content.primary'),
|
|
40
|
+
}),
|
|
41
|
+
[theme]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const rowModifiers = useMemo(() => (testID ? [fillMaxWidth(), testIdModifier(testID)] : [fillMaxWidth()]), [testID])
|
|
45
|
+
|
|
46
|
+
// Material reports a tap on the segment that is already selected too; iOS does not, and neither does this.
|
|
47
|
+
const select = useCallback(
|
|
48
|
+
(index: number) => {
|
|
49
|
+
if (index === selectedIndex) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
haptics.selection()
|
|
54
|
+
onChange?.(index, values[index] ?? '')
|
|
55
|
+
},
|
|
56
|
+
[onChange, selectedIndex, values]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Host colorScheme={theme.dark ? 'dark' : 'light'} matchContents={MATCH_HEIGHT_ONLY} seedColor={theme.materialSeed}>
|
|
61
|
+
<SingleChoiceSegmentedButtonRow modifiers={rowModifiers}>
|
|
62
|
+
{values.map((label, index) => (
|
|
63
|
+
<SegmentedButton
|
|
64
|
+
colors={colors}
|
|
65
|
+
enabled={!disabled}
|
|
66
|
+
key={index}
|
|
67
|
+
modifiers={SEGMENT}
|
|
68
|
+
onClick={() => select(index)}
|
|
69
|
+
selected={index === selectedIndex}
|
|
70
|
+
>
|
|
71
|
+
<SegmentedButton.Label>
|
|
72
|
+
<Text style={theme.typography.subheadlineMedium}>{label}</Text>
|
|
73
|
+
</SegmentedButton.Label>
|
|
74
|
+
</SegmentedButton>
|
|
75
|
+
))}
|
|
76
|
+
</SingleChoiceSegmentedButtonRow>
|
|
77
|
+
</Host>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {SegmentedControl as NativeSegmentedControl} from '@hzblj/kara-native/swift-ui'
|
|
2
|
+
import {useAppTheme} from '@hzblj/kara-theme'
|
|
3
|
+
import {useCallback, useMemo} from 'react'
|
|
4
|
+
|
|
5
|
+
import type {SegmentedControlComponent, SegmentedControlProps} from './segmented-control.types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* kara's own `UISegmentedControl`, with the pill in the accent and white text on it — the system control tints only the
|
|
9
|
+
* pill, and SwiftUI's segmented picker not even that. A plain `UIView` in React Native layout: it needs no host, takes
|
|
10
|
+
* the width it is given and UIKit's height. UIKit plays the control's own feedback.
|
|
11
|
+
*/
|
|
12
|
+
export const SegmentedControl: SegmentedControlComponent = ({
|
|
13
|
+
disabled = false,
|
|
14
|
+
onChange,
|
|
15
|
+
selectedIndex,
|
|
16
|
+
testID,
|
|
17
|
+
values,
|
|
18
|
+
}: SegmentedControlProps) => {
|
|
19
|
+
const theme = useAppTheme()
|
|
20
|
+
const face = theme.typography.footnoteMedium
|
|
21
|
+
|
|
22
|
+
// The native control speaks in values; a segment's is its index, so two labels may read the same.
|
|
23
|
+
const options = useMemo(() => values.map((label, index) => ({label, value: String(index)})), [values])
|
|
24
|
+
|
|
25
|
+
const onValueChange = useCallback(
|
|
26
|
+
(value: string) => {
|
|
27
|
+
const index = Number(value)
|
|
28
|
+
onChange?.(index, values[index] ?? '')
|
|
29
|
+
},
|
|
30
|
+
[onChange, values]
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<NativeSegmentedControl
|
|
35
|
+
activePillColor={theme.color('fill.accent.primary')}
|
|
36
|
+
activeTextColor={theme.color('content.on.bg')}
|
|
37
|
+
disabled={disabled}
|
|
38
|
+
fontFamily={face.fontFamily}
|
|
39
|
+
fontSize={face.fontSize}
|
|
40
|
+
inactiveTextColor={theme.color('content.primary')}
|
|
41
|
+
onValueChange={onValueChange}
|
|
42
|
+
options={options}
|
|
43
|
+
selectedValue={String(selectedIndex)}
|
|
44
|
+
testID={testID}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A row of mutually exclusive segments, the selected one in the accent — kara's own `UISegmentedControl` on iOS,
|
|
5
|
+
* Material's single-choice segmented buttons on Android. It needs no `Screen` or `Island`: iOS is a plain view, Android
|
|
6
|
+
* brings its own host. It takes the width it is given; the height is the platform's.
|
|
7
|
+
*/
|
|
3
8
|
export type SegmentedControlProps = {
|
|
9
|
+
/** Stops the user changing the segment, and dims the control. */
|
|
4
10
|
disabled?: boolean
|
|
11
|
+
/**
|
|
12
|
+
* Called with the index and label of the segment the user picked — never for a tap on the one already selected.
|
|
13
|
+
* Android plays `selection`; iOS's control plays its own feedback.
|
|
14
|
+
*/
|
|
5
15
|
onChange?: (index: number, value: string) => void
|
|
16
|
+
/** The selected segment. Controlled: pass back what `onChange` reports. */
|
|
6
17
|
selectedIndex: number
|
|
18
|
+
/** Test identifier, on the whole control — reach a segment by its label. */
|
|
7
19
|
testID?: string
|
|
20
|
+
/** The segments' labels, in order. */
|
|
8
21
|
values: string[]
|
|
9
22
|
}
|
|
10
23
|
|
|
@@ -5,7 +5,7 @@ import {useAnimatedStyle, useSharedValue, withRepeat, withTiming} from 'react-na
|
|
|
5
5
|
import {ABox} from '../box'
|
|
6
6
|
import type {SkeletonComponent, SkeletonProps} from './skeleton.types'
|
|
7
7
|
|
|
8
|
-
export const Skeleton: SkeletonComponent = ({borderRadius = 'small',
|
|
8
|
+
export const Skeleton: SkeletonComponent = ({borderRadius = 'small', h, testID, w}: SkeletonProps) => {
|
|
9
9
|
const pulse = useSharedValue<number>(opacity.o40)
|
|
10
10
|
|
|
11
11
|
useEffect(() => {
|
|
@@ -18,11 +18,11 @@ export const Skeleton: SkeletonComponent = ({borderRadius = 'small', height, tes
|
|
|
18
18
|
<ABox
|
|
19
19
|
bg="fill.quaternary"
|
|
20
20
|
borderRadius={borderRadius}
|
|
21
|
-
h={
|
|
21
|
+
h={h}
|
|
22
22
|
style={rStyle}
|
|
23
23
|
testID={testID}
|
|
24
|
-
w={
|
|
25
|
-
wide={
|
|
24
|
+
w={w}
|
|
25
|
+
wide={w === undefined}
|
|
26
26
|
/>
|
|
27
27
|
)
|
|
28
28
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import type {SxDimension} from '@hzblj/kara-sx'
|
|
1
2
|
import type {RadiiSize} from '@hzblj/kara-theme'
|
|
2
3
|
import type {ReactNode} from 'react'
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A loading placeholder that pulses in `fill.quaternary` — a React Native view, so it needs no host. Sized with the
|
|
7
|
+
* `sx` names: `w`, `h`, `borderRadius`.
|
|
8
|
+
*/
|
|
4
9
|
export type SkeletonProps = {
|
|
10
|
+
/** The corner radius. `small` by default. */
|
|
5
11
|
borderRadius?: RadiiSize
|
|
6
|
-
|
|
12
|
+
/** In points. */
|
|
13
|
+
h: number
|
|
14
|
+
/** Test identifier, forwarded to the view. */
|
|
7
15
|
testID?: string
|
|
8
|
-
|
|
16
|
+
/** In points, or a percentage of the row (`60%` for a line that ends early). Unset, it fills the row. */
|
|
17
|
+
w?: SxDimension
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
export type SkeletonComponent = (props: SkeletonProps) => ReactNode
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/** SwiftUI `Slider` / Compose's Material `Slider`, tinted `fill.accent.primary`. */
|
|
3
4
|
export type SliderProps = {
|
|
5
|
+
/** Stops the user moving the thumb, and dims the slider. */
|
|
4
6
|
disabled?: boolean
|
|
7
|
+
/** The upper end. `1` by default. */
|
|
5
8
|
max?: number
|
|
9
|
+
/** The lower end. `0` by default. */
|
|
6
10
|
min?: number
|
|
11
|
+
/** Called with the new value as the thumb moves. */
|
|
7
12
|
onValueChange?: (value: number) => void
|
|
13
|
+
/** The increment between stops; leave it unset for a continuous slider. On Android every stop plays a `tick`. */
|
|
8
14
|
step?: number
|
|
15
|
+
/** Between `min` and `max`. Controlled: pass back what `onValueChange` reports. */
|
|
9
16
|
value: number
|
|
10
17
|
}
|
|
11
18
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type {SpaceSize} from '@hzblj/kara-theme'
|
|
2
2
|
import type {ReactNode} from 'react'
|
|
3
3
|
|
|
4
|
+
/** Takes up the free space in a stack — SwiftUI `Spacer` / Compose `Spacer` with `weight(1)`. */
|
|
4
5
|
export type SpacerProps = {
|
|
6
|
+
/** The least room it takes. iOS holds it along the stack's axis; Android as a minimum height only. */
|
|
5
7
|
min?: SpaceSize
|
|
8
|
+
/** iOS: forwarded to the native view. Android ignores it. */
|
|
6
9
|
testID?: string
|
|
7
10
|
}
|
|
8
11
|
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type {ResponsiveSx} from '@hzblj/kara-sx'
|
|
2
2
|
import type {ReactNode} from 'react'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* A container padded `s16` and rounded `large` unless told otherwise — a SwiftUI `VStack` on iOS, Material's
|
|
6
|
+
* `Surface` around a `Column` on Android.
|
|
7
|
+
*/
|
|
4
8
|
export type SurfaceProps = ResponsiveSx & {
|
|
9
|
+
/** Stacked vertically, from the leading edge. */
|
|
5
10
|
children?: ReactNode
|
|
11
|
+
/** iOS wraps the content in a `GlassEffectContainer`; Android raises it to a tonal elevation of `3`. */
|
|
6
12
|
glass?: boolean
|
|
13
|
+
/** Test identifier, forwarded to the native view. */
|
|
7
14
|
testID?: string
|
|
8
15
|
}
|
|
9
16
|
|
|
@@ -7,6 +7,9 @@ import {haptics} from '../../haptics'
|
|
|
7
7
|
import {useHostGuard} from '../../host-context'
|
|
8
8
|
import type {SwitchComponent, SwitchProps} from './switch.types'
|
|
9
9
|
|
|
10
|
+
/** Stands in for a missing handler: Material drops its 48dp touch target without one, and the switch moves. */
|
|
11
|
+
const NOOP = () => undefined
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
14
|
* Material's switch, tinted with the accent like SwiftUI's toggle. With a label it is a full-width row — label at the
|
|
12
15
|
* start, switch at the end — and the whole row toggles, the way a Material settings row does. A flip plays
|
|
@@ -35,7 +38,7 @@ export const Switch: SwitchComponent = ({disabled = false, label, onValueChange,
|
|
|
35
38
|
checkedTrackColor: theme.color('fill.accent.primary'),
|
|
36
39
|
}}
|
|
37
40
|
enabled={!disabled}
|
|
38
|
-
onCheckedChange={onValueChange ? toggle :
|
|
41
|
+
onCheckedChange={onValueChange ? toggle : NOOP}
|
|
39
42
|
value={value}
|
|
40
43
|
/>
|
|
41
44
|
)
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/** SwiftUI `Toggle` / Compose `Switch`, tinted `fill.accent.primary`. */
|
|
3
4
|
export type SwitchProps = {
|
|
5
|
+
/** Stops the user flipping it, and dims it. */
|
|
4
6
|
disabled?: boolean
|
|
7
|
+
/**
|
|
8
|
+
* Shown beside the switch. On Android it makes a full-width row — label at the start, switch at the end — that
|
|
9
|
+
* toggles wherever it is tapped.
|
|
10
|
+
*/
|
|
5
11
|
label?: string
|
|
12
|
+
/** Called with the new value when the user flips it. On Android a flip plays `toggleOn` / `toggleOff`. */
|
|
6
13
|
onValueChange?: (value: boolean) => void
|
|
14
|
+
/** Whether it is on. Controlled: pass back what `onValueChange` reports. */
|
|
7
15
|
value: boolean
|
|
8
16
|
}
|
|
9
17
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {Text as ComposeText} from '@expo/ui/jetpack-compose'
|
|
2
2
|
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
|
+
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
3
4
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
5
|
import {useMemo} from 'react'
|
|
5
6
|
|
|
@@ -32,11 +33,16 @@ export const Text: TextComponent = ({
|
|
|
32
33
|
testID,
|
|
33
34
|
transform,
|
|
34
35
|
variant = 'body',
|
|
36
|
+
...sx
|
|
35
37
|
}: TextProps) => {
|
|
36
38
|
useHostGuard('Text')
|
|
37
39
|
const theme = useAppTheme()
|
|
38
40
|
const style = theme.typography[variant]
|
|
39
|
-
const
|
|
41
|
+
const sxModifiers = useSxModifiers(sx)
|
|
42
|
+
const modifiers = useMemo(
|
|
43
|
+
() => (testID ? [...sxModifiers, testIdModifier(testID)] : sxModifiers),
|
|
44
|
+
[sxModifiers, testID]
|
|
45
|
+
)
|
|
40
46
|
|
|
41
47
|
return (
|
|
42
48
|
<ComposeText
|
|
@@ -10,11 +10,12 @@ import {
|
|
|
10
10
|
underline,
|
|
11
11
|
} from '@expo/ui/swift-ui/modifiers'
|
|
12
12
|
import {scaledFont} from '@hzblj/kara-native/swift-ui'
|
|
13
|
+
import {type FrameAlignment, useSxModifiers} from '@hzblj/kara-sx'
|
|
13
14
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
14
15
|
import {useMemo} from 'react'
|
|
15
16
|
|
|
16
17
|
import {useHostGuard} from '../../host-context'
|
|
17
|
-
import type {TextComponent, TextProps} from './text.types'
|
|
18
|
+
import type {TextAlign, TextComponent, TextProps} from './text.types'
|
|
18
19
|
|
|
19
20
|
const ALIGNMENT = {
|
|
20
21
|
center: 'center',
|
|
@@ -22,6 +23,13 @@ const ALIGNMENT = {
|
|
|
22
23
|
right: 'trailing',
|
|
23
24
|
} as const
|
|
24
25
|
|
|
26
|
+
/** Where a frame larger than the text — `wide`, `w`, `h` — puts it: on the side its lines align to, centred vertically. */
|
|
27
|
+
const FRAME_ALIGNMENT = {
|
|
28
|
+
center: 'center',
|
|
29
|
+
left: 'leading',
|
|
30
|
+
right: 'trailing',
|
|
31
|
+
} as const satisfies Record<TextAlign, FrameAlignment>
|
|
32
|
+
|
|
25
33
|
export const Text: TextComponent = ({
|
|
26
34
|
align,
|
|
27
35
|
children,
|
|
@@ -31,10 +39,13 @@ export const Text: TextComponent = ({
|
|
|
31
39
|
testID,
|
|
32
40
|
transform,
|
|
33
41
|
variant = 'body',
|
|
42
|
+
...sx
|
|
34
43
|
}: TextProps) => {
|
|
35
44
|
useHostGuard('Text')
|
|
36
45
|
const theme = useAppTheme()
|
|
37
46
|
const style = theme.typography[variant]
|
|
47
|
+
// After the text's own modifiers: the font and colour belong to the glyphs, the padding and frame to the box around.
|
|
48
|
+
const sxModifiers = useSxModifiers(sx, FRAME_ALIGNMENT[align ?? 'left'])
|
|
38
49
|
|
|
39
50
|
const modifiers = useMemo(() => {
|
|
40
51
|
const list: ModifierConfig[] = [
|
|
@@ -65,8 +76,8 @@ export const Text: TextComponent = ({
|
|
|
65
76
|
list.push(lineLimit(numberOfLines))
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
return list
|
|
69
|
-
}, [align, color, decoration, numberOfLines, style, theme, transform])
|
|
79
|
+
return [...list, ...sxModifiers]
|
|
80
|
+
}, [align, color, decoration, numberOfLines, style, sxModifiers, theme, transform])
|
|
70
81
|
|
|
71
82
|
return (
|
|
72
83
|
<SwiftUIText modifiers={modifiers} testID={testID}>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type {ResponsiveSx} from '@hzblj/kara-sx'
|
|
1
2
|
import type {ColorToken, TextVariant} from '@hzblj/kara-theme'
|
|
2
3
|
import type {ReactNode} from 'react'
|
|
3
4
|
|
|
@@ -7,14 +8,30 @@ export type TextDecoration = 'none' | 'strikethrough' | 'underline'
|
|
|
7
8
|
|
|
8
9
|
export type TextTransform = 'lowercase' | 'uppercase'
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* SwiftUI `Text` / Compose `Text` in a theme typography variant — a native atom; outside a host, use `RnText`. It takes
|
|
13
|
+
* the native `sx` surface too: padding, a frame (`wide` lines it up by `align`), a background, and the `ios` bucket's
|
|
14
|
+
* `contentTransition`, `animation` and `monospacedDigit` for a number that changes in place.
|
|
15
|
+
*/
|
|
16
|
+
export type TextProps = ResponsiveSx & {
|
|
17
|
+
/**
|
|
18
|
+
* How its lines align. iOS reads `left` and `right` as leading and trailing, so they flip in a right-to-left
|
|
19
|
+
* layout; Android keeps them as they are.
|
|
20
|
+
*/
|
|
11
21
|
align?: TextAlign
|
|
22
|
+
/** The text. */
|
|
12
23
|
children?: ReactNode
|
|
24
|
+
/** The text's colour. `content.primary` by default. */
|
|
13
25
|
color?: ColorToken
|
|
26
|
+
/** An underline or a strikethrough. */
|
|
14
27
|
decoration?: TextDecoration
|
|
28
|
+
/** The most lines it wraps to. iOS truncates the last with an ellipsis; Android clips it. */
|
|
15
29
|
numberOfLines?: number
|
|
30
|
+
/** Test identifier, forwarded to the native view. */
|
|
16
31
|
testID?: string
|
|
32
|
+
/** Upper- or lower-cases the text. Android applies it only when `children` is a plain string. */
|
|
17
33
|
transform?: TextTransform
|
|
34
|
+
/** The typography variant. `body` by default. */
|
|
18
35
|
variant?: TextVariant
|
|
19
36
|
}
|
|
20
37
|
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* An uncontrolled text field with an optional label above it — SwiftUI `TextField` (`SecureField` when `secure`) /
|
|
5
|
+
* Compose `OutlinedTextField`.
|
|
6
|
+
*/
|
|
3
7
|
export type TextFieldProps = {
|
|
8
|
+
/** The text it starts with. Read once, on mount — the field is uncontrolled. */
|
|
4
9
|
defaultValue?: string
|
|
10
|
+
/** Stops editing and dims the field. On iOS it outranks `error`. */
|
|
5
11
|
disabled?: boolean
|
|
12
|
+
/** Marks the field invalid: a destructive outline and label. */
|
|
6
13
|
error?: boolean
|
|
14
|
+
/** Shown above the field, in `labelMedium`. */
|
|
7
15
|
label?: string
|
|
16
|
+
/** Wraps the text and grows the field to fit it. iOS ignores it when `secure`. */
|
|
8
17
|
multiline?: boolean
|
|
18
|
+
/** Called with the whole text after every change. */
|
|
9
19
|
onChangeText?: (text: string) => void
|
|
20
|
+
/** Masks the text, for a password. */
|
|
10
21
|
secure?: boolean
|
|
22
|
+
/** iOS: forwarded to the native field. Android ignores it. */
|
|
11
23
|
testID?: string
|
|
12
24
|
}
|
|
13
25
|
|
|
@@ -4,11 +4,20 @@ import type {ReactNode} from 'react'
|
|
|
4
4
|
|
|
5
5
|
export type VStackAlign = 'center' | 'end' | 'start'
|
|
6
6
|
|
|
7
|
+
/** Stacks its children top to bottom — SwiftUI `VStack` / Compose `Column`. */
|
|
7
8
|
export type VStackProps = ResponsiveSx & {
|
|
9
|
+
/** The children's horizontal alignment. `start` by default. */
|
|
8
10
|
align?: VStackAlign
|
|
11
|
+
/** Laid out top to bottom, `spacing` apart. */
|
|
9
12
|
children?: ReactNode
|
|
13
|
+
/**
|
|
14
|
+
* Makes the whole stack pressable. iOS adds a tap gesture with no press feedback; Android a `clickable` whose ripple
|
|
15
|
+
* fills the stack's shape.
|
|
16
|
+
*/
|
|
10
17
|
onPress?: () => void
|
|
18
|
+
/** The space between children. */
|
|
11
19
|
spacing?: SpaceSize
|
|
20
|
+
/** Test identifier, forwarded to the native view. */
|
|
12
21
|
testID?: string
|
|
13
22
|
}
|
|
14
23
|
|