@hzblj/kara-components 0.17.0 → 0.18.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 +49 -0
- package/lib/module/atoms/badge/badge.android.js +5 -0
- package/lib/module/atoms/badge/badge.android.js.map +1 -1
- package/lib/module/atoms/badge/badge.ios.js +2 -0
- package/lib/module/atoms/badge/badge.ios.js.map +1 -1
- package/lib/module/atoms/button/button-tokens.js +8 -0
- package/lib/module/atoms/button/button-tokens.js.map +1 -1
- package/lib/module/atoms/button/button.android.js +43 -15
- package/lib/module/atoms/button/button.android.js.map +1 -1
- package/lib/module/atoms/button/button.ios.js +66 -10
- package/lib/module/atoms/button/button.ios.js.map +1 -1
- package/lib/module/atoms/checkbox/checkbox.android.js +5 -1
- package/lib/module/atoms/checkbox/checkbox.android.js.map +1 -1
- package/lib/module/atoms/checkbox/checkbox.ios.js +3 -1
- package/lib/module/atoms/checkbox/checkbox.ios.js.map +1 -1
- package/lib/module/atoms/color-picker/color-picker.android.js +4 -0
- package/lib/module/atoms/color-picker/color-picker.android.js.map +1 -1
- package/lib/module/atoms/color-picker/color-picker.ios.js +24 -2
- package/lib/module/atoms/color-picker/color-picker.ios.js.map +1 -1
- package/lib/module/atoms/date-picker/date-picker.android.js +85 -10
- package/lib/module/atoms/date-picker/date-picker.android.js.map +1 -1
- package/lib/module/atoms/date-picker/date-picker.ios.js +9 -7
- package/lib/module/atoms/date-picker/date-picker.ios.js.map +1 -1
- package/lib/module/atoms/divider/divider.android.js +17 -4
- package/lib/module/atoms/divider/divider.android.js.map +1 -1
- package/lib/module/atoms/divider/divider.ios.js +4 -0
- package/lib/module/atoms/divider/divider.ios.js.map +1 -1
- package/lib/module/atoms/fab/fab.android.js +64 -8
- package/lib/module/atoms/fab/fab.android.js.map +1 -1
- package/lib/module/atoms/fab/fab.ios.js +68 -12
- package/lib/module/atoms/fab/fab.ios.js.map +1 -1
- package/lib/module/atoms/icon-button/icon-button.android.js +48 -5
- package/lib/module/atoms/icon-button/icon-button.android.js.map +1 -1
- package/lib/module/atoms/icon-button/icon-button.ios.js +60 -10
- package/lib/module/atoms/icon-button/icon-button.ios.js.map +1 -1
- package/lib/module/atoms/list/list.android.js +32 -4
- package/lib/module/atoms/list/list.android.js.map +1 -1
- package/lib/module/atoms/list/list.ios.js +15 -5
- package/lib/module/atoms/list/list.ios.js.map +1 -1
- package/lib/module/atoms/progress/progress.android.js +21 -7
- package/lib/module/atoms/progress/progress.android.js.map +1 -1
- package/lib/module/atoms/progress/progress.ios.js +5 -1
- package/lib/module/atoms/progress/progress.ios.js.map +1 -1
- package/lib/module/atoms/slider/slider.android.js +16 -2
- package/lib/module/atoms/slider/slider.android.js.map +1 -1
- package/lib/module/atoms/slider/slider.ios.js +22 -2
- package/lib/module/atoms/slider/slider.ios.js.map +1 -1
- package/lib/module/atoms/switch/switch.android.js +5 -1
- package/lib/module/atoms/switch/switch.android.js.map +1 -1
- package/lib/module/atoms/switch/switch.ios.js +3 -1
- package/lib/module/atoms/switch/switch.ios.js.map +1 -1
- package/lib/module/atoms/text/spans.js +2 -1
- package/lib/module/atoms/text/spans.js.map +1 -1
- package/lib/module/atoms/text/text.android.js +18 -6
- package/lib/module/atoms/text/text.android.js.map +1 -1
- package/lib/module/atoms/text/text.ios.js +6 -2
- package/lib/module/atoms/text/text.ios.js.map +1 -1
- package/lib/module/atoms/text-field/text-field-input.js +14 -0
- package/lib/module/atoms/text-field/text-field-input.js.map +1 -0
- package/lib/module/atoms/text-field/text-field.android.js +113 -6
- package/lib/module/atoms/text-field/text-field.android.js.map +1 -1
- package/lib/module/atoms/text-field/text-field.ios.js +75 -3
- package/lib/module/atoms/text-field/text-field.ios.js.map +1 -1
- package/lib/module/molecules/collapsible/collapsible.android.js +4 -3
- package/lib/module/molecules/collapsible/collapsible.android.js.map +1 -1
- package/lib/module/molecules/collapsible/collapsible.ios.js +3 -1
- package/lib/module/molecules/collapsible/collapsible.ios.js.map +1 -1
- package/lib/module/molecules/radio-group/radio-group.android.js +10 -2
- package/lib/module/molecules/radio-group/radio-group.android.js.map +1 -1
- package/lib/module/molecules/radio-group/radio-group.ios.js +6 -2
- package/lib/module/molecules/radio-group/radio-group.ios.js.map +1 -1
- package/lib/module/molecules/search-bar/search-bar.js +9 -0
- package/lib/module/molecules/search-bar/search-bar.js.map +1 -1
- package/lib/module/molecules/stepper/stepper.android.js +25 -13
- package/lib/module/molecules/stepper/stepper.android.js.map +1 -1
- package/lib/module/molecules/stepper/stepper.ios.js +12 -6
- package/lib/module/molecules/stepper/stepper.ios.js.map +1 -1
- package/lib/module/organisms/action-sheet/action-sheet.android.js +16 -8
- package/lib/module/organisms/action-sheet/action-sheet.android.js.map +1 -1
- package/lib/module/organisms/action-sheet/action-sheet.ios.js +16 -3
- package/lib/module/organisms/action-sheet/action-sheet.ios.js.map +1 -1
- package/lib/module/organisms/alert/alert.android.js +4 -3
- package/lib/module/organisms/alert/alert.android.js.map +1 -1
- package/lib/module/organisms/alert/alert.ios.js +3 -2
- package/lib/module/organisms/alert/alert.ios.js.map +1 -1
- package/lib/module/organisms/context-menu/context-menu.android.js +3 -1
- package/lib/module/organisms/context-menu/context-menu.android.js.map +1 -1
- package/lib/module/organisms/context-menu/context-menu.ios.js +1 -0
- package/lib/module/organisms/context-menu/context-menu.ios.js.map +1 -1
- package/lib/module/organisms/fab-menu/fab-menu.android.js +3 -0
- package/lib/module/organisms/fab-menu/fab-menu.android.js.map +1 -1
- package/lib/module/organisms/fab-menu/fab-menu.ios.js +3 -0
- package/lib/module/organisms/fab-menu/fab-menu.ios.js.map +1 -1
- package/lib/module/organisms/menu/menu-parts.js +20 -0
- package/lib/module/organisms/menu/menu-parts.js.map +1 -0
- package/lib/module/organisms/menu/menu.android.js +51 -19
- package/lib/module/organisms/menu/menu.android.js.map +1 -1
- package/lib/module/organisms/menu/menu.ios.js +71 -8
- package/lib/module/organisms/menu/menu.ios.js.map +1 -1
- package/lib/module/organisms/sheet/sheet.js +2 -1
- package/lib/module/organisms/sheet/sheet.js.map +1 -1
- package/lib/typescript/atoms/badge/badge.types.d.ts +2 -0
- package/lib/typescript/atoms/button/button-tokens.d.ts +6 -2
- package/lib/typescript/atoms/button/button.types.d.ts +19 -5
- package/lib/typescript/atoms/checkbox/checkbox.types.d.ts +2 -0
- package/lib/typescript/atoms/color-picker/color-picker.types.d.ts +2 -0
- package/lib/typescript/atoms/date-picker/date-picker.ios.d.ts +5 -5
- package/lib/typescript/atoms/date-picker/date-picker.types.d.ts +30 -7
- package/lib/typescript/atoms/divider/divider.ios.d.ts +4 -0
- package/lib/typescript/atoms/divider/divider.types.d.ts +13 -2
- package/lib/typescript/atoms/fab/fab.types.d.ts +21 -4
- package/lib/typescript/atoms/icon-button/icon-button.types.d.ts +23 -4
- package/lib/typescript/atoms/list/list.types.d.ts +15 -1
- package/lib/typescript/atoms/progress/progress.types.d.ts +13 -2
- package/lib/typescript/atoms/slider/slider.ios.d.ts +4 -0
- package/lib/typescript/atoms/slider/slider.types.d.ts +7 -0
- package/lib/typescript/atoms/switch/switch.types.d.ts +2 -0
- package/lib/typescript/atoms/text/spans.d.ts +2 -0
- package/lib/typescript/atoms/text/text.types.d.ts +9 -1
- package/lib/typescript/atoms/text-field/text-field-input.d.ts +7 -0
- package/lib/typescript/atoms/text-field/text-field.types.d.ts +77 -4
- package/lib/typescript/molecules/collapsible/collapsible.types.d.ts +2 -0
- package/lib/typescript/molecules/radio-group/radio-group.types.d.ts +4 -0
- package/lib/typescript/molecules/search-bar/search-bar.types.d.ts +11 -1
- package/lib/typescript/molecules/stepper/stepper.ios.d.ts +2 -1
- package/lib/typescript/molecules/stepper/stepper.types.d.ts +17 -0
- package/lib/typescript/organisms/action-sheet/action-sheet.ios.d.ts +4 -0
- package/lib/typescript/organisms/action-sheet/action-sheet.types.d.ts +8 -3
- package/lib/typescript/organisms/alert/alert.types.d.ts +8 -3
- package/lib/typescript/organisms/menu/menu-parts.d.ts +14 -0
- package/lib/typescript/organisms/menu/menu.types.d.ts +35 -7
- package/lib/typescript/organisms/sheet/sheet.types.d.ts +7 -1
- package/package.json +4 -4
- package/src/atoms/badge/badge.android.tsx +9 -2
- package/src/atoms/badge/badge.ios.tsx +2 -2
- package/src/atoms/badge/badge.types.ts +2 -0
- package/src/atoms/button/button-tokens.ts +6 -3
- package/src/atoms/button/button.android.tsx +35 -11
- package/src/atoms/button/button.ios.tsx +72 -8
- package/src/atoms/button/button.types.ts +20 -5
- package/src/atoms/checkbox/checkbox.android.tsx +5 -2
- package/src/atoms/checkbox/checkbox.ios.tsx +2 -2
- package/src/atoms/checkbox/checkbox.types.ts +2 -0
- package/src/atoms/color-picker/color-picker.android.tsx +10 -2
- package/src/atoms/color-picker/color-picker.ios.tsx +24 -1
- package/src/atoms/color-picker/color-picker.types.ts +2 -0
- package/src/atoms/date-picker/date-picker.android.tsx +86 -16
- package/src/atoms/date-picker/date-picker.ios.tsx +20 -8
- package/src/atoms/date-picker/date-picker.types.ts +31 -7
- package/src/atoms/divider/divider.android.tsx +14 -3
- package/src/atoms/divider/divider.ios.tsx +4 -0
- package/src/atoms/divider/divider.types.ts +14 -2
- package/src/atoms/fab/fab.android.tsx +61 -11
- package/src/atoms/fab/fab.ios.tsx +48 -13
- package/src/atoms/fab/fab.types.ts +22 -4
- package/src/atoms/icon-button/icon-button.android.tsx +60 -10
- package/src/atoms/icon-button/icon-button.ios.tsx +77 -8
- package/src/atoms/icon-button/icon-button.types.ts +24 -4
- package/src/atoms/list/list.android.tsx +26 -4
- package/src/atoms/list/list.ios.tsx +20 -5
- package/src/atoms/list/list.types.ts +16 -1
- package/src/atoms/progress/progress.android.tsx +32 -6
- package/src/atoms/progress/progress.ios.tsx +11 -5
- package/src/atoms/progress/progress.types.ts +13 -2
- package/src/atoms/slider/slider.android.tsx +18 -2
- package/src/atoms/slider/slider.ios.tsx +38 -2
- package/src/atoms/slider/slider.types.ts +7 -0
- package/src/atoms/switch/switch.android.tsx +5 -2
- package/src/atoms/switch/switch.ios.tsx +2 -2
- package/src/atoms/switch/switch.types.ts +2 -0
- package/src/atoms/text/spans.ts +2 -1
- package/src/atoms/text/text.android.tsx +20 -7
- package/src/atoms/text/text.ios.tsx +7 -0
- package/src/atoms/text/text.types.ts +10 -1
- package/src/atoms/text-field/text-field-input.ts +17 -0
- package/src/atoms/text-field/text-field.android.tsx +132 -9
- package/src/atoms/text-field/text-field.ios.tsx +110 -6
- package/src/atoms/text-field/text-field.types.ts +98 -4
- package/src/molecules/collapsible/collapsible.android.tsx +10 -2
- package/src/molecules/collapsible/collapsible.ios.tsx +8 -2
- package/src/molecules/collapsible/collapsible.types.ts +2 -0
- package/src/molecules/radio-group/radio-group.android.tsx +18 -4
- package/src/molecules/radio-group/radio-group.ios.tsx +19 -5
- package/src/molecules/radio-group/radio-group.types.ts +4 -0
- package/src/molecules/search-bar/search-bar.tsx +9 -0
- package/src/molecules/search-bar/search-bar.types.ts +12 -1
- package/src/molecules/stepper/stepper.android.tsx +24 -4
- package/src/molecules/stepper/stepper.ios.tsx +23 -8
- package/src/molecules/stepper/stepper.types.ts +17 -0
- package/src/organisms/action-sheet/action-sheet.android.tsx +23 -11
- package/src/organisms/action-sheet/action-sheet.ios.tsx +20 -3
- package/src/organisms/action-sheet/action-sheet.types.ts +8 -3
- package/src/organisms/alert/alert.android.tsx +11 -8
- package/src/organisms/alert/alert.ios.tsx +2 -1
- package/src/organisms/alert/alert.types.ts +8 -3
- package/src/organisms/context-menu/context-menu.android.tsx +3 -2
- package/src/organisms/context-menu/context-menu.ios.tsx +1 -0
- package/src/organisms/fab-menu/fab-menu.android.tsx +3 -0
- package/src/organisms/fab-menu/fab-menu.ios.tsx +9 -1
- package/src/organisms/menu/menu-parts.ts +21 -0
- package/src/organisms/menu/menu.android.tsx +39 -10
- package/src/organisms/menu/menu.ios.tsx +55 -5
- package/src/organisms/menu/menu.types.ts +30 -7
- package/src/organisms/sheet/sheet.tsx +2 -1
- package/src/organisms/sheet/sheet.types.ts +7 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {LazyColumn, PullToRefreshBox} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
3
|
import {useSxModifiers} from '@hzblj/kara-sx'
|
|
3
|
-
import {
|
|
4
|
+
import {spacing} from '@hzblj/kara-theme'
|
|
5
|
+
import {Children, Fragment, isValidElement, useMemo} from 'react'
|
|
4
6
|
import {useHostGuard} from '../../host-context'
|
|
5
7
|
import {useRefreshState} from '../../use-refresh-state'
|
|
8
|
+
import {Card} from '../card'
|
|
6
9
|
import {Divider} from '../divider'
|
|
7
10
|
import type {ListComponent, ListProps} from './list.types'
|
|
8
11
|
|
|
@@ -15,12 +18,31 @@ const withSeparators = (children: ListProps['children']) =>
|
|
|
15
18
|
</Fragment>
|
|
16
19
|
))
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
/** The card's inset from the column's edges, as `Form` keeps its cards off the screen's. */
|
|
22
|
+
const INSET = {bottom: spacing.s16, end: spacing.s16, start: spacing.s16, top: spacing.s16}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `plain` is the column as it always was. `insetGrouped` sets the rows in one `Card`, the way `Form` draws a section,
|
|
26
|
+
* inset by the column's content padding: the card is a single item of the column, so its rows are composed together
|
|
27
|
+
* rather than as they scroll in. A long list that wants laziness stays `plain`.
|
|
28
|
+
*/
|
|
29
|
+
export const List: ListComponent = ({children, onRefresh, separators = true, testID, variant = 'plain'}: ListProps) => {
|
|
19
30
|
useHostGuard('List')
|
|
20
|
-
const
|
|
31
|
+
const sxModifiers = useSxModifiers({wide: true})
|
|
32
|
+
// On the column, not the refresh box around it: the column is what scrolls.
|
|
33
|
+
const modifiers = useMemo(
|
|
34
|
+
() => (testID ? [...sxModifiers, testIdModifier(testID)] : sxModifiers),
|
|
35
|
+
[sxModifiers, testID]
|
|
36
|
+
)
|
|
21
37
|
const {isRefreshing, refresh} = useRefreshState(onRefresh)
|
|
38
|
+
const rows = separators ? withSeparators(children) : children
|
|
39
|
+
const grouped = variant === 'insetGrouped'
|
|
22
40
|
|
|
23
|
-
const column =
|
|
41
|
+
const column = (
|
|
42
|
+
<LazyColumn contentPadding={grouped ? INSET : undefined} modifiers={modifiers}>
|
|
43
|
+
{grouped && Children.toArray(children).length > 0 ? <Card wide>{rows}</Card> : rows}
|
|
44
|
+
</LazyColumn>
|
|
45
|
+
)
|
|
24
46
|
|
|
25
47
|
if (!onRefresh) {
|
|
26
48
|
return column
|
|
@@ -1,18 +1,33 @@
|
|
|
1
|
-
import {List as SwiftUIList} from '@expo/ui/swift-ui'
|
|
2
|
-
import {type ModifierConfig, refreshable} from '@expo/ui/swift-ui/modifiers'
|
|
1
|
+
import {Group, List as SwiftUIList} from '@expo/ui/swift-ui'
|
|
2
|
+
import {listRowSeparator, listStyle, type ModifierConfig, refreshable} from '@expo/ui/swift-ui/modifiers'
|
|
3
3
|
import {useMemo} from 'react'
|
|
4
4
|
|
|
5
5
|
import {useHostGuard} from '../../host-context'
|
|
6
6
|
import type {ListComponent, ListProps} from './list.types'
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* `listRowSeparator` is a row's preference, not the list's: set on the `List` it reaches no row. A `Group` hands a
|
|
10
|
+
* modifier to each of its children, so the rows go in one, and each of them hides its own separator.
|
|
11
|
+
*/
|
|
12
|
+
const NO_SEPARATORS = [listRowSeparator('hidden')]
|
|
13
|
+
|
|
14
|
+
export const List: ListComponent = ({
|
|
15
|
+
children,
|
|
16
|
+
onRefresh,
|
|
17
|
+
separators = true,
|
|
18
|
+
testID,
|
|
19
|
+
variant = 'insetGrouped',
|
|
20
|
+
}: ListProps) => {
|
|
9
21
|
useHostGuard('List')
|
|
10
22
|
|
|
11
|
-
const modifiers = useMemo<ModifierConfig[]>(
|
|
23
|
+
const modifiers = useMemo<ModifierConfig[]>(
|
|
24
|
+
() => [listStyle(variant), ...(onRefresh ? [refreshable(onRefresh)] : [])],
|
|
25
|
+
[onRefresh, variant]
|
|
26
|
+
)
|
|
12
27
|
|
|
13
28
|
return (
|
|
14
29
|
<SwiftUIList modifiers={modifiers} testID={testID}>
|
|
15
|
-
{children}
|
|
30
|
+
{separators ? children : <Group modifiers={NO_SEPARATORS}>{children}</Group>}
|
|
16
31
|
</SwiftUIList>
|
|
17
32
|
)
|
|
18
33
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type {ReactNode} from 'react'
|
|
2
2
|
|
|
3
|
+
/** How the rows are laid out: in a rounded card inset from the edges, or edge to edge. */
|
|
4
|
+
export type ListVariant = 'insetGrouped' | 'plain'
|
|
5
|
+
|
|
3
6
|
/**
|
|
4
7
|
* A scrolling list: SwiftUI `List`, Compose `LazyColumn`. It has no height of its own, so it needs one from outside:
|
|
5
8
|
* a `Screen`, or an `Island` with `matchContents={false}` and a height. In an island that sizes itself to its content
|
|
@@ -17,8 +20,20 @@ export type ListProps = {
|
|
|
17
20
|
* until the promise settles, so neither platform needs a `refreshing` flag kept in sync by hand.
|
|
18
21
|
*/
|
|
19
22
|
onRefresh?: () => Promise<void>
|
|
20
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Draws a line between rows. On by default. Off, iOS hides SwiftUI's row separators and Android draws no `Divider`,
|
|
25
|
+
* for rows that carry their own spacing (cards, media).
|
|
26
|
+
*/
|
|
27
|
+
separators?: boolean
|
|
28
|
+
/** Test identifier, forwarded to the native view. */
|
|
21
29
|
testID?: string
|
|
30
|
+
/**
|
|
31
|
+
* The layout. Each platform's own list by default: `insetGrouped` on iOS, `plain` on Android. `insetGrouped` sets
|
|
32
|
+
* the rows in a rounded card inset from the edges (SwiftUI's `insetGrouped` list style; on Android a `Card` inside the
|
|
33
|
+
* column, the way `Form` draws its sections). `plain` runs the rows edge to edge (SwiftUI's `plain` style; Android's
|
|
34
|
+
* column as it is).
|
|
35
|
+
*/
|
|
36
|
+
variant?: ListVariant
|
|
22
37
|
}
|
|
23
38
|
|
|
24
39
|
export type ListComponent = (props: ListProps) => ReactNode
|
|
@@ -1,19 +1,45 @@
|
|
|
1
1
|
import {CircularProgressIndicator, LinearProgressIndicator} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
3
|
import {useAccessibilityModifiers} from '@hzblj/kara-sx'
|
|
3
|
-
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
+
import {useAppTheme, withOpacity} from '@hzblj/kara-theme'
|
|
5
|
+
import {useMemo} from 'react'
|
|
4
6
|
|
|
5
7
|
import {useHostGuard} from '../../host-context'
|
|
6
8
|
import type {ProgressComponent, ProgressProps} from './progress.types'
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Material draws the track in the seeded scheme's `secondaryContainer`, a pale accent: under a red or a green bar it
|
|
12
|
+
* would read as a second colour. Any other colour gets its own faded track instead, the way the accent's is pale; the
|
|
13
|
+
* accent keeps Material's. An indeterminate ring has no track in Material, so it is left without one.
|
|
14
|
+
*/
|
|
15
|
+
export const Progress: ProgressComponent = ({
|
|
16
|
+
accessibilityLabel,
|
|
17
|
+
circular = false,
|
|
18
|
+
color = 'fill.accent.primary',
|
|
19
|
+
testID,
|
|
20
|
+
value,
|
|
21
|
+
}: ProgressProps) => {
|
|
9
22
|
useHostGuard('Progress')
|
|
10
23
|
const theme = useAppTheme()
|
|
11
|
-
const
|
|
12
|
-
|
|
24
|
+
const indicator = theme.color(color)
|
|
25
|
+
// The website reads the default above from the source, so it stays a literal rather than a shared constant.
|
|
26
|
+
const track = color === 'fill.accent.primary' ? undefined : withOpacity(String(indicator), 'o20')
|
|
27
|
+
const accessibilityModifiers = useAccessibilityModifiers({accessibilityLabel})
|
|
28
|
+
const modifiers = useMemo(
|
|
29
|
+
() => (testID ? [testIdModifier(testID), ...accessibilityModifiers] : accessibilityModifiers),
|
|
30
|
+
[accessibilityModifiers, testID]
|
|
31
|
+
)
|
|
13
32
|
|
|
14
33
|
if (circular) {
|
|
15
|
-
return
|
|
34
|
+
return (
|
|
35
|
+
<CircularProgressIndicator
|
|
36
|
+
color={indicator}
|
|
37
|
+
modifiers={modifiers}
|
|
38
|
+
progress={value}
|
|
39
|
+
trackColor={value === undefined ? undefined : track}
|
|
40
|
+
/>
|
|
41
|
+
)
|
|
16
42
|
}
|
|
17
43
|
|
|
18
|
-
return <LinearProgressIndicator color={
|
|
44
|
+
return <LinearProgressIndicator color={indicator} modifiers={modifiers} progress={value} trackColor={track} />
|
|
19
45
|
}
|
|
@@ -7,7 +7,13 @@ import {useMemo} from 'react'
|
|
|
7
7
|
import {useHostGuard} from '../../host-context'
|
|
8
8
|
import type {ProgressComponent, ProgressProps} from './progress.types'
|
|
9
9
|
|
|
10
|
-
export const Progress: ProgressComponent = ({
|
|
10
|
+
export const Progress: ProgressComponent = ({
|
|
11
|
+
accessibilityLabel,
|
|
12
|
+
circular = false,
|
|
13
|
+
color = 'fill.accent.primary',
|
|
14
|
+
testID,
|
|
15
|
+
value,
|
|
16
|
+
}: ProgressProps) => {
|
|
11
17
|
useHostGuard('Progress')
|
|
12
18
|
const theme = useAppTheme()
|
|
13
19
|
const accessibilityModifiers = useAccessibilityModifiers({accessibilityLabel})
|
|
@@ -16,16 +22,16 @@ export const Progress: ProgressComponent = ({accessibilityLabel, circular = fals
|
|
|
16
22
|
const modifiers = useMemo(
|
|
17
23
|
() => [
|
|
18
24
|
ring ? gaugeStyle('circularCapacity') : progressViewStyle(circular ? 'circular' : 'linear'),
|
|
19
|
-
tint(theme.color(
|
|
25
|
+
tint(theme.color(color)),
|
|
20
26
|
...accessibilityModifiers,
|
|
21
27
|
],
|
|
22
|
-
[accessibilityModifiers, circular, ring, theme]
|
|
28
|
+
[accessibilityModifiers, circular, color, ring, theme]
|
|
23
29
|
)
|
|
24
30
|
|
|
25
31
|
// SwiftUI's circular `ProgressView` ignores its value on iOS and spins; a ring that fills is a capacity `Gauge`.
|
|
26
32
|
return ring ? (
|
|
27
|
-
<Gauge modifiers={modifiers} value={value} />
|
|
33
|
+
<Gauge modifiers={modifiers} testID={testID} value={value} />
|
|
28
34
|
) : (
|
|
29
|
-
<ProgressView modifiers={modifiers} value={value ?? null} />
|
|
35
|
+
<ProgressView modifiers={modifiers} testID={testID} value={value ?? null} />
|
|
30
36
|
)
|
|
31
37
|
}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import type {ColorToken} from '@hzblj/kara-theme'
|
|
1
2
|
import type {ReactNode} from 'react'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
|
-
* A progress bar or ring, tinted `fill.accent.primary
|
|
5
|
-
* value, which `ProgressView` cannot draw on iOS) / Compose's `LinearProgressIndicator` and
|
|
5
|
+
* A progress bar or ring, tinted `fill.accent.primary` unless `color` names another: SwiftUI `ProgressView` (a capacity
|
|
6
|
+
* `Gauge` for a ring with a value, which `ProgressView` cannot draw on iOS) / Compose's `LinearProgressIndicator` and
|
|
7
|
+
* `CircularProgressIndicator`.
|
|
6
8
|
*/
|
|
7
9
|
export type ProgressProps = {
|
|
8
10
|
/** What is in progress, for VoiceOver and TalkBack: "Uploading". They read how far along it is themselves. */
|
|
9
11
|
accessibilityLabel?: string
|
|
10
12
|
/** A circular indicator instead of a bar. Off by default. */
|
|
11
13
|
circular?: boolean
|
|
14
|
+
/**
|
|
15
|
+
* Its colour: `fill.destructive.primary` for storage nearly full, `fill.success.primary` for a goal reached.
|
|
16
|
+
* `fill.accent.primary` by default. iOS tints the bar or ring with it. Android draws the indicator in it and, for any
|
|
17
|
+
* colour but the accent, the track in the same colour faded (Material's own track stays under the accent, and an
|
|
18
|
+
* indeterminate ring has none).
|
|
19
|
+
*/
|
|
20
|
+
color?: ColorToken
|
|
21
|
+
/** Test identifier, forwarded to the native view. */
|
|
22
|
+
testID?: string
|
|
12
23
|
/** Between `0` and `1`. Leave it unset for an indeterminate indicator. */
|
|
13
24
|
value?: number
|
|
14
25
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {Slider as ComposeSlider} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
3
|
import {haptics} from '@hzblj/expo-rich-haptics'
|
|
3
4
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
-
import {useCallback, useEffect, useRef} from 'react'
|
|
5
|
+
import {useCallback, useEffect, useMemo, useRef} from 'react'
|
|
5
6
|
|
|
6
7
|
import {useHostGuard} from '../../host-context'
|
|
7
8
|
import type {SliderComponent, SliderProps} from './slider.types'
|
|
@@ -9,14 +10,17 @@ import type {SliderComponent, SliderProps} from './slider.types'
|
|
|
9
10
|
/**
|
|
10
11
|
* Material's slider. `step` is the increment, as SwiftUI takes it; Compose counts the stops between the two ends
|
|
11
12
|
* instead, so it is converted. A stepped slider ticks at every stop the thumb reaches — Compose's Material slider
|
|
12
|
-
* plays nothing of its own, where iOS 26's slider already does.
|
|
13
|
+
* plays nothing of its own, where iOS 26's slider already does. `onValueChangeEnd` is Material's
|
|
14
|
+
* `onValueChangeFinished`, which reports no value: it gets the last one the drag reported.
|
|
13
15
|
*/
|
|
14
16
|
export const Slider: SliderComponent = ({
|
|
15
17
|
disabled = false,
|
|
16
18
|
max = 1,
|
|
17
19
|
min = 0,
|
|
18
20
|
onValueChange,
|
|
21
|
+
onValueChangeEnd,
|
|
19
22
|
step,
|
|
23
|
+
testID,
|
|
20
24
|
value,
|
|
21
25
|
}: SliderProps) => {
|
|
22
26
|
useHostGuard('Slider')
|
|
@@ -27,6 +31,7 @@ export const Slider: SliderComponent = ({
|
|
|
27
31
|
// Compose reports faster than React re-renders the controlled value.
|
|
28
32
|
const stopOf = useCallback((at: number) => (step ? Math.round((at - min) / step) : 0), [min, step])
|
|
29
33
|
const stop = useRef(stopOf(value))
|
|
34
|
+
const dragged = useRef<number | undefined>(undefined)
|
|
30
35
|
|
|
31
36
|
useEffect(() => {
|
|
32
37
|
stop.current = stopOf(value)
|
|
@@ -41,11 +46,20 @@ export const Slider: SliderComponent = ({
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
stop.current = reached
|
|
49
|
+
dragged.current = next
|
|
44
50
|
onValueChange?.(next)
|
|
45
51
|
},
|
|
46
52
|
[onValueChange, stopOf, steps]
|
|
47
53
|
)
|
|
48
54
|
|
|
55
|
+
const onFinished = useCallback(() => {
|
|
56
|
+
const final = dragged.current ?? value
|
|
57
|
+
dragged.current = undefined
|
|
58
|
+
onValueChangeEnd?.(final)
|
|
59
|
+
}, [onValueChangeEnd, value])
|
|
60
|
+
|
|
61
|
+
const modifiers = useMemo(() => (testID ? [testIdModifier(testID)] : undefined), [testID])
|
|
62
|
+
|
|
49
63
|
return (
|
|
50
64
|
<ComposeSlider
|
|
51
65
|
colors={{
|
|
@@ -55,7 +69,9 @@ export const Slider: SliderComponent = ({
|
|
|
55
69
|
enabled={!disabled}
|
|
56
70
|
max={max}
|
|
57
71
|
min={min}
|
|
72
|
+
modifiers={modifiers}
|
|
58
73
|
onValueChange={onChange}
|
|
74
|
+
onValueChangeFinished={onValueChangeEnd ? onFinished : undefined}
|
|
59
75
|
steps={steps}
|
|
60
76
|
value={value}
|
|
61
77
|
/>
|
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
import {Slider as SwiftUISlider} from '@expo/ui/swift-ui'
|
|
2
2
|
import {disabled as disabledModifier, tint} from '@expo/ui/swift-ui/modifiers'
|
|
3
3
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
-
import {useMemo} from 'react'
|
|
4
|
+
import {useCallback, useMemo, useRef} from 'react'
|
|
5
5
|
|
|
6
6
|
import {useHostGuard} from '../../host-context'
|
|
7
7
|
import type {SliderComponent, SliderProps} from './slider.types'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* SwiftUI's slider. `onValueChangeEnd` is its `onEditingChanged(false)`, with the last value the drag reported: the
|
|
11
|
+
* controlled `value` can still be a render behind when the finger lifts.
|
|
12
|
+
*/
|
|
9
13
|
export const Slider: SliderComponent = ({
|
|
10
14
|
disabled = false,
|
|
11
15
|
max = 1,
|
|
12
16
|
min = 0,
|
|
13
17
|
onValueChange,
|
|
18
|
+
onValueChangeEnd,
|
|
14
19
|
step,
|
|
20
|
+
testID,
|
|
15
21
|
value,
|
|
16
22
|
}: SliderProps) => {
|
|
17
23
|
useHostGuard('Slider')
|
|
18
24
|
const theme = useAppTheme()
|
|
25
|
+
const dragged = useRef<number | undefined>(undefined)
|
|
19
26
|
|
|
20
27
|
const modifiers = useMemo(() => {
|
|
21
28
|
const list = [tint(theme.color('fill.accent.primary'))]
|
|
@@ -27,7 +34,36 @@ export const Slider: SliderComponent = ({
|
|
|
27
34
|
return list
|
|
28
35
|
}, [disabled, theme])
|
|
29
36
|
|
|
37
|
+
const onChange = useCallback(
|
|
38
|
+
(next: number) => {
|
|
39
|
+
dragged.current = next
|
|
40
|
+
onValueChange?.(next)
|
|
41
|
+
},
|
|
42
|
+
[onValueChange]
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const onEditingChanged = useCallback(
|
|
46
|
+
(editing: boolean) => {
|
|
47
|
+
const final = dragged.current ?? value
|
|
48
|
+
dragged.current = undefined
|
|
49
|
+
|
|
50
|
+
if (!editing) {
|
|
51
|
+
onValueChangeEnd?.(final)
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[onValueChangeEnd, value]
|
|
55
|
+
)
|
|
56
|
+
|
|
30
57
|
return (
|
|
31
|
-
<SwiftUISlider
|
|
58
|
+
<SwiftUISlider
|
|
59
|
+
max={max}
|
|
60
|
+
min={min}
|
|
61
|
+
modifiers={modifiers}
|
|
62
|
+
onEditingChanged={onValueChangeEnd ? onEditingChanged : undefined}
|
|
63
|
+
onValueChange={onChange}
|
|
64
|
+
step={step}
|
|
65
|
+
testID={testID}
|
|
66
|
+
value={value}
|
|
67
|
+
/>
|
|
32
68
|
)
|
|
33
69
|
}
|
|
@@ -10,8 +10,15 @@ export type SliderProps = {
|
|
|
10
10
|
min?: number
|
|
11
11
|
/** Called with the new value as the thumb moves. */
|
|
12
12
|
onValueChange?: (value: number) => void
|
|
13
|
+
/**
|
|
14
|
+
* Called once with the final value when the user lets go of the slider: where a seek or a volume commits, rather
|
|
15
|
+
* than at every step of the drag.
|
|
16
|
+
*/
|
|
17
|
+
onValueChangeEnd?: (value: number) => void
|
|
13
18
|
/** The increment between stops; leave it unset for a continuous slider. On Android every stop plays a `tick`. */
|
|
14
19
|
step?: number
|
|
20
|
+
/** Test identifier, forwarded to the native view. */
|
|
21
|
+
testID?: string
|
|
15
22
|
/** Between `min` and `max`. Controlled: pass back what `onValueChange` reports. */
|
|
16
23
|
value: number
|
|
17
24
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Switch as ComposeSwitch, Row, Text} from '@expo/ui/jetpack-compose'
|
|
2
|
-
import {clickable, fillMaxWidth} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
|
+
import {clickable, fillMaxWidth, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
3
|
import {haptics} from '@hzblj/expo-rich-haptics'
|
|
4
4
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
5
5
|
import {useCallback, useMemo} from 'react'
|
|
@@ -16,7 +16,7 @@ const NOOP = () => undefined
|
|
|
16
16
|
* `toggleOn`/`toggleOff` — Android 14's constants made for switches — since Compose's Material switch plays nothing
|
|
17
17
|
* of its own. iOS stays silent, as `UISwitch` does.
|
|
18
18
|
*/
|
|
19
|
-
export const Switch: SwitchComponent = ({disabled = false, label, onValueChange, value}: SwitchProps) => {
|
|
19
|
+
export const Switch: SwitchComponent = ({disabled = false, label, onValueChange, testID, value}: SwitchProps) => {
|
|
20
20
|
useHostGuard('Switch')
|
|
21
21
|
const theme = useAppTheme()
|
|
22
22
|
const toggle = useCallback(
|
|
@@ -30,6 +30,8 @@ export const Switch: SwitchComponent = ({disabled = false, label, onValueChange,
|
|
|
30
30
|
() => (disabled || !onValueChange ? [fillMaxWidth()] : [fillMaxWidth(), clickable(() => toggle(!value))]),
|
|
31
31
|
[disabled, onValueChange, toggle, value]
|
|
32
32
|
)
|
|
33
|
+
// On the switch, not the row: the switch is its own toggleable node inside the row, and the one a test reads.
|
|
34
|
+
const controlModifiers = useMemo(() => (testID ? [testIdModifier(testID)] : undefined), [testID])
|
|
33
35
|
const control = (
|
|
34
36
|
<ComposeSwitch
|
|
35
37
|
colors={{
|
|
@@ -38,6 +40,7 @@ export const Switch: SwitchComponent = ({disabled = false, label, onValueChange,
|
|
|
38
40
|
checkedTrackColor: theme.color('fill.accent.primary'),
|
|
39
41
|
}}
|
|
40
42
|
enabled={!disabled}
|
|
43
|
+
modifiers={controlModifiers}
|
|
41
44
|
onCheckedChange={onValueChange ? toggle : NOOP}
|
|
42
45
|
value={value}
|
|
43
46
|
/>
|
|
@@ -6,7 +6,7 @@ import {useMemo} from 'react'
|
|
|
6
6
|
import {useHostGuard} from '../../host-context'
|
|
7
7
|
import type {SwitchComponent, SwitchProps} from './switch.types'
|
|
8
8
|
|
|
9
|
-
export const Switch: SwitchComponent = ({disabled = false, label, onValueChange, value}: SwitchProps) => {
|
|
9
|
+
export const Switch: SwitchComponent = ({disabled = false, label, onValueChange, testID, value}: SwitchProps) => {
|
|
10
10
|
useHostGuard('Switch')
|
|
11
11
|
const theme = useAppTheme()
|
|
12
12
|
|
|
@@ -20,5 +20,5 @@ export const Switch: SwitchComponent = ({disabled = false, label, onValueChange,
|
|
|
20
20
|
return list
|
|
21
21
|
}, [disabled, theme])
|
|
22
22
|
|
|
23
|
-
return <Toggle isOn={value} label={label} modifiers={modifiers} onIsOnChange={onValueChange} />
|
|
23
|
+
return <Toggle isOn={value} label={label} modifiers={modifiers} onIsOnChange={onValueChange} testID={testID} />
|
|
24
24
|
}
|
|
@@ -11,6 +11,8 @@ export type SwitchProps = {
|
|
|
11
11
|
label?: string
|
|
12
12
|
/** Called with the new value when the user flips it. On Android a flip plays `toggleOn` / `toggleOff`. */
|
|
13
13
|
onValueChange?: (value: boolean) => void
|
|
14
|
+
/** Test identifier, forwarded to the native switch (on Android the switch itself, not the labelled row). */
|
|
15
|
+
testID?: string
|
|
14
16
|
/** Whether it is on. Controlled: pass back what `onValueChange` reports. */
|
|
15
17
|
value: boolean
|
|
16
18
|
}
|
package/src/atoms/text/spans.ts
CHANGED
|
@@ -4,7 +4,8 @@ import {Children, isValidElement, type ReactNode} from 'react'
|
|
|
4
4
|
/** A run of the text: a string child, or a nested `Text` with a colour of its own. */
|
|
5
5
|
export type TextSpan = {color?: ColorToken; text: string}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/** The string and number children, joined: the text of a `Text` without spans, however JSX split it. */
|
|
8
|
+
export const textOf = (children: ReactNode): string =>
|
|
8
9
|
Children.toArray(children)
|
|
9
10
|
.map(child => (typeof child === 'string' || typeof child === 'number' ? String(child) : ''))
|
|
10
11
|
.join('')
|
|
@@ -1,6 +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 {AnimatedText, type TextAnimation} from '@hzblj/kara-native/jetpack-compose'
|
|
3
|
+
import {AnimatedText, type AnimatedTextOverflow, type TextAnimation} from '@hzblj/kara-native/jetpack-compose'
|
|
4
4
|
import {
|
|
5
5
|
animateConfig,
|
|
6
6
|
type SxAnimateConfig,
|
|
@@ -12,8 +12,8 @@ import {duration, easing, springPhysics, useAppTheme} from '@hzblj/kara-theme'
|
|
|
12
12
|
import {useMemo} from 'react'
|
|
13
13
|
|
|
14
14
|
import {useHostGuard} from '../../host-context'
|
|
15
|
-
import {spansOf} from './spans'
|
|
16
|
-
import type {TextComponent, TextDecoration, TextProps, TextTransform} from './text.types'
|
|
15
|
+
import {spansOf, textOf} from './spans'
|
|
16
|
+
import type {TextComponent, TextDecoration, TextProps, TextTransform, TextTruncation} from './text.types'
|
|
17
17
|
|
|
18
18
|
const DECORATION = {
|
|
19
19
|
none: 'none',
|
|
@@ -21,6 +21,12 @@ const DECORATION = {
|
|
|
21
21
|
underline: 'underline',
|
|
22
22
|
} as const
|
|
23
23
|
|
|
24
|
+
const OVERFLOW = {
|
|
25
|
+
head: 'startEllipsis',
|
|
26
|
+
middle: 'middleEllipsis',
|
|
27
|
+
tail: 'ellipsis',
|
|
28
|
+
} as const satisfies Record<TextTruncation, AnimatedTextOverflow>
|
|
29
|
+
|
|
24
30
|
const applyTransform = (children: TextProps['children'], transform: TextTransform | undefined) => {
|
|
25
31
|
if (!transform || typeof children !== 'string') {
|
|
26
32
|
return children
|
|
@@ -68,6 +74,7 @@ export const Text: TextComponent = ({
|
|
|
68
74
|
numberOfLines,
|
|
69
75
|
testID,
|
|
70
76
|
transform,
|
|
77
|
+
truncation = 'tail',
|
|
71
78
|
variant = 'body',
|
|
72
79
|
...props
|
|
73
80
|
}: TextProps) => {
|
|
@@ -83,11 +90,15 @@ export const Text: TextComponent = ({
|
|
|
83
90
|
)
|
|
84
91
|
const spans = useMemo(() => spansOf(children, Text), [children])
|
|
85
92
|
const animate = sx.animate === false ? undefined : sx.animate
|
|
93
|
+
// Compose ellipsizes at the start or in the middle of a single line only, and clips a longer text: that ends at the
|
|
94
|
+
// tail instead.
|
|
95
|
+
const overflow = numberOfLines === 1 ? OVERFLOW[truncation] : 'ellipsis'
|
|
86
96
|
|
|
87
|
-
// `@expo/ui`'s text takes spans but animates none,
|
|
88
|
-
// eases each span's colour,
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
// `@expo/ui`'s text takes spans but animates none, sets no font features and ellipsizes only at the end: with
|
|
98
|
+
// `animate`, kara's own Compose text eases each span's colour, with `monospacedDigit` asks the face for its tabular
|
|
99
|
+
// figures, and puts the ellipsis at the start or in the middle of a line.
|
|
100
|
+
if (animate !== undefined || monospacedDigit || overflow !== 'ellipsis') {
|
|
101
|
+
const runs = spans ?? [{text: textOf(children)}]
|
|
91
102
|
|
|
92
103
|
return (
|
|
93
104
|
<AnimatedText
|
|
@@ -106,6 +117,7 @@ export const Text: TextComponent = ({
|
|
|
106
117
|
textAlign={align}
|
|
107
118
|
textColor={theme.colorString(color)}
|
|
108
119
|
textDecoration={decorationOf(decoration)}
|
|
120
|
+
textOverflow={overflow}
|
|
109
121
|
/>
|
|
110
122
|
)
|
|
111
123
|
}
|
|
@@ -115,6 +127,7 @@ export const Text: TextComponent = ({
|
|
|
115
127
|
color={theme.colorString(color)}
|
|
116
128
|
maxLines={numberOfLines}
|
|
117
129
|
modifiers={modifiers}
|
|
130
|
+
overflow="ellipsis"
|
|
118
131
|
style={{
|
|
119
132
|
fontFamily: style.fontFamily,
|
|
120
133
|
fontSize: style.fontSize,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
multilineTextAlignment,
|
|
9
9
|
strikethrough,
|
|
10
10
|
textCase,
|
|
11
|
+
truncationMode,
|
|
11
12
|
underline,
|
|
12
13
|
} from '@expo/ui/swift-ui/modifiers'
|
|
13
14
|
import {scaledFont} from '@hzblj/kara-native/swift-ui'
|
|
@@ -48,6 +49,7 @@ export const Text: TextComponent = ({
|
|
|
48
49
|
numberOfLines,
|
|
49
50
|
testID,
|
|
50
51
|
transform,
|
|
52
|
+
truncation,
|
|
51
53
|
variant = 'body',
|
|
52
54
|
...props
|
|
53
55
|
}: TextProps) => {
|
|
@@ -99,6 +101,10 @@ export const Text: TextComponent = ({
|
|
|
99
101
|
list.push(lineLimit(numberOfLines))
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
if (truncation) {
|
|
105
|
+
list.push(truncationMode(truncation))
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
return [...list, ...transition, ...sxModifiers, ...accessibilityModifiers]
|
|
103
109
|
}, [
|
|
104
110
|
accessibilityModifiers,
|
|
@@ -112,6 +118,7 @@ export const Text: TextComponent = ({
|
|
|
112
118
|
theme,
|
|
113
119
|
transform,
|
|
114
120
|
transition,
|
|
121
|
+
truncation,
|
|
115
122
|
])
|
|
116
123
|
|
|
117
124
|
return (
|
|
@@ -8,6 +8,9 @@ export type TextDecoration = 'none' | 'strikethrough' | 'underline'
|
|
|
8
8
|
|
|
9
9
|
export type TextTransform = 'lowercase' | 'uppercase'
|
|
10
10
|
|
|
11
|
+
/** Where the ellipsis goes in a text cut short: its start, its middle or its end. */
|
|
12
|
+
export type TextTruncation = 'head' | 'middle' | 'tail'
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
15
|
* SwiftUI `Text` / Compose `Text` in a theme typography variant. A native atom: outside a host, use `RnText`. It takes
|
|
13
16
|
* the native `sx` surface too: padding, a frame (`wide` lines it up by `align`), a background, and the `ios` bucket's
|
|
@@ -34,7 +37,7 @@ export type TextProps = ResponsiveSx &
|
|
|
34
37
|
color?: ColorToken
|
|
35
38
|
/** An underline or a strikethrough. */
|
|
36
39
|
decoration?: TextDecoration
|
|
37
|
-
/** The most lines it wraps to.
|
|
40
|
+
/** The most lines it wraps to. A text cut short ends with an ellipsis, where `truncation` puts it. */
|
|
38
41
|
numberOfLines?: number
|
|
39
42
|
/**
|
|
40
43
|
* Sets it in the theme's fixed-width face, at the variant's size and weight: the app's `fonts.mono`, else Menlo on
|
|
@@ -51,6 +54,12 @@ export type TextProps = ResponsiveSx &
|
|
|
51
54
|
testID?: string
|
|
52
55
|
/** Upper- or lower-cases the text. Android applies it only when `children` is a plain string. */
|
|
53
56
|
transform?: TextTransform
|
|
57
|
+
/**
|
|
58
|
+
* Where the ellipsis goes in a text cut short. `tail` by default: the end of the last line. `head` keeps the end of
|
|
59
|
+
* the text and `middle` both of its ends, for a file name, a path or an address. Android puts it at the head or in
|
|
60
|
+
* the middle of a single line only (`numberOfLines={1}`), as Compose does; a longer text there ends at the tail.
|
|
61
|
+
*/
|
|
62
|
+
truncation?: TextTruncation
|
|
54
63
|
/** The typography variant. `body` by default. */
|
|
55
64
|
variant?: TextVariant
|
|
56
65
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type {TextFieldAutoCapitalize, TextFieldKeyboard} from './text-field.types'
|
|
2
|
+
|
|
3
|
+
/** Keyboards for an address, where a capital or a correction the user did not type would only break it. */
|
|
4
|
+
const ADDRESS_KEYBOARDS: ReadonlySet<TextFieldKeyboard> = new Set(['email', 'url'])
|
|
5
|
+
|
|
6
|
+
/** The capitalisation asked for, else `none` for an address; `undefined` leaves the platform's own. */
|
|
7
|
+
export const capitalizationFor = (
|
|
8
|
+
keyboard: TextFieldKeyboard,
|
|
9
|
+
autoCapitalize: TextFieldAutoCapitalize | undefined
|
|
10
|
+
): TextFieldAutoCapitalize | undefined => autoCapitalize ?? (ADDRESS_KEYBOARDS.has(keyboard) ? 'none' : undefined)
|
|
11
|
+
|
|
12
|
+
/** Autocorrection as asked for, else on for everything but an address. */
|
|
13
|
+
export const autoCorrectFor = (keyboard: TextFieldKeyboard, autoCorrect: boolean | undefined): boolean =>
|
|
14
|
+
autoCorrect ?? !ADDRESS_KEYBOARDS.has(keyboard)
|
|
15
|
+
|
|
16
|
+
/** A no-op for a ref call that lands after the native view has gone. */
|
|
17
|
+
export const ignoreDetachedView = () => undefined
|