@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
|
@@ -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}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import {Host, DatePicker as SwiftUIDatePicker} from '@expo/ui/swift-ui'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
datePickerStyle as datePickerStyleModifier,
|
|
4
|
+
disabled as disabledModifier,
|
|
5
|
+
labelsHidden,
|
|
6
|
+
tint,
|
|
7
|
+
} from '@expo/ui/swift-ui/modifiers'
|
|
3
8
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
9
|
import {useMemo} from 'react'
|
|
5
10
|
|
|
@@ -13,13 +18,15 @@ const COMPONENTS = {
|
|
|
13
18
|
} as const satisfies Record<DatePickerMode, ('date' | 'hourAndMinute')[]>
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
|
-
* The system's compact
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `useInsideHost`).
|
|
21
|
+
* The system's date picker, in the accent. `compact` by default: the value in a pill, and a tap on it opens the
|
|
22
|
+
* calendar popover UIKit owns. `graphical` is that calendar in place, `wheel` the value on wheels. Its own host, sized
|
|
23
|
+
* to the picker on both axes (a host that took the width it was given would lay the pill out against the far edge of
|
|
24
|
+
* it, or past its container's), so it drops into a row the way a `Text` does. The label is the caller's, beside it.
|
|
25
|
+
* Inside a `Screen` or `Island` it renders into that host instead (see `useInsideHost`).
|
|
21
26
|
*/
|
|
22
27
|
export const DatePicker: DatePickerComponent = ({
|
|
28
|
+
datePickerStyle = 'compact',
|
|
29
|
+
disabled = false,
|
|
23
30
|
max,
|
|
24
31
|
min,
|
|
25
32
|
mode = 'date',
|
|
@@ -31,8 +38,13 @@ export const DatePicker: DatePickerComponent = ({
|
|
|
31
38
|
const insideHost = useInsideHost()
|
|
32
39
|
|
|
33
40
|
const modifiers = useMemo(
|
|
34
|
-
() => [
|
|
35
|
-
|
|
41
|
+
() => [
|
|
42
|
+
datePickerStyleModifier(datePickerStyle),
|
|
43
|
+
labelsHidden(),
|
|
44
|
+
tint(theme.color('fill.accent.primary')),
|
|
45
|
+
...(disabled ? [disabledModifier(true)] : []),
|
|
46
|
+
],
|
|
47
|
+
[datePickerStyle, disabled, theme]
|
|
36
48
|
)
|
|
37
49
|
|
|
38
50
|
const picker = (
|
|
@@ -2,24 +2,48 @@ import type {ReactNode} from 'react'
|
|
|
2
2
|
|
|
3
3
|
export type DatePickerMode = 'date' | 'datetime' | 'time'
|
|
4
4
|
|
|
5
|
+
/** How SwiftUI shows the picker: a pill that opens the calendar, the calendar in place, or wheels. */
|
|
6
|
+
export type DatePickerStyleName = 'compact' | 'graphical' | 'wheel'
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
|
-
* A date or time picker: SwiftUI's
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* A date or time picker: SwiftUI's `DatePicker` on iOS (a pill by default, a calendar or wheels with
|
|
10
|
+
* `datePickerStyle`), a field that opens Material's picker dialog on a tap on Android, or Material's calendar in place
|
|
11
|
+
* with `datePickerStyle="graphical"`. The React Native tier: it brings its own host and sizes itself to the value, so it
|
|
12
|
+
* sits in a row beside its label like a `Text`. Inside a `Screen` or `Island` it joins that host instead, beside a
|
|
13
|
+
* native `Text` label.
|
|
9
14
|
*/
|
|
10
15
|
export type DatePickerProps = {
|
|
16
|
+
/**
|
|
17
|
+
* SwiftUI's date picker style. `compact` by default: the value in a pill, which opens the calendar in a popover.
|
|
18
|
+
* `graphical` is the calendar in place, a month at a time, with the time under it in `datetime` mode: what a screen
|
|
19
|
+
* built around picking a day wants. `wheel` puts the value on wheels. Android shows Material's calendar in place for
|
|
20
|
+
* `graphical` in `date` and `datetime` modes (without its title and headline); otherwise it keeps its field and
|
|
21
|
+
* dialog, since Material has no wheel.
|
|
22
|
+
*/
|
|
23
|
+
datePickerStyle?: DatePickerStyleName
|
|
24
|
+
/**
|
|
25
|
+
* Stops the user changing the date, and dims the picker. Material's calendar has no disabled state of its own, so a
|
|
26
|
+
* `graphical` picker on Android is dimmed and offers only the selected day.
|
|
27
|
+
*/
|
|
28
|
+
disabled?: boolean
|
|
11
29
|
/** The latest date that can be picked. Android ignores it in `time` mode. */
|
|
12
30
|
max?: Date
|
|
13
31
|
/** The earliest date that can be picked. Android ignores it in `time` mode. */
|
|
14
32
|
min?: Date
|
|
15
33
|
/**
|
|
16
|
-
* What it picks. `date` by default. Android has no combined dialog, so there `datetime` picks the date
|
|
17
|
-
* `value`'s time of day.
|
|
34
|
+
* What it picks. `date` by default. Android has no combined dialog or calendar, so there `datetime` picks the date
|
|
35
|
+
* and keeps `value`'s time of day.
|
|
18
36
|
*/
|
|
19
37
|
mode?: DatePickerMode
|
|
20
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Called with the new date: on iOS as it changes, on Android when the dialog is confirmed or a day in the calendar
|
|
40
|
+
* is tapped.
|
|
41
|
+
*/
|
|
21
42
|
onValueChange?: (date: Date) => void
|
|
22
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Test identifier, on the native picker on iOS and on Android's calendar, and on the tappable field elsewhere on
|
|
45
|
+
* Android.
|
|
46
|
+
*/
|
|
23
47
|
testID?: string
|
|
24
48
|
/** The selected date. Controlled: update it from `onValueChange`. */
|
|
25
49
|
value: Date
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
import {HorizontalDivider} from '@expo/ui/jetpack-compose'
|
|
1
|
+
import {HorizontalDivider, VerticalDivider} from '@expo/ui/jetpack-compose'
|
|
2
|
+
import {testID as testIdModifier} from '@expo/ui/jetpack-compose/modifiers'
|
|
2
3
|
import {useAppTheme} from '@hzblj/kara-theme'
|
|
4
|
+
import {useMemo} from 'react'
|
|
3
5
|
|
|
4
6
|
import {useHostGuard} from '../../host-context'
|
|
5
7
|
import type {DividerComponent, DividerProps} from './divider.types'
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Compose's dividers. Unlike SwiftUI's, a Compose divider does not turn with its stack, so `axis` picks the one:
|
|
11
|
+
* `VerticalDivider` fills the height of the `Row` around it.
|
|
12
|
+
*/
|
|
13
|
+
export const Divider: DividerComponent = ({axis = 'horizontal', color = 'border.tertiary', testID}: DividerProps) => {
|
|
8
14
|
useHostGuard('Divider')
|
|
9
15
|
const theme = useAppTheme()
|
|
16
|
+
const modifiers = useMemo(() => (testID ? [testIdModifier(testID)] : undefined), [testID])
|
|
10
17
|
|
|
11
|
-
return
|
|
18
|
+
return axis === 'vertical' ? (
|
|
19
|
+
<VerticalDivider color={theme.color(color)} modifiers={modifiers} />
|
|
20
|
+
) : (
|
|
21
|
+
<HorizontalDivider color={theme.color(color)} modifiers={modifiers} />
|
|
22
|
+
)
|
|
12
23
|
}
|
|
@@ -6,6 +6,10 @@ import {useMemo} from 'react'
|
|
|
6
6
|
import {useHostGuard} from '../../host-context'
|
|
7
7
|
import type {DividerComponent, DividerProps} from './divider.types'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* SwiftUI's divider, which turns with its stack: vertical in an `HStack`, horizontal anywhere else. So `axis`, which
|
|
11
|
+
* Android needs to pick its divider, has nothing to do here.
|
|
12
|
+
*/
|
|
9
13
|
export const Divider: DividerComponent = ({color = 'border.tertiary', testID}: DividerProps) => {
|
|
10
14
|
useHostGuard('Divider')
|
|
11
15
|
const theme = useAppTheme()
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import type {ColorToken} from '@hzblj/kara-theme'
|
|
2
2
|
import type {ReactNode} from 'react'
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** Which way the line runs. */
|
|
5
|
+
export type DividerAxis = 'horizontal' | 'vertical'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A hairline between views: SwiftUI `Divider` / Compose `HorizontalDivider`, or `VerticalDivider` with
|
|
9
|
+
* `axis="vertical"`.
|
|
10
|
+
*/
|
|
5
11
|
export type DividerProps = {
|
|
12
|
+
/**
|
|
13
|
+
* Which way the line runs. `horizontal` by default: across a `VStack`. `vertical` stands it up between the children
|
|
14
|
+
* of an `HStack`, as tall as the stack, so give the stack a height (`h`). iOS ignores it: SwiftUI's divider turns by
|
|
15
|
+
* itself, vertical in an `HStack` and horizontal anywhere else. Set it anyway, so Android draws the same.
|
|
16
|
+
*/
|
|
17
|
+
axis?: DividerAxis
|
|
6
18
|
/** The line's colour. `border.tertiary` by default. */
|
|
7
19
|
color?: ColorToken
|
|
8
|
-
/** Test identifier, forwarded to the native view
|
|
20
|
+
/** Test identifier, forwarded to the native view. */
|
|
9
21
|
testID?: string
|
|
10
22
|
}
|
|
11
23
|
|
|
@@ -1,23 +1,73 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ExtendedFloatingActionButton,
|
|
3
|
+
FloatingActionButton,
|
|
4
|
+
LargeFloatingActionButton,
|
|
5
|
+
SmallFloatingActionButton,
|
|
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'
|
|
3
|
-
import {useAppTheme} from '@hzblj/kara-theme'
|
|
9
|
+
import {type SpaceSize, useAppTheme} from '@hzblj/kara-theme'
|
|
10
|
+
import {useMemo} from 'react'
|
|
4
11
|
|
|
5
12
|
import {useHostGuard} from '../../host-context'
|
|
6
13
|
import {Icon} from '../icon'
|
|
7
|
-
import
|
|
14
|
+
import {Text} from '../text'
|
|
15
|
+
import type {FabComponent, FabProps, FabSize} from './fab.types'
|
|
8
16
|
|
|
9
|
-
|
|
17
|
+
/** Material's three FABs, and the icon each draws (`FloatingActionButtonDefaults.LargeIconSize` is 36dp). */
|
|
18
|
+
const SIZES = {
|
|
19
|
+
large: {icon: 's36', Material: LargeFloatingActionButton},
|
|
20
|
+
regular: {icon: 's24', Material: FloatingActionButton},
|
|
21
|
+
small: {icon: 's24', Material: SmallFloatingActionButton},
|
|
22
|
+
} as const satisfies Record<FabSize, {Material: unknown; icon: SpaceSize}>
|
|
23
|
+
|
|
24
|
+
export const Fab: FabComponent = ({
|
|
25
|
+
accessibilityHint,
|
|
26
|
+
accessibilityLabel,
|
|
27
|
+
label,
|
|
28
|
+
name,
|
|
29
|
+
onPress,
|
|
30
|
+
size = 'regular',
|
|
31
|
+
testID,
|
|
32
|
+
}: FabProps) => {
|
|
10
33
|
useHostGuard('Fab')
|
|
11
34
|
|
|
12
35
|
const theme = useAppTheme()
|
|
13
|
-
const
|
|
36
|
+
const accessibilityModifiers = useAccessibilityModifiers({accessibilityHint}, accessibilityHint !== undefined)
|
|
37
|
+
const modifiers = useMemo(
|
|
38
|
+
() => (testID ? [testIdModifier(testID), ...accessibilityModifiers] : accessibilityModifiers),
|
|
39
|
+
[accessibilityModifiers, testID]
|
|
40
|
+
)
|
|
41
|
+
const containerColor = theme.color('fill.accent.primary')
|
|
42
|
+
|
|
43
|
+
/*
|
|
44
|
+
* `@expo/ui`'s FABs are slot-based: content outside `.Icon` (and the extended one's `.Text`) is not composed at all.
|
|
45
|
+
* The label rides on the icon, as for every FAB, and the text beside it is hidden from TalkBack so the button is not
|
|
46
|
+
* read twice; a label on the FAB itself would clear the click inside it, which Material's FAB composes after our
|
|
47
|
+
* modifiers.
|
|
48
|
+
*/
|
|
49
|
+
if (label) {
|
|
50
|
+
return (
|
|
51
|
+
<ExtendedFloatingActionButton containerColor={containerColor} modifiers={modifiers} onClick={onPress}>
|
|
52
|
+
<ExtendedFloatingActionButton.Icon>
|
|
53
|
+
<Icon accessibilityLabel={accessibilityLabel} color="content.on.accent" name={name} />
|
|
54
|
+
</ExtendedFloatingActionButton.Icon>
|
|
55
|
+
<ExtendedFloatingActionButton.Text>
|
|
56
|
+
<Text accessibilityHidden color="content.on.accent" variant="body">
|
|
57
|
+
{label}
|
|
58
|
+
</Text>
|
|
59
|
+
</ExtendedFloatingActionButton.Text>
|
|
60
|
+
</ExtendedFloatingActionButton>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const {icon, Material} = SIZES[size]
|
|
14
65
|
|
|
15
66
|
return (
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</FloatingActionButton>
|
|
67
|
+
<Material containerColor={containerColor} modifiers={modifiers} onClick={onPress}>
|
|
68
|
+
<Material.Icon>
|
|
69
|
+
<Icon accessibilityLabel={accessibilityLabel} color="content.on.accent" name={name} size={icon} />
|
|
70
|
+
</Material.Icon>
|
|
71
|
+
</Material>
|
|
22
72
|
)
|
|
23
73
|
}
|
|
@@ -1,23 +1,49 @@
|
|
|
1
1
|
import {Button} from '@expo/ui/swift-ui'
|
|
2
2
|
import {buttonStyle} from '@expo/ui/swift-ui/modifiers'
|
|
3
|
-
import {useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
|
|
3
|
+
import {type ResponsiveSx, useAccessibilityModifiers, useSxModifiers} from '@hzblj/kara-sx'
|
|
4
|
+
import type {SpaceSize} from '@hzblj/kara-theme'
|
|
4
5
|
import {useMemo} from 'react'
|
|
5
6
|
import {useHostGuard} from '../../host-context'
|
|
7
|
+
import {HStack} from '../h-stack'
|
|
6
8
|
import {Icon} from '../icon'
|
|
7
|
-
import
|
|
9
|
+
import {Text} from '../text'
|
|
10
|
+
import type {FabComponent, FabProps, FabSize} from './fab.types'
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
/** Material's sizes, in points: the circle across and the icon in it. */
|
|
13
|
+
const SIZES = {
|
|
14
|
+
large: {icon: 's36', side: 96},
|
|
15
|
+
regular: {icon: 's24', side: 56},
|
|
16
|
+
small: {icon: 's24', side: 40},
|
|
17
|
+
} as const satisfies Record<FabSize, {icon: SpaceSize; side: number}>
|
|
18
|
+
|
|
19
|
+
const SURFACE = {bg: 'fill.accent.primary', borderRadius: 'full', shadow: 'btn.accent'} as const satisfies ResponsiveSx
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Material's extended FAB in kara's iOS language: the regular FAB's height, accent and shadow, drawn as a capsule (the
|
|
23
|
+
* shape a round button takes once it has words), 16 before the icon and 20 after the text, as Material pads it.
|
|
24
|
+
*/
|
|
25
|
+
const EXTENDED = {...SURFACE, h: SIZES.regular.side, pl: 's16', pr: 's20'} as const satisfies ResponsiveSx
|
|
26
|
+
|
|
27
|
+
export const Fab: FabComponent = ({
|
|
28
|
+
accessibilityHint,
|
|
29
|
+
accessibilityLabel,
|
|
30
|
+
label,
|
|
31
|
+
name,
|
|
32
|
+
onPress,
|
|
33
|
+
size = 'regular',
|
|
34
|
+
testID,
|
|
35
|
+
}: FabProps) => {
|
|
10
36
|
useHostGuard('Fab')
|
|
11
|
-
const
|
|
37
|
+
const extended = Boolean(label)
|
|
38
|
+
const {icon, side} = SIZES[size]
|
|
39
|
+
|
|
40
|
+
// An icon alone carries the label; beside text, the button does, in place of what the text would say.
|
|
41
|
+
const accessibilityModifiers = useAccessibilityModifiers(
|
|
42
|
+
extended ? {accessibilityHint, accessibilityLabel} : {accessibilityHint}
|
|
43
|
+
)
|
|
12
44
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
borderRadius: 'full',
|
|
16
|
-
h: 56,
|
|
17
|
-
p: 's16',
|
|
18
|
-
shadow: 'btn.accent',
|
|
19
|
-
w: 56,
|
|
20
|
-
})
|
|
45
|
+
// The frame centres the icon, so one circle serves every size: `regular` is the 56pt one it always was.
|
|
46
|
+
const sxModifiers = useSxModifiers(extended ? EXTENDED : {...SURFACE, h: side, w: side}, 'center')
|
|
21
47
|
const modifiers = useMemo(
|
|
22
48
|
() => [buttonStyle('plain'), ...sxModifiers, ...accessibilityModifiers],
|
|
23
49
|
[accessibilityModifiers, sxModifiers]
|
|
@@ -25,7 +51,16 @@ export const Fab: FabComponent = ({accessibilityHint, accessibilityLabel, name,
|
|
|
25
51
|
|
|
26
52
|
return (
|
|
27
53
|
<Button modifiers={modifiers} onPress={onPress} testID={testID}>
|
|
28
|
-
|
|
54
|
+
{extended ? (
|
|
55
|
+
<HStack align="center" spacing="s12">
|
|
56
|
+
<Icon color="content.on.accent" name={name} />
|
|
57
|
+
<Text color="content.on.accent" variant="body">
|
|
58
|
+
{label}
|
|
59
|
+
</Text>
|
|
60
|
+
</HStack>
|
|
61
|
+
) : (
|
|
62
|
+
<Icon accessibilityLabel={accessibilityLabel} color="content.on.accent" name={name} size={icon} />
|
|
63
|
+
)}
|
|
29
64
|
</Button>
|
|
30
65
|
)
|
|
31
66
|
}
|
|
@@ -2,23 +2,41 @@ import type {ReactNode} from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type {IconName} from '../icon'
|
|
4
4
|
|
|
5
|
+
/** Material's three floating action buttons: 40, 56 and 96 across. */
|
|
6
|
+
export type FabSize = 'large' | 'regular' | 'small'
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
|
-
* A floating action button: Material's `FloatingActionButton` on Android,
|
|
7
|
-
*
|
|
9
|
+
* A floating action button: Material's `FloatingActionButton` on Android, an accent circle (a plain SwiftUI `Button`)
|
|
10
|
+
* on iOS. With a `label` it is the extended one, the icon and the text side by side: Material's
|
|
11
|
+
* `ExtendedFloatingActionButton`, an accent capsule on iOS. It renders inline; pinning it to a corner is up to the
|
|
12
|
+
* screen.
|
|
8
13
|
*/
|
|
9
14
|
export type FabProps = {
|
|
10
15
|
/**
|
|
11
16
|
* What VoiceOver and TalkBack say for the button, since an icon has no words of its own: "Share", "Add to
|
|
12
|
-
* favourites". Required for that reason.
|
|
17
|
+
* favourites". Required for that reason. With a `label` it is still what they say, in place of the label, so give
|
|
18
|
+
* it the label's words (or words that start with them), and Voice Control finds the button by what it shows.
|
|
13
19
|
*/
|
|
14
20
|
accessibilityLabel: string
|
|
15
21
|
/** What pressing it does, when the label alone does not say. See `AccessibilityProps`. */
|
|
16
22
|
accessibilityHint?: string
|
|
23
|
+
/**
|
|
24
|
+
* Text beside the icon, which makes it an extended FAB: "Compose", "New post". For a screen whose main action needs
|
|
25
|
+
* its words. Material's `ExtendedFloatingActionButton` on Android; on iOS the same accent, 56pt tall, as a capsule.
|
|
26
|
+
*/
|
|
27
|
+
label?: string
|
|
17
28
|
/** The icon, one of the app's, drawn in `content.on.accent`. */
|
|
18
29
|
name: IconName
|
|
19
30
|
/** Called when the button is pressed. */
|
|
20
31
|
onPress?: () => void
|
|
21
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* `regular` by default, 56 across. `small` is 40, for a secondary action beside a regular one; `large` is 96, with a
|
|
34
|
+
* 36 icon, for a screen built around one action. Material's `SmallFloatingActionButton` and
|
|
35
|
+
* `LargeFloatingActionButton` on Android, the same sizes of circle on iOS. A FAB with a `label` has one size and
|
|
36
|
+
* ignores it.
|
|
37
|
+
*/
|
|
38
|
+
size?: FabSize
|
|
39
|
+
/** Test identifier, forwarded to the native view. */
|
|
22
40
|
testID?: string
|
|
23
41
|
}
|
|
24
42
|
|