@hzblj/kara-components 0.16.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 +62 -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/picker/picker.ios.js +27 -15
- package/lib/module/atoms/picker/picker.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/picker/picker.ios.d.ts +5 -3
- package/lib/typescript/atoms/picker/picker.types.d.ts +11 -3
- 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/picker/picker.ios.tsx +46 -16
- package/src/atoms/picker/picker.types.ts +12 -3
- 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,28 +1,78 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IconButton as ComposeIconButton,
|
|
3
|
+
FilledIconButton,
|
|
4
|
+
FilledTonalIconButton,
|
|
5
|
+
OutlinedIconButton,
|
|
6
|
+
} from '@expo/ui/jetpack-compose'
|
|
7
|
+
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
8
|
import {useAccessibilityModifiers} from '@hzblj/kara-sx'
|
|
9
|
+
import {type ColorToken, useAppTheme} from '@hzblj/kara-theme'
|
|
10
|
+
import {useMemo} from 'react'
|
|
3
11
|
|
|
4
12
|
import {useHostGuard} from '../../host-context'
|
|
13
|
+
import {buttonTokens, contentOnTint} from '../button/button-tokens'
|
|
5
14
|
import {Icon} from '../icon'
|
|
6
|
-
import type {IconButtonComponent, IconButtonProps} from './icon-button.types'
|
|
15
|
+
import type {IconButtonComponent, IconButtonProps, IconButtonVariant} from './icon-button.types'
|
|
16
|
+
|
|
17
|
+
/** Material has no glass: `glass` is the tonal button, a contained one that still reads over a photo or a map. */
|
|
18
|
+
const MATERIAL_BY_VARIANT = {
|
|
19
|
+
filled: FilledIconButton,
|
|
20
|
+
glass: FilledTonalIconButton,
|
|
21
|
+
outlined: OutlinedIconButton,
|
|
22
|
+
plain: ComposeIconButton,
|
|
23
|
+
tonal: FilledTonalIconButton,
|
|
24
|
+
} as const satisfies Record<IconButtonVariant, unknown>
|
|
25
|
+
|
|
26
|
+
/** Which `Button` variant's tokens fill the container: the same accent and secondary fills a `Button` draws. */
|
|
27
|
+
const FILL_BY_VARIANT = {
|
|
28
|
+
filled: 'accent',
|
|
29
|
+
glass: 'secondary',
|
|
30
|
+
outlined: undefined,
|
|
31
|
+
plain: undefined,
|
|
32
|
+
tonal: 'secondary',
|
|
33
|
+
} as const satisfies Record<IconButtonVariant, 'accent' | 'secondary' | undefined>
|
|
7
34
|
|
|
8
35
|
export const IconButton: IconButtonComponent = ({
|
|
9
36
|
accessibilityHint,
|
|
10
37
|
accessibilityLabel,
|
|
38
|
+
color,
|
|
11
39
|
disabled = false,
|
|
12
40
|
name,
|
|
13
41
|
onPress,
|
|
42
|
+
testID,
|
|
43
|
+
variant = 'plain',
|
|
14
44
|
}: IconButtonProps) => {
|
|
15
45
|
useHostGuard('IconButton')
|
|
46
|
+
const theme = useAppTheme()
|
|
16
47
|
// The label rides on the icon, as Material's own icon buttons carry it; the hint labels the click.
|
|
17
|
-
const
|
|
48
|
+
const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint}, accessibilityHint !== undefined)
|
|
49
|
+
const modifiers = useMemo(
|
|
50
|
+
() => (testID ? [testIdModifier(testID), ...accessibilityModifiers] : accessibilityModifiers),
|
|
51
|
+
[accessibilityModifiers, testID]
|
|
52
|
+
)
|
|
53
|
+
const MaterialIconButton = MATERIAL_BY_VARIANT[variant]
|
|
54
|
+
const fillVariant = FILL_BY_VARIANT[variant]
|
|
55
|
+
const tokens = fillVariant ? buttonTokens(fillVariant, disabled, theme) : undefined
|
|
56
|
+
|
|
57
|
+
// `filled` takes the colour as its fill and picks the icon's on it, as a tinted `Button` does; the rest colour the icon.
|
|
58
|
+
const filled = variant === 'filled'
|
|
59
|
+
const fill = filled && color && !disabled ? color : tokens?.fill
|
|
60
|
+
let iconColor: ColorToken = color ?? 'content.primary'
|
|
61
|
+
|
|
62
|
+
if (disabled) {
|
|
63
|
+
iconColor = 'btn.content.disabled'
|
|
64
|
+
} else if (filled) {
|
|
65
|
+
iconColor = color ? contentOnTint(color, theme) : 'content.on.accent'
|
|
66
|
+
}
|
|
18
67
|
|
|
19
68
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
69
|
+
<MaterialIconButton
|
|
70
|
+
colors={fill ? {containerColor: theme.color(fill), disabledContainerColor: theme.color(fill)} : undefined}
|
|
71
|
+
enabled={!disabled}
|
|
72
|
+
modifiers={modifiers}
|
|
73
|
+
onClick={onPress}
|
|
74
|
+
>
|
|
75
|
+
<Icon accessibilityLabel={accessibilityLabel} color={iconColor} name={name} />
|
|
76
|
+
</MaterialIconButton>
|
|
27
77
|
)
|
|
28
78
|
}
|
|
@@ -1,46 +1,115 @@
|
|
|
1
1
|
import {Button} from '@expo/ui/swift-ui'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buttonBorderShape,
|
|
4
|
+
buttonStyle,
|
|
5
|
+
contentShape,
|
|
6
|
+
disabled as disabledModifier,
|
|
7
|
+
shapes,
|
|
8
|
+
tint as tintModifier,
|
|
9
|
+
} from '@expo/ui/swift-ui/modifiers'
|
|
10
|
+
import {isLiquidGlassAvailable} from '@hzblj/kara-native'
|
|
3
11
|
import {useAccessibilityModifiers} from '@hzblj/kara-sx'
|
|
12
|
+
import {type ColorToken, useAppTheme} from '@hzblj/kara-theme'
|
|
4
13
|
import {useMemo} from 'react'
|
|
5
14
|
|
|
6
15
|
import {useHostGuard} from '../../host-context'
|
|
16
|
+
import type {ButtonStyleName} from '../button/button.types'
|
|
17
|
+
import {contentOnTint} from '../button/button-tokens'
|
|
7
18
|
import {Icon} from '../icon'
|
|
8
19
|
import {ZStack} from '../z-stack'
|
|
9
|
-
import type {IconButtonComponent, IconButtonProps} from './icon-button.types'
|
|
20
|
+
import type {IconButtonComponent, IconButtonProps, IconButtonVariant} from './icon-button.types'
|
|
10
21
|
|
|
11
22
|
/** Apple's least tap target. Android's `IconButton` is Material's 48dp; this is the same idea in points. */
|
|
12
23
|
const TARGET = 44
|
|
13
24
|
|
|
25
|
+
/** Material's 40dp container, the ring an `outlined` button draws inside its tap target. */
|
|
26
|
+
const RING = 40
|
|
27
|
+
|
|
14
28
|
/** The whole square takes the tap, not only the glyph's own pixels. */
|
|
15
29
|
const WHOLE_TARGET = {modifiers: [contentShape(shapes.circle())]}
|
|
16
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The SwiftUI style behind each variant. `outlined` has none (SwiftUI draws no ring around a button), so it is a plain
|
|
33
|
+
* button whose label draws the ring.
|
|
34
|
+
*/
|
|
35
|
+
const STYLE_BY_VARIANT = {
|
|
36
|
+
filled: 'borderedProminent',
|
|
37
|
+
glass: 'glass',
|
|
38
|
+
outlined: 'plain',
|
|
39
|
+
plain: 'plain',
|
|
40
|
+
tonal: 'bordered',
|
|
41
|
+
} as const satisfies Record<IconButtonVariant, ButtonStyleName>
|
|
42
|
+
|
|
17
43
|
export const IconButton: IconButtonComponent = ({
|
|
18
44
|
accessibilityHint,
|
|
19
45
|
accessibilityLabel,
|
|
46
|
+
color,
|
|
20
47
|
disabled = false,
|
|
21
48
|
name,
|
|
22
49
|
onPress,
|
|
23
50
|
testID,
|
|
51
|
+
variant = 'plain',
|
|
24
52
|
}: IconButtonProps) => {
|
|
25
53
|
useHostGuard('IconButton')
|
|
54
|
+
const theme = useAppTheme()
|
|
26
55
|
const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint})
|
|
56
|
+
// Below iOS 26 `@expo/ui` turns `.glass` into `.automatic`, a bare icon; the tonal circle is what Android draws too.
|
|
57
|
+
const style = variant === 'glass' && !isLiquidGlassAvailable() ? 'bordered' : STYLE_BY_VARIANT[variant]
|
|
58
|
+
const bezel = style !== 'plain'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* `filled` takes the colour as its fill, and the icon a colour that reads on it. `tonal` passes it as the tint too,
|
|
62
|
+
* which SwiftUI's bordered style draws its pale circle in. SwiftUI dims a disabled button's label itself, and greys a
|
|
63
|
+
* prominent one's fill, so a disabled filled icon goes back to the primary content colour rather than white on grey.
|
|
64
|
+
*/
|
|
65
|
+
const fill: ColorToken | undefined = variant === 'filled' ? (color ?? 'fill.accent.primary') : undefined
|
|
66
|
+
const tint = fill ?? (variant === 'tonal' ? color : undefined)
|
|
67
|
+
const iconColor: ColorToken = fill
|
|
68
|
+
? disabled
|
|
69
|
+
? 'content.primary'
|
|
70
|
+
: color
|
|
71
|
+
? contentOnTint(color, theme)
|
|
72
|
+
: 'content.on.accent'
|
|
73
|
+
: (color ?? 'content.primary')
|
|
27
74
|
|
|
28
75
|
const modifiers = useMemo(() => {
|
|
29
|
-
const list = [buttonStyle(
|
|
76
|
+
const list = [buttonStyle(style)]
|
|
77
|
+
|
|
78
|
+
if (bezel) {
|
|
79
|
+
list.push(buttonBorderShape('circle'))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (tint) {
|
|
83
|
+
list.push(tintModifier(theme.color(tint)))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
list.push(...accessibilityModifiers)
|
|
30
87
|
|
|
31
88
|
if (disabled) {
|
|
32
89
|
list.push(disabledModifier(true))
|
|
33
90
|
}
|
|
34
91
|
|
|
35
92
|
return list
|
|
36
|
-
}, [accessibilityModifiers, disabled])
|
|
93
|
+
}, [accessibilityModifiers, bezel, disabled, style, theme, tint])
|
|
94
|
+
|
|
95
|
+
// A bezel style sizes its circle around the icon itself; a plain button's label is the whole target.
|
|
96
|
+
const icon = <Icon accessibilityLabel={accessibilityLabel} color={iconColor} name={name} />
|
|
37
97
|
|
|
38
98
|
return (
|
|
39
99
|
<Button modifiers={modifiers} onPress={onPress} testID={testID}>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
100
|
+
{bezel ? (
|
|
101
|
+
icon
|
|
102
|
+
) : (
|
|
103
|
+
<ZStack h={TARGET} ios={WHOLE_TARGET} w={TARGET}>
|
|
104
|
+
{variant === 'outlined' ? (
|
|
105
|
+
<ZStack borderColor="border.secondary.base" borderRadius="full" borderWidth={1} h={RING} w={RING}>
|
|
106
|
+
{icon}
|
|
107
|
+
</ZStack>
|
|
108
|
+
) : (
|
|
109
|
+
icon
|
|
110
|
+
)}
|
|
111
|
+
</ZStack>
|
|
112
|
+
)}
|
|
44
113
|
</Button>
|
|
45
114
|
)
|
|
46
115
|
}
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import type {ColorToken} from '@hzblj/kara-theme'
|
|
1
2
|
import type {ReactNode} from 'react'
|
|
2
3
|
|
|
3
4
|
import type {IconName} from '../icon'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
|
|
7
|
+
* How much the button stands out: just the icon (`plain`), on a fill in the accent (`filled`), on a muted fill
|
|
8
|
+
* (`tonal`), in a ring (`outlined`) or on Liquid Glass (`glass`).
|
|
9
|
+
*/
|
|
10
|
+
export type IconButtonVariant = 'filled' | 'glass' | 'outlined' | 'plain' | 'tonal'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A button that is just an icon: a SwiftUI `Button` on iOS, one of Material's icon buttons on Android. A `plain` one
|
|
14
|
+
* sits centred in a tap target the size each platform asks for, 44pt on iOS and 48dp on Android, so a row of them
|
|
15
|
+
* spaces itself the same way on both; the other variants draw a circle around the icon.
|
|
9
16
|
*/
|
|
10
17
|
export type IconButtonProps = {
|
|
11
18
|
/**
|
|
@@ -15,14 +22,27 @@ export type IconButtonProps = {
|
|
|
15
22
|
accessibilityLabel: string
|
|
16
23
|
/** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
|
|
17
24
|
accessibilityHint?: string
|
|
25
|
+
/**
|
|
26
|
+
* The icon's colour, `content.primary` by default. On `filled` it is the fill instead (`fill.accent.primary` by
|
|
27
|
+
* default) and the icon picks a colour that reads on it, as a `Button`'s `tint` does on a prominent style. On iOS a
|
|
28
|
+
* `tonal` button's circle takes a pale shade of it too.
|
|
29
|
+
*/
|
|
30
|
+
color?: ColorToken
|
|
18
31
|
/** Dims the icon (iOS's own disabled look, `btn.content.disabled` on Android) and stops the button responding. */
|
|
19
32
|
disabled?: boolean
|
|
20
33
|
/** The icon, one of the app's. */
|
|
21
34
|
name: IconName
|
|
22
35
|
/** Called when the button is pressed. */
|
|
23
36
|
onPress?: () => void
|
|
24
|
-
/** Test identifier, forwarded to the native view
|
|
37
|
+
/** Test identifier, forwarded to the native view. */
|
|
25
38
|
testID?: string
|
|
39
|
+
/**
|
|
40
|
+
* The look. `plain` by default. iOS draws the others as SwiftUI button styles in a circle: `borderedProminent` for
|
|
41
|
+
* `filled`, `bordered` for `tonal`, `glass` for `glass` (`bordered` below iOS 26), and `outlined` as a plain button
|
|
42
|
+
* in a hairline ring. Android draws Material's `IconButton`, `FilledIconButton`, `FilledTonalIconButton` and
|
|
43
|
+
* `OutlinedIconButton`; Material has no glass, so there `glass` is the tonal one.
|
|
44
|
+
*/
|
|
45
|
+
variant?: IconButtonVariant
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
export type IconButtonComponent = (props: IconButtonProps) => ReactNode
|
|
@@ -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,6 +1,12 @@
|
|
|
1
1
|
import {Picker as NativePicker} from '@expo/ui/community/picker'
|
|
2
|
-
import {Picker as SwiftUIPicker, Text as SwiftUIText} from '@expo/ui/swift-ui'
|
|
3
|
-
import {
|
|
2
|
+
import {Host, Picker as SwiftUIPicker, Text as SwiftUIText} from '@expo/ui/swift-ui'
|
|
3
|
+
import {
|
|
4
|
+
disabled as disabledModifier,
|
|
5
|
+
foregroundStyle,
|
|
6
|
+
pickerStyle as pickerStyleModifier,
|
|
7
|
+
tag,
|
|
8
|
+
tint,
|
|
9
|
+
} from '@expo/ui/swift-ui/modifiers'
|
|
4
10
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
5
11
|
import {useCallback, useMemo} from 'react'
|
|
6
12
|
|
|
@@ -8,30 +14,54 @@ import {useInsideHost} from '../../host-context'
|
|
|
8
14
|
import type {PickerComponent, PickerProps} from './picker.types'
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
|
-
* A SwiftUI
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* A SwiftUI picker: a wheel, or with `pickerStyle="menu"` a button with the selection that opens the options as a
|
|
18
|
+
* menu, in the accent. On its own the wheel is `@expo/ui`'s community picker, which brings a host of its own, and the
|
|
19
|
+
* menu sits in a host sized to its button on both axes, as the date picker's pill does, so it drops into a row the way
|
|
20
|
+
* a `Text` does. Inside a `Screen` or `Island` either renders into that host instead (see `useInsideHost`): a host
|
|
21
|
+
* nested in the island's stack is given no height there, and the wheel spills over the views around it.
|
|
14
22
|
*/
|
|
15
|
-
export const Picker: PickerComponent = ({
|
|
23
|
+
export const Picker: PickerComponent = ({
|
|
24
|
+
disabled = false,
|
|
25
|
+
onValueChange,
|
|
26
|
+
options,
|
|
27
|
+
pickerStyle = 'wheel',
|
|
28
|
+
testID,
|
|
29
|
+
value,
|
|
30
|
+
}: PickerProps) => {
|
|
16
31
|
const theme = useAppTheme()
|
|
17
32
|
const insideHost = useInsideHost()
|
|
18
33
|
const color = theme.colorString('content.primary')
|
|
34
|
+
const menu = pickerStyle === 'menu'
|
|
19
35
|
|
|
20
36
|
const onChange = useCallback((next: string | number | null) => onValueChange?.(String(next)), [onValueChange])
|
|
21
|
-
const
|
|
22
|
-
() => [
|
|
23
|
-
|
|
37
|
+
const modifiers = useMemo(
|
|
38
|
+
() => [
|
|
39
|
+
pickerStyleModifier(pickerStyle),
|
|
40
|
+
...(menu ? [tint(theme.color('fill.accent.primary'))] : []),
|
|
41
|
+
...(disabled ? [disabledModifier(true)] : []),
|
|
42
|
+
],
|
|
43
|
+
[disabled, menu, pickerStyle, theme]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const picker = (
|
|
47
|
+
<SwiftUIPicker modifiers={modifiers} onSelectionChange={onChange} selection={value} testID={testID}>
|
|
48
|
+
{options.map(option => (
|
|
49
|
+
<SwiftUIText key={option.value} modifiers={[tag(option.value), foregroundStyle(color)]}>
|
|
50
|
+
{option.label}
|
|
51
|
+
</SwiftUIText>
|
|
52
|
+
))}
|
|
53
|
+
</SwiftUIPicker>
|
|
24
54
|
)
|
|
25
55
|
|
|
26
56
|
if (insideHost) {
|
|
57
|
+
return picker
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (menu) {
|
|
27
61
|
return (
|
|
28
|
-
<
|
|
29
|
-
{
|
|
30
|
-
|
|
31
|
-
{option.label}
|
|
32
|
-
</SwiftUIText>
|
|
33
|
-
))}
|
|
34
|
-
</SwiftUIPicker>
|
|
62
|
+
<Host colorScheme={theme.dark ? 'dark' : 'light'} matchContents>
|
|
63
|
+
{picker}
|
|
64
|
+
</Host>
|
|
35
65
|
)
|
|
36
66
|
}
|
|
37
67
|
|
|
@@ -8,10 +8,13 @@ export type PickerOption = {
|
|
|
8
8
|
value: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** How SwiftUI shows the picker: a wheel, or a button that opens the options as a menu. */
|
|
12
|
+
export type PickerStyleName = 'menu' | 'wheel'
|
|
13
|
+
|
|
11
14
|
/**
|
|
12
|
-
* A single choice from a list: a SwiftUI
|
|
13
|
-
* own host, so it renders outside `Screen` and `Island` as well. Inside one it
|
|
14
|
-
* can share a stack with other native views.
|
|
15
|
+
* A single choice from a list: a SwiftUI picker on iOS (a wheel, or a menu with `pickerStyle`), Material's exposed
|
|
16
|
+
* dropdown on Android. It brings its own host, so it renders outside `Screen` and `Island` as well. Inside one it
|
|
17
|
+
* renders into that host instead, so it can share a stack with other native views.
|
|
15
18
|
*/
|
|
16
19
|
export type PickerProps = {
|
|
17
20
|
/** Stops the user changing the selection, and dims the picker. */
|
|
@@ -20,6 +23,12 @@ export type PickerProps = {
|
|
|
20
23
|
onValueChange?: (value: string) => void
|
|
21
24
|
/** The choices, in order. */
|
|
22
25
|
options: PickerOption[]
|
|
26
|
+
/**
|
|
27
|
+
* SwiftUI's picker style. `wheel` by default: the options on a wheel, always in view. `menu` is a button with the
|
|
28
|
+
* selection, in the accent, that opens the options as a menu: what a screen with more than one picker, or a picker
|
|
29
|
+
* in a row, wants. iOS only. Android ignores it: its exposed dropdown is a menu already.
|
|
30
|
+
*/
|
|
31
|
+
pickerStyle?: PickerStyleName
|
|
23
32
|
/**
|
|
24
33
|
* Android: the dropdown field's test tag. iOS: the wheel's, inside a `Screen` or `Island` only; on its own,
|
|
25
34
|
* `@expo/ui`'s wheel picker drops it.
|
|
@@ -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
|
}
|