@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
|
@@ -5,13 +5,13 @@ import {Text} from '../text'
|
|
|
5
5
|
import type {BadgeComponent, BadgeProps} from './badge.types'
|
|
6
6
|
import {BADGE_TOKENS} from './badge-tokens'
|
|
7
7
|
|
|
8
|
-
export const Badge: BadgeComponent = ({label, tone = 'neutral'}: BadgeProps) => {
|
|
8
|
+
export const Badge: BadgeComponent = ({label, testID, tone = 'neutral'}: BadgeProps) => {
|
|
9
9
|
useHostGuard('Badge')
|
|
10
10
|
const tokens = BADGE_TOKENS[tone]
|
|
11
11
|
const modifiers = useSxModifiers({bg: tokens.fill, borderRadius: 'full', px: 's8', py: 's2'})
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<HStack modifiers={modifiers}>
|
|
14
|
+
<HStack modifiers={modifiers} testID={testID}>
|
|
15
15
|
<Text color={tokens.content} variant="labelMedium">
|
|
16
16
|
{label}
|
|
17
17
|
</Text>
|
|
@@ -6,6 +6,8 @@ export type BadgeTone = 'accent' | 'destructive' | 'neutral' | 'success'
|
|
|
6
6
|
export type BadgeProps = {
|
|
7
7
|
/** The text inside. */
|
|
8
8
|
label: string
|
|
9
|
+
/** Test identifier, forwarded to the native view. */
|
|
10
|
+
testID?: string
|
|
9
11
|
/** The fill and text colours. `neutral` by default. */
|
|
10
12
|
tone?: BadgeTone
|
|
11
13
|
}
|
|
@@ -2,16 +2,18 @@ import type {ColorToken, ShadowName, Theme} from '@hzblj/kara-theme'
|
|
|
2
2
|
|
|
3
3
|
import type {ButtonVariant} from './button.types'
|
|
4
4
|
|
|
5
|
+
/** `fill` and `shadow` are unset for `plain`: a text button has no container to fill or lift. */
|
|
5
6
|
type ButtonTokens = {
|
|
6
7
|
content: ColorToken
|
|
7
|
-
fill: ColorToken
|
|
8
|
-
shadow: ShadowName
|
|
8
|
+
fill: ColorToken | undefined
|
|
9
|
+
shadow: ShadowName | undefined
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
const TOKENS = {
|
|
12
13
|
accent: {content: 'content.on.accent', fill: 'fill.accent.primary', shadow: 'btn.accent'},
|
|
13
14
|
destructive: {content: 'content.on.destructive', fill: 'fill.destructive.primary', shadow: 'btn.destructive'},
|
|
14
15
|
glass: {content: 'content.primary', fill: 'btn.bg.glass.base', shadow: 'btn.glass'},
|
|
16
|
+
plain: {content: 'content.accent', fill: undefined, shadow: undefined},
|
|
15
17
|
secondary: {content: 'content.primary', fill: 'btn.bg.secondary.base', shadow: 'btn.secondary'},
|
|
16
18
|
} as const satisfies Record<ButtonVariant, ButtonTokens>
|
|
17
19
|
|
|
@@ -26,8 +28,9 @@ export const buttonTokens = (variant: ButtonVariant, disabled: boolean, theme: T
|
|
|
26
28
|
accent: 'fill.accent.disabled',
|
|
27
29
|
destructive: 'fill.destructive.disabled',
|
|
28
30
|
glass: 'btn.bg.glass.disabled',
|
|
31
|
+
plain: undefined,
|
|
29
32
|
secondary: 'btn.bg.secondary.disabled',
|
|
30
|
-
} as const satisfies Record<ButtonVariant, ColorToken>
|
|
33
|
+
} as const satisfies Record<ButtonVariant, ColorToken | undefined>
|
|
31
34
|
|
|
32
35
|
void theme
|
|
33
36
|
|
|
@@ -1,23 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
Button as ComposeButton,
|
|
3
|
+
Spacer as ComposeSpacer,
|
|
4
|
+
Text as ComposeText,
|
|
5
|
+
OutlinedButton,
|
|
6
|
+
TextButton,
|
|
7
|
+
} from '@expo/ui/jetpack-compose'
|
|
8
|
+
import {testID as testIdModifier, width as widthModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
3
9
|
import {useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
|
|
4
|
-
import {useAppTheme} from '@hzblj/kara-theme'
|
|
10
|
+
import {spacing, useAppTheme} from '@hzblj/kara-theme'
|
|
5
11
|
import {useMemo} from 'react'
|
|
6
12
|
import {useHostGuard} from '../../host-context'
|
|
13
|
+
import {Icon} from '../icon'
|
|
7
14
|
import type {ButtonComponent, ButtonProps} from './button.types'
|
|
8
15
|
import {buttonTokens, contentOnTint} from './button-tokens'
|
|
9
16
|
|
|
17
|
+
/** Material's `ButtonDefaults.IconSpacing`: the gap between a button's icon and its label. */
|
|
18
|
+
const ICON_GAP = [widthModifier(spacing.s8)]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Material's padding for a button with a leading icon (`ButtonWithIconContentPadding`,
|
|
22
|
+
* `TextButtonWithIconContentPadding`): the icon side a step tighter than the label's. Top and bottom keep the defaults.
|
|
23
|
+
*/
|
|
24
|
+
const WITH_ICON_PADDING = {end: spacing.s24, start: spacing.s16}
|
|
25
|
+
const TEXT_WITH_ICON_PADDING = {end: spacing.s16, start: spacing.s12}
|
|
26
|
+
|
|
10
27
|
/**
|
|
11
28
|
* Material's filled button, in its own stadium shape. No `borderRadius` or `shadow` from the tokens: those shape the
|
|
12
29
|
* SwiftUI button's glossy ring, and laid over a Compose button they clip and outline a rounded rectangle around the
|
|
13
|
-
* pill Material draws. The label is a Compose `Text` — `@expo/ui`'s button takes composable content, not a string
|
|
30
|
+
* pill Material draws. The label is a Compose `Text` — `@expo/ui`'s button takes composable content, not a string —
|
|
31
|
+
* after the `icon`, when there is one, in the same row.
|
|
14
32
|
*
|
|
15
|
-
* Material has no glass, so `glass` is its outlined button — and a `tint` makes any variant a filled
|
|
16
|
-
* colour, as a prominent style does on iOS. `
|
|
33
|
+
* Material has no glass, so `glass` is its outlined button — and a `tint` makes any variant but `plain` a filled
|
|
34
|
+
* button in that colour, as a prominent style does on iOS. `plain` is Material's `TextButton`, and a `tint` colours its
|
|
35
|
+
* label. `buttonStyle` and `controlSize` are SwiftUI's alone.
|
|
17
36
|
*/
|
|
18
37
|
export const Button: ButtonComponent = ({
|
|
19
38
|
accessibilityHint,
|
|
20
39
|
disabled = false,
|
|
40
|
+
icon,
|
|
21
41
|
label,
|
|
22
42
|
onPress,
|
|
23
43
|
testID,
|
|
@@ -35,27 +55,31 @@ export const Button: ButtonComponent = ({
|
|
|
35
55
|
[accessibilityModifiers, sxModifiers, testID]
|
|
36
56
|
)
|
|
37
57
|
const style = theme.typography.body
|
|
58
|
+
const text = variant === 'plain'
|
|
38
59
|
const outlined = variant === 'glass' && !tint
|
|
39
60
|
const fill = tint && !disabled ? tint : tokens.fill
|
|
40
|
-
const content = tint
|
|
41
|
-
const MaterialButton = outlined ? OutlinedButton : ComposeButton
|
|
61
|
+
const content = !tint || disabled ? tokens.content : text ? tint : contentOnTint(tint, theme)
|
|
62
|
+
const MaterialButton = text ? TextButton : outlined ? OutlinedButton : ComposeButton
|
|
42
63
|
|
|
43
64
|
return (
|
|
44
65
|
<MaterialButton
|
|
45
66
|
colors={
|
|
46
|
-
outlined
|
|
47
|
-
? {
|
|
48
|
-
: {
|
|
67
|
+
fill && !text && !outlined
|
|
68
|
+
? {
|
|
49
69
|
containerColor: theme.color(fill),
|
|
50
70
|
contentColor: theme.color(content),
|
|
51
71
|
disabledContainerColor: theme.color(fill),
|
|
52
72
|
disabledContentColor: theme.color(content),
|
|
53
73
|
}
|
|
74
|
+
: {contentColor: theme.color(content), disabledContentColor: theme.color(content)}
|
|
54
75
|
}
|
|
76
|
+
contentPadding={icon ? (text ? TEXT_WITH_ICON_PADDING : WITH_ICON_PADDING) : undefined}
|
|
55
77
|
enabled={!disabled}
|
|
56
78
|
modifiers={modifiers}
|
|
57
79
|
onClick={onPress}
|
|
58
80
|
>
|
|
81
|
+
{icon ? <Icon color={content} name={icon} size="s20" /> : null}
|
|
82
|
+
{icon ? <ComposeSpacer modifiers={ICON_GAP} /> : null}
|
|
59
83
|
<ComposeText
|
|
60
84
|
color={theme.colorString(content)}
|
|
61
85
|
style={{
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import {Button as SwiftUIButton} from '@expo/ui/swift-ui'
|
|
1
|
+
import {Button as SwiftUIButton, Label as SwiftUILabel} from '@expo/ui/swift-ui'
|
|
2
2
|
import {
|
|
3
3
|
buttonStyle as buttonStyleModifier,
|
|
4
|
+
contentShape,
|
|
4
5
|
controlSize as controlSizeModifier,
|
|
5
6
|
disabled as disabledModifier,
|
|
6
7
|
glassEffect as glassEffectModifier,
|
|
8
|
+
labelStyle,
|
|
9
|
+
shapes,
|
|
7
10
|
tint as tintModifier,
|
|
8
11
|
} from '@expo/ui/swift-ui/modifiers'
|
|
9
12
|
import {useAccessibilityModifiers} from '@hzblj/kara-sx'
|
|
10
|
-
import {type ColorToken, useAppTheme} from '@hzblj/kara-theme'
|
|
13
|
+
import {type ColorToken, type Theme, useAppTheme} from '@hzblj/kara-theme'
|
|
11
14
|
import {useMemo} from 'react'
|
|
12
15
|
import {useHostGuard} from '../../host-context'
|
|
16
|
+
import {Icon} from '../icon'
|
|
13
17
|
import {Text} from '../text'
|
|
14
18
|
import {VStack} from '../v-stack'
|
|
15
19
|
import type {ButtonComponent, ButtonProps, ButtonStyleName, ButtonVariant} from './button.types'
|
|
@@ -19,6 +23,7 @@ const STYLE_BY_VARIANT = {
|
|
|
19
23
|
accent: 'borderedProminent',
|
|
20
24
|
destructive: 'borderedProminent',
|
|
21
25
|
glass: 'glass',
|
|
26
|
+
plain: 'borderless',
|
|
22
27
|
secondary: 'bordered',
|
|
23
28
|
} as const satisfies Record<ButtonVariant, ButtonStyleName>
|
|
24
29
|
|
|
@@ -26,14 +31,56 @@ const TINT_BY_VARIANT = {
|
|
|
26
31
|
accent: 'fill.accent.primary',
|
|
27
32
|
destructive: 'fill.destructive.primary',
|
|
28
33
|
glass: undefined,
|
|
34
|
+
plain: undefined,
|
|
29
35
|
secondary: undefined,
|
|
30
36
|
} as const satisfies Record<ButtonVariant, ColorToken | undefined>
|
|
31
37
|
|
|
38
|
+
/** Both, wherever the button sits: a toolbar or a menu would otherwise show the icon alone. */
|
|
39
|
+
const TITLE_AND_ICON = [labelStyle('titleAndIcon')]
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `borderless` draws nothing around the label, so the label is the button: padding takes it to a 44pt tap target,
|
|
43
|
+
* and a rectangle content shape makes the whole of it (the padding, and the width `wide` gives it) take the tap,
|
|
44
|
+
* not only the glyphs.
|
|
45
|
+
*/
|
|
46
|
+
const TEXT_BUTTON_TARGET = {modifiers: [contentShape(shapes.rectangle())]}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A prominent style (and tinted glass) is coloured by the tint, so a caller's own tint picks the label on it. A text
|
|
50
|
+
* style has nothing to fill, so there the tint is the label's own colour.
|
|
51
|
+
*/
|
|
52
|
+
const labelColor = ({
|
|
53
|
+
disabled,
|
|
54
|
+
filled,
|
|
55
|
+
textOnly,
|
|
56
|
+
theme,
|
|
57
|
+
tint,
|
|
58
|
+
tokens,
|
|
59
|
+
}: {
|
|
60
|
+
disabled: boolean
|
|
61
|
+
filled: boolean
|
|
62
|
+
textOnly: boolean
|
|
63
|
+
theme: Theme
|
|
64
|
+
tint: ColorToken | undefined
|
|
65
|
+
tokens: {content: ColorToken}
|
|
66
|
+
}): ColorToken => {
|
|
67
|
+
if (!tint || disabled) {
|
|
68
|
+
return tokens.content
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (filled) {
|
|
72
|
+
return contentOnTint(tint, theme)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return textOnly ? tint : tokens.content
|
|
76
|
+
}
|
|
77
|
+
|
|
32
78
|
export const Button: ButtonComponent = ({
|
|
33
79
|
accessibilityHint,
|
|
34
80
|
buttonStyle,
|
|
35
81
|
controlSize = 'large',
|
|
36
82
|
disabled = false,
|
|
83
|
+
icon,
|
|
37
84
|
label,
|
|
38
85
|
onPress,
|
|
39
86
|
testID,
|
|
@@ -58,8 +105,10 @@ export const Button: ButtonComponent = ({
|
|
|
58
105
|
*/
|
|
59
106
|
const glassTint = style === 'glass' && resolvedTint ? theme.color(resolvedTint) : undefined
|
|
60
107
|
const filled = Boolean(glassTint) || style === 'borderedProminent' || style === 'glassProminent'
|
|
61
|
-
|
|
62
|
-
|
|
108
|
+
const textOnly = style === 'borderless' || style === 'plain'
|
|
109
|
+
// Only the `plain` variant's own text button is padded out; another variant restyled `plain` keeps its layout.
|
|
110
|
+
const textButton = variant === 'plain' && textOnly
|
|
111
|
+
const content = labelColor({disabled, filled, textOnly, theme, tint, tokens})
|
|
63
112
|
|
|
64
113
|
const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint})
|
|
65
114
|
|
|
@@ -85,12 +134,27 @@ export const Button: ButtonComponent = ({
|
|
|
85
134
|
return list
|
|
86
135
|
}, [accessibilityModifiers, controlSize, disabled, glassTint, resolvedTint, style, theme])
|
|
87
136
|
|
|
137
|
+
const text = (
|
|
138
|
+
<Text color={content} variant="body">
|
|
139
|
+
{label}
|
|
140
|
+
</Text>
|
|
141
|
+
)
|
|
142
|
+
|
|
88
143
|
return (
|
|
89
144
|
<SwiftUIButton modifiers={modifiers} onPress={onPress} testID={testID}>
|
|
90
|
-
<VStack
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
145
|
+
<VStack
|
|
146
|
+
align="center"
|
|
147
|
+
ios={textButton ? TEXT_BUTTON_TARGET : undefined}
|
|
148
|
+
py={glassTint ? 's16' : textButton ? 's12' : undefined}
|
|
149
|
+
wide={wide}
|
|
150
|
+
>
|
|
151
|
+
{icon ? (
|
|
152
|
+
<SwiftUILabel icon={<Icon color={content} name={icon} size="s20" />} modifiers={TITLE_AND_ICON}>
|
|
153
|
+
{text}
|
|
154
|
+
</SwiftUILabel>
|
|
155
|
+
) : (
|
|
156
|
+
text
|
|
157
|
+
)}
|
|
94
158
|
</VStack>
|
|
95
159
|
</SwiftUIButton>
|
|
96
160
|
)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {ColorToken} from '@hzblj/kara-theme'
|
|
2
2
|
import type {ReactNode} from 'react'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import type {IconName} from '../icon'
|
|
5
|
+
|
|
6
|
+
export type ButtonVariant = 'accent' | 'destructive' | 'glass' | 'plain' | 'secondary'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* SwiftUI `ButtonStyle`. iOS only: Compose has no equivalent, so Android ignores it and keeps
|
|
@@ -22,19 +24,28 @@ export type ButtonStyleName =
|
|
|
22
24
|
*/
|
|
23
25
|
export type ButtonControlSize = 'extraLarge' | 'large' | 'mini' | 'regular' | 'small'
|
|
24
26
|
|
|
25
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* A text button: SwiftUI `Button` on iOS, Material's filled button on Android (outlined for `glass`, a `TextButton`
|
|
29
|
+
* for `plain`).
|
|
30
|
+
*/
|
|
26
31
|
export type ButtonProps = {
|
|
27
32
|
/** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
|
|
28
33
|
accessibilityHint?: string
|
|
29
34
|
/**
|
|
30
35
|
* Replaces the style the variant picks: `borderedProminent` for `accent` and `destructive`, `bordered` for
|
|
31
|
-
* `secondary`, `glass` for `glass`. iOS only. Android ignores it.
|
|
36
|
+
* `secondary`, `glass` for `glass`, `borderless` for `plain`. iOS only. Android ignores it.
|
|
32
37
|
*/
|
|
33
38
|
buttonStyle?: ButtonStyleName
|
|
34
39
|
/** SwiftUI's control size. `large` by default. iOS only. Android ignores it. */
|
|
35
40
|
controlSize?: ButtonControlSize
|
|
36
41
|
/** Draws the button in its disabled colours and stops it responding to presses. */
|
|
37
42
|
disabled?: boolean
|
|
43
|
+
/**
|
|
44
|
+
* One of the app's icons, before the label and in the label's colour. iOS sets the two as a SwiftUI `Label`; Android
|
|
45
|
+
* puts the icon in the button's content row, as Material's buttons with an icon do. Screen readers read the label
|
|
46
|
+
* only.
|
|
47
|
+
*/
|
|
48
|
+
icon?: IconName
|
|
38
49
|
/** The button's text. */
|
|
39
50
|
label: string
|
|
40
51
|
/** Called when the button is pressed. */
|
|
@@ -44,10 +55,14 @@ export type ButtonProps = {
|
|
|
44
55
|
/**
|
|
45
56
|
* Overrides the variant's own tint. A prominent style takes it as the fill and picks a contrasting
|
|
46
57
|
* label. `glass` has to reach the material through `glassEffect` instead, so tinting one costs it
|
|
47
|
-
* the stock `.glass` style (see the iOS implementation).
|
|
58
|
+
* the stock `.glass` style (see the iOS implementation). `plain` has no fill, so there it colours the label.
|
|
48
59
|
*/
|
|
49
60
|
tint?: ColorToken
|
|
50
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* The look. `accent` by default. Material has no glass, so on Android `glass` is an outlined button. `plain` is a
|
|
63
|
+
* text button in the accent, with no fill or border, for a lesser action beside a prominent one ("Skip", "Forgot
|
|
64
|
+
* password?"): SwiftUI's `borderless` style, Material's `TextButton`.
|
|
65
|
+
*/
|
|
51
66
|
variant?: ButtonVariant
|
|
52
67
|
/** Stretches the button to the full width it is offered. On by default. */
|
|
53
68
|
wide?: boolean
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {Checkbox as ComposeCheckbox, 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 {spacing, useAppTheme} from '@hzblj/kara-theme'
|
|
4
4
|
import {useMemo} from 'react'
|
|
5
5
|
|
|
@@ -14,17 +14,20 @@ const NOOP = () => undefined
|
|
|
14
14
|
* gets a handler, so a disabled or read-only box keeps the touch target an enabled one has, and lines up with it;
|
|
15
15
|
* `enabled` is what stops the input.
|
|
16
16
|
*/
|
|
17
|
-
export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueChange, value}: CheckboxProps) => {
|
|
17
|
+
export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueChange, testID, value}: CheckboxProps) => {
|
|
18
18
|
useHostGuard('Checkbox')
|
|
19
19
|
const theme = useAppTheme()
|
|
20
20
|
const rowModifiers = useMemo(
|
|
21
21
|
() => (disabled || !onValueChange ? [fillMaxWidth()] : [fillMaxWidth(), clickable(() => onValueChange(!value))]),
|
|
22
22
|
[disabled, onValueChange, value]
|
|
23
23
|
)
|
|
24
|
+
// On the box, not the row: the box is its own toggleable node inside the row, and the one a test reads.
|
|
25
|
+
const controlModifiers = useMemo(() => (testID ? [testIdModifier(testID)] : undefined), [testID])
|
|
24
26
|
const control = (
|
|
25
27
|
<ComposeCheckbox
|
|
26
28
|
colors={{checkedColor: theme.color('fill.accent.primary'), checkmarkColor: theme.color('content.on.accent')}}
|
|
27
29
|
enabled={!disabled}
|
|
30
|
+
modifiers={controlModifiers}
|
|
28
31
|
onCheckedChange={onValueChange ?? NOOP}
|
|
29
32
|
value={value}
|
|
30
33
|
/>
|
|
@@ -6,7 +6,7 @@ import {useMemo} from 'react'
|
|
|
6
6
|
import {useHostGuard} from '../../host-context'
|
|
7
7
|
import type {CheckboxComponent, CheckboxProps} from './checkbox.types'
|
|
8
8
|
|
|
9
|
-
export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueChange, value}: CheckboxProps) => {
|
|
9
|
+
export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueChange, testID, value}: CheckboxProps) => {
|
|
10
10
|
useHostGuard('Checkbox')
|
|
11
11
|
const theme = useAppTheme()
|
|
12
12
|
|
|
@@ -20,5 +20,5 @@ export const Checkbox: CheckboxComponent = ({disabled = false, label, onValueCha
|
|
|
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
|
}
|
|
@@ -8,6 +8,8 @@ export type CheckboxProps = {
|
|
|
8
8
|
label?: string
|
|
9
9
|
/** Called with the new value when the user toggles it. */
|
|
10
10
|
onValueChange?: (value: boolean) => void
|
|
11
|
+
/** Test identifier, forwarded to the native control (on Android the box itself, not the labelled row). */
|
|
12
|
+
testID?: string
|
|
11
13
|
/** Whether it is checked. Controlled: update it from `onValueChange`. */
|
|
12
14
|
value: boolean
|
|
13
15
|
}
|
|
@@ -9,8 +9,10 @@ import type {ColorPickerComponent, ColorPickerProps} from './color-picker.types'
|
|
|
9
9
|
/**
|
|
10
10
|
* The row SwiftUI's `ColorPicker` lays out on iOS, the label leading and the well trailing, around kara's own Compose
|
|
11
11
|
* well: Compose ships no colour picker, so the sheet behind it draws the iOS picker's grid, spectrum and sliders.
|
|
12
|
+
* `disabled` dims the label and the well, which then does not open its sheet.
|
|
12
13
|
*/
|
|
13
14
|
export const ColorPicker: ColorPickerComponent = ({
|
|
15
|
+
disabled = false,
|
|
14
16
|
label,
|
|
15
17
|
onValueChange,
|
|
16
18
|
supportsOpacity = false,
|
|
@@ -21,9 +23,15 @@ export const ColorPicker: ColorPickerComponent = ({
|
|
|
21
23
|
|
|
22
24
|
return (
|
|
23
25
|
<HStack align="center" spacing="s12" testID={testID} wide>
|
|
24
|
-
{label ? <Text>{label}</Text> : null}
|
|
26
|
+
{label ? <Text color={disabled ? 'content.tertiary' : undefined}>{label}</Text> : null}
|
|
25
27
|
<Spacer />
|
|
26
|
-
<ColorWell
|
|
28
|
+
<ColorWell
|
|
29
|
+
enabled={!disabled}
|
|
30
|
+
onValueChange={onValueChange}
|
|
31
|
+
supportsOpacity={supportsOpacity}
|
|
32
|
+
title={label}
|
|
33
|
+
value={value}
|
|
34
|
+
/>
|
|
27
35
|
</HStack>
|
|
28
36
|
)
|
|
29
37
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import {ColorPicker as SwiftUIColorPicker} from '@expo/ui/swift-ui'
|
|
2
|
+
import {disabled as disabledModifier, labelsHidden, opacity as opacityModifier} from '@expo/ui/swift-ui/modifiers'
|
|
3
|
+
import {opacity} from '@hzblj/kara-theme'
|
|
2
4
|
|
|
3
5
|
import {useHostGuard} from '../../host-context'
|
|
6
|
+
import {HStack} from '../h-stack'
|
|
7
|
+
import {Spacer} from '../spacer'
|
|
8
|
+
import {Text} from '../text'
|
|
4
9
|
import type {ColorPickerComponent, ColorPickerProps} from './color-picker.types'
|
|
5
10
|
|
|
11
|
+
// iOS 27 stops a disabled picker but draws its label and well at full strength: the label is then kara's own text,
|
|
12
|
+
// dimmed, and the well takes the opacity.
|
|
13
|
+
const DISABLED = [labelsHidden(), disabledModifier(true), opacityModifier(opacity.o40)]
|
|
14
|
+
|
|
6
15
|
export const ColorPicker: ColorPickerComponent = ({
|
|
16
|
+
disabled = false,
|
|
7
17
|
label,
|
|
8
18
|
onValueChange,
|
|
9
19
|
supportsOpacity = false,
|
|
@@ -12,13 +22,26 @@ export const ColorPicker: ColorPickerComponent = ({
|
|
|
12
22
|
}: ColorPickerProps) => {
|
|
13
23
|
useHostGuard('ColorPicker')
|
|
14
24
|
|
|
15
|
-
|
|
25
|
+
const picker = (
|
|
16
26
|
<SwiftUIColorPicker
|
|
17
27
|
label={label}
|
|
28
|
+
modifiers={disabled ? DISABLED : undefined}
|
|
18
29
|
onSelectionChange={onValueChange}
|
|
19
30
|
selection={value}
|
|
20
31
|
supportsOpacity={supportsOpacity}
|
|
21
32
|
testID={testID}
|
|
22
33
|
/>
|
|
23
34
|
)
|
|
35
|
+
|
|
36
|
+
if (!disabled) {
|
|
37
|
+
return picker
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<HStack align="center" spacing="s12" wide>
|
|
42
|
+
{label ? <Text color="content.tertiary">{label}</Text> : null}
|
|
43
|
+
<Spacer />
|
|
44
|
+
{picker}
|
|
45
|
+
</HStack>
|
|
46
|
+
)
|
|
24
47
|
}
|
|
@@ -6,6 +6,8 @@ import type {ReactNode} from 'react'
|
|
|
6
6
|
* spectrum and RGB sliders.
|
|
7
7
|
*/
|
|
8
8
|
export type ColorPickerProps = {
|
|
9
|
+
/** Stops the user picking a colour, and dims the label and the well. */
|
|
10
|
+
disabled?: boolean
|
|
9
11
|
/** Text beside the colour well, and the Android sheet's title. */
|
|
10
12
|
label?: string
|
|
11
13
|
/** Called with the colour the user picks. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {DateTimePicker} from '@expo/ui/community/datetime-picker'
|
|
2
|
-
import {RNHostView} from '@expo/ui/jetpack-compose'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import {DateTimePicker as ComposeDateTimePicker, Host, RNHostView} from '@expo/ui/jetpack-compose'
|
|
3
|
+
import {alpha, testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
4
|
+
import {opacity, useAppTheme} from '@hzblj/kara-theme'
|
|
5
|
+
import {useCallback, useMemo, useState} from 'react'
|
|
5
6
|
|
|
6
7
|
import {useInsideHost} from '../../host-context'
|
|
7
8
|
import {Box} from '../box'
|
|
@@ -9,6 +10,8 @@ import {Pressable} from '../pressable'
|
|
|
9
10
|
import {RnText} from '../rn-text'
|
|
10
11
|
import type {DatePickerComponent, DatePickerMode, DatePickerProps} from './date-picker.types'
|
|
11
12
|
|
|
13
|
+
const MATCH_HEIGHT_ONLY = {horizontal: false, vertical: true}
|
|
14
|
+
|
|
12
15
|
const formatted = (value: Date, mode: DatePickerMode): string => {
|
|
13
16
|
if (mode === 'time') {
|
|
14
17
|
return value.toLocaleTimeString(undefined, {hour: '2-digit', minute: '2-digit'})
|
|
@@ -21,20 +24,68 @@ const formatted = (value: Date, mode: DatePickerMode): string => {
|
|
|
21
24
|
: date
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
/** Material's calendar counts in UTC days: `value`'s local day goes in as that day's UTC midnight. */
|
|
28
|
+
const utcDayOf = (value: Date): string =>
|
|
29
|
+
new Date(Date.UTC(value.getFullYear(), value.getMonth(), value.getDate())).toISOString()
|
|
30
|
+
|
|
31
|
+
/** The UTC day the calendar reports, back on `value`'s local time of day. */
|
|
32
|
+
const localDayOf = (utcDay: Date, value: Date): Date => {
|
|
33
|
+
const next = new Date(value)
|
|
34
|
+
next.setFullYear(utcDay.getUTCFullYear(), utcDay.getUTCMonth(), utcDay.getUTCDate())
|
|
35
|
+
|
|
36
|
+
return next
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Material's calendar in place, for `graphical`: the month and its days, without the title and headline its dialog
|
|
41
|
+
* shows, in the accent. It also reports its day when it first shows and whenever `value` hands it a new one, so only a
|
|
42
|
+
* day that differs from `value` goes out. It has no disabled state, so a disabled one is dimmed and offers only the
|
|
43
|
+
* selected day.
|
|
44
|
+
*/
|
|
45
|
+
const Calendar = ({disabled, max, min, onValueChange, testID, value}: DatePickerProps) => {
|
|
46
|
+
const theme = useAppTheme()
|
|
47
|
+
|
|
48
|
+
const onDateSelected = useCallback(
|
|
49
|
+
(date: Date) => {
|
|
50
|
+
const next = localDayOf(date, value)
|
|
51
|
+
|
|
52
|
+
if (!disabled && next.getTime() !== value.getTime()) {
|
|
53
|
+
onValueChange?.(next)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[disabled, onValueChange, value]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const modifiers = useMemo(
|
|
60
|
+
() => [...(disabled ? [alpha(opacity.o40)] : []), ...(testID ? [testIdModifier(testID)] : [])],
|
|
61
|
+
[disabled, testID]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<ComposeDateTimePicker
|
|
66
|
+
color={theme.color('fill.accent.primary')}
|
|
67
|
+
displayedComponents="date"
|
|
68
|
+
initialDate={utcDayOf(value)}
|
|
69
|
+
modifiers={modifiers}
|
|
70
|
+
onDateSelected={onDateSelected}
|
|
71
|
+
selectableDates={disabled ? {end: value, start: value} : min || max ? {end: max, start: min} : undefined}
|
|
72
|
+
showHeadline={false}
|
|
73
|
+
showTitle={false}
|
|
74
|
+
showVariantToggle={false}
|
|
75
|
+
variant="picker"
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
24
80
|
/**
|
|
25
|
-
* The value as a field that opens Material's date picker dialog on a tap. Android's picker *is* a dialog
|
|
26
|
-
* opens
|
|
81
|
+
* The value as a field that opens Material's date picker dialog on a tap. Android's picker *is* a dialog (mounted, it
|
|
82
|
+
* opens), so it is mounted only while open, and unmounted on confirm or dismiss, as the component asks its caller to.
|
|
27
83
|
* Both are React Native views; inside a `Screen` or `Island` `RNHostView` carries them into that host, sized to the
|
|
28
|
-
* field (see `useInsideHost`).
|
|
84
|
+
* field (see `useInsideHost`). With `datePickerStyle="graphical"` and a date to pick, it is Material's calendar in
|
|
85
|
+
* place instead: a Compose view, in a host of its own the width of its container, or straight in the enclosing one.
|
|
29
86
|
*/
|
|
30
|
-
export const DatePicker: DatePickerComponent = ({
|
|
31
|
-
max,
|
|
32
|
-
min,
|
|
33
|
-
mode = 'date',
|
|
34
|
-
onValueChange,
|
|
35
|
-
testID,
|
|
36
|
-
value,
|
|
37
|
-
}: DatePickerProps) => {
|
|
87
|
+
export const DatePicker: DatePickerComponent = (props: DatePickerProps) => {
|
|
88
|
+
const {datePickerStyle = 'compact', disabled = false, max, min, mode = 'date', onValueChange, testID, value} = props
|
|
38
89
|
const theme = useAppTheme()
|
|
39
90
|
const insideHost = useInsideHost()
|
|
40
91
|
const [open, setOpen] = useState(false)
|
|
@@ -50,20 +101,39 @@ export const DatePicker: DatePickerComponent = ({
|
|
|
50
101
|
[onValueChange]
|
|
51
102
|
)
|
|
52
103
|
|
|
104
|
+
if (datePickerStyle === 'graphical' && mode !== 'time') {
|
|
105
|
+
const calendar = <Calendar {...props} disabled={disabled} />
|
|
106
|
+
|
|
107
|
+
return insideHost ? (
|
|
108
|
+
calendar
|
|
109
|
+
) : (
|
|
110
|
+
<Host
|
|
111
|
+
colorScheme={theme.dark ? 'dark' : 'light'}
|
|
112
|
+
matchContents={MATCH_HEIGHT_ONLY}
|
|
113
|
+
seedColor={theme.materialSeed}
|
|
114
|
+
>
|
|
115
|
+
{calendar}
|
|
116
|
+
</Host>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
53
120
|
const field = (
|
|
54
121
|
<>
|
|
55
122
|
<Pressable
|
|
56
123
|
alignSelf="flex-start"
|
|
57
124
|
bg="fill.tertiary"
|
|
58
125
|
borderRadius="medium"
|
|
126
|
+
disabled={disabled}
|
|
59
127
|
onPress={show}
|
|
60
128
|
px="s12"
|
|
61
129
|
py="s8"
|
|
62
130
|
testID={testID}
|
|
63
131
|
>
|
|
64
|
-
<RnText variant="body">
|
|
132
|
+
<RnText color={disabled ? 'content.tertiary' : 'content.primary'} variant="body">
|
|
133
|
+
{formatted(value, mode)}
|
|
134
|
+
</RnText>
|
|
65
135
|
</Pressable>
|
|
66
|
-
{open ? (
|
|
136
|
+
{open && !disabled ? (
|
|
67
137
|
<DateTimePicker
|
|
68
138
|
accentColor={theme.colorString('fill.accent.primary')}
|
|
69
139
|
maximumDate={max}
|