@hzblj/kara-components 0.17.0 → 0.19.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/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/segmented-control/segmented-control.android.js +66 -12
- package/lib/module/atoms/segmented-control/segmented-control.android.js.map +1 -1
- package/lib/module/atoms/segmented-control/segmented-control.ios.js +19 -8
- package/lib/module/atoms/segmented-control/segmented-control.ios.js.map +1 -1
- package/lib/module/atoms/segmented-control/segments.js +16 -0
- package/lib/module/atoms/segmented-control/segments.js.map +1 -0
- 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/segmented-control/segmented-control.ios.d.ts +3 -1
- package/lib/typescript/atoms/segmented-control/segmented-control.types.d.ts +26 -3
- package/lib/typescript/atoms/segmented-control/segments.d.ts +6 -0
- 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/segmented-control/segmented-control.android.tsx +69 -10
- package/src/atoms/segmented-control/segmented-control.ios.tsx +24 -5
- package/src/atoms/segmented-control/segmented-control.types.ts +28 -3
- package/src/atoms/segmented-control/segments.ts +16 -0
- 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
|
@@ -9,8 +9,15 @@ export type SliderProps = {
|
|
|
9
9
|
min?: number;
|
|
10
10
|
/** Called with the new value as the thumb moves. */
|
|
11
11
|
onValueChange?: (value: number) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Called once with the final value when the user lets go of the slider: where a seek or a volume commits, rather
|
|
14
|
+
* than at every step of the drag.
|
|
15
|
+
*/
|
|
16
|
+
onValueChangeEnd?: (value: number) => void;
|
|
12
17
|
/** The increment between stops; leave it unset for a continuous slider. On Android every stop plays a `tick`. */
|
|
13
18
|
step?: number;
|
|
19
|
+
/** Test identifier, forwarded to the native view. */
|
|
20
|
+
testID?: string;
|
|
14
21
|
/** Between `min` and `max`. Controlled: pass back what `onValueChange` reports. */
|
|
15
22
|
value: number;
|
|
16
23
|
};
|
|
@@ -10,6 +10,8 @@ export type SwitchProps = {
|
|
|
10
10
|
label?: string;
|
|
11
11
|
/** Called with the new value when the user flips it. On Android a flip plays `toggleOn` / `toggleOff`. */
|
|
12
12
|
onValueChange?: (value: boolean) => void;
|
|
13
|
+
/** Test identifier, forwarded to the native switch (on Android the switch itself, not the labelled row). */
|
|
14
|
+
testID?: string;
|
|
13
15
|
/** Whether it is on. Controlled: pass back what `onValueChange` reports. */
|
|
14
16
|
value: boolean;
|
|
15
17
|
};
|
|
@@ -5,6 +5,8 @@ export type TextSpan = {
|
|
|
5
5
|
color?: ColorToken;
|
|
6
6
|
text: string;
|
|
7
7
|
};
|
|
8
|
+
/** The string and number children, joined: the text of a `Text` without spans, however JSX split it. */
|
|
9
|
+
export declare const textOf: (children: ReactNode) => string;
|
|
8
10
|
/**
|
|
9
11
|
* The children as runs when any of them is a nested `Text` (`type`, the platform's own component), `null` when they
|
|
10
12
|
* are only text. A span keeps its `color`; anything else it sets is the outer text's.
|
|
@@ -4,6 +4,8 @@ import type { ReactNode } from 'react';
|
|
|
4
4
|
export type TextAlign = 'center' | 'left' | 'right';
|
|
5
5
|
export type TextDecoration = 'none' | 'strikethrough' | 'underline';
|
|
6
6
|
export type TextTransform = 'lowercase' | 'uppercase';
|
|
7
|
+
/** Where the ellipsis goes in a text cut short: its start, its middle or its end. */
|
|
8
|
+
export type TextTruncation = 'head' | 'middle' | 'tail';
|
|
7
9
|
/**
|
|
8
10
|
* SwiftUI `Text` / Compose `Text` in a theme typography variant. A native atom: outside a host, use `RnText`. It takes
|
|
9
11
|
* the native `sx` surface too: padding, a frame (`wide` lines it up by `align`), a background, and the `ios` bucket's
|
|
@@ -29,7 +31,7 @@ export type TextProps = ResponsiveSx & AccessibilityProps & {
|
|
|
29
31
|
color?: ColorToken;
|
|
30
32
|
/** An underline or a strikethrough. */
|
|
31
33
|
decoration?: TextDecoration;
|
|
32
|
-
/** The most lines it wraps to.
|
|
34
|
+
/** The most lines it wraps to. A text cut short ends with an ellipsis, where `truncation` puts it. */
|
|
33
35
|
numberOfLines?: number;
|
|
34
36
|
/**
|
|
35
37
|
* Sets it in the theme's fixed-width face, at the variant's size and weight: the app's `fonts.mono`, else Menlo on
|
|
@@ -46,6 +48,12 @@ export type TextProps = ResponsiveSx & AccessibilityProps & {
|
|
|
46
48
|
testID?: string;
|
|
47
49
|
/** Upper- or lower-cases the text. Android applies it only when `children` is a plain string. */
|
|
48
50
|
transform?: TextTransform;
|
|
51
|
+
/**
|
|
52
|
+
* Where the ellipsis goes in a text cut short. `tail` by default: the end of the last line. `head` keeps the end of
|
|
53
|
+
* the text and `middle` both of its ends, for a file name, a path or an address. Android puts it at the head or in
|
|
54
|
+
* the middle of a single line only (`numberOfLines={1}`), as Compose does; a longer text there ends at the tail.
|
|
55
|
+
*/
|
|
56
|
+
truncation?: TextTruncation;
|
|
49
57
|
/** The typography variant. `body` by default. */
|
|
50
58
|
variant?: TextVariant;
|
|
51
59
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TextFieldAutoCapitalize, TextFieldKeyboard } from './text-field.types';
|
|
2
|
+
/** The capitalisation asked for, else `none` for an address; `undefined` leaves the platform's own. */
|
|
3
|
+
export declare const capitalizationFor: (keyboard: TextFieldKeyboard, autoCapitalize: TextFieldAutoCapitalize | undefined) => TextFieldAutoCapitalize | undefined;
|
|
4
|
+
/** Autocorrection as asked for, else on for everything but an address. */
|
|
5
|
+
export declare const autoCorrectFor: (keyboard: TextFieldKeyboard, autoCorrect: boolean | undefined) => boolean;
|
|
6
|
+
/** A no-op for a ref call that lands after the native view has gone. */
|
|
7
|
+
export declare const ignoreDetachedView: () => undefined;
|
|
@@ -1,9 +1,50 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
1
|
+
import type { ReactNode, Ref } from 'react';
|
|
2
|
+
/** The keyboard a field brings up. */
|
|
3
|
+
export type TextFieldKeyboard = 'decimal' | 'default' | 'email' | 'number' | 'phone' | 'url';
|
|
4
|
+
/** When the keyboard turns Shift on by itself. */
|
|
5
|
+
export type TextFieldAutoCapitalize = 'characters' | 'none' | 'sentences' | 'words';
|
|
6
|
+
/** What the return key says. */
|
|
7
|
+
export type TextFieldSubmitLabel = 'done' | 'go' | 'next' | 'search' | 'send';
|
|
8
|
+
/**
|
|
9
|
+
* What a field holds, so the system can offer to fill it in: an account's email or password, a new strong password,
|
|
10
|
+
* the code from a text message, the user's own name, phone number or address.
|
|
11
|
+
*/
|
|
12
|
+
export type TextFieldContentType = 'addressCity' | 'countryName' | 'creditCardNumber' | 'emailAddress' | 'familyName' | 'fullStreetAddress' | 'givenName' | 'name' | 'newPassword' | 'oneTimeCode' | 'password' | 'postalCode' | 'streetAddressLine1' | 'streetAddressLine2' | 'telephoneNumber' | 'username';
|
|
13
|
+
/** Drives a `TextField` from code, as when `onSubmit` moves on to the next field. */
|
|
14
|
+
export type TextFieldRef = {
|
|
15
|
+
/** Ends editing: the field gives up focus and the keyboard goes down. */
|
|
16
|
+
blur: () => void;
|
|
17
|
+
/** Empties the field and calls `onChangeText` with `''`. */
|
|
18
|
+
clear: () => void;
|
|
19
|
+
/** Starts editing: the field takes focus and brings up the keyboard. */
|
|
20
|
+
focus: () => void;
|
|
21
|
+
};
|
|
2
22
|
/**
|
|
3
23
|
* An uncontrolled text field with an optional label above it: SwiftUI `TextField` (`SecureField` when `secure`) /
|
|
4
|
-
* Compose `OutlinedTextField`.
|
|
24
|
+
* Compose `OutlinedTextField`. `keyboard`, `autoCapitalize`, `autoCorrect` and `contentType` fit it to the text it
|
|
25
|
+
* takes; `submitLabel`, `onSubmit` and `ref` chain fields so the return key moves on to the next.
|
|
5
26
|
*/
|
|
6
27
|
export type TextFieldProps = {
|
|
28
|
+
/**
|
|
29
|
+
* When the keyboard turns Shift on by itself: never (`none`), at the start of `sentences`, of `words`, or for all
|
|
30
|
+
* `characters`. Unset, it is `none` for the `email` and `url` keyboards, and otherwise each platform's own:
|
|
31
|
+
* `sentences` on iOS, `none` on Android.
|
|
32
|
+
*/
|
|
33
|
+
autoCapitalize?: TextFieldAutoCapitalize;
|
|
34
|
+
/**
|
|
35
|
+
* Lets the keyboard correct words as they are typed and suggest the next. On unless the keyboard is `email` or
|
|
36
|
+
* `url`, where a correction would only break an address. A `secure` field never corrects.
|
|
37
|
+
*/
|
|
38
|
+
autoCorrect?: boolean;
|
|
39
|
+
/** Takes focus, and brings up the keyboard, as soon as the field appears. Off by default. */
|
|
40
|
+
autoFocus?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* What the field holds, so the system can offer to fill it in: a saved `username` or `password` from the keychain
|
|
43
|
+
* or a password manager, a strong `newPassword` for a sign-up, the `oneTimeCode` from a text message, the user's own
|
|
44
|
+
* `emailAddress`, `name`, `telephoneNumber` or address. iOS's `textContentType`; on Android a Compose autofill
|
|
45
|
+
* hint, which an autofill service (Google's, a password manager) answers.
|
|
46
|
+
*/
|
|
47
|
+
contentType?: TextFieldContentType;
|
|
7
48
|
/** The text it starts with. Read once, on mount: the field is uncontrolled. */
|
|
8
49
|
defaultValue?: string;
|
|
9
50
|
/** Stops editing and dims the field. On iOS it outranks `error`. */
|
|
@@ -14,15 +55,47 @@ export type TextFieldProps = {
|
|
|
14
55
|
* colour is a pale red in dark mode. `true` marks it without a message.
|
|
15
56
|
*/
|
|
16
57
|
error?: boolean | string;
|
|
58
|
+
/**
|
|
59
|
+
* The keyboard it brings up. `default` by default. `email` and `url` add their `@` and `.` keys and turn
|
|
60
|
+
* capitals and corrections off unless `autoCapitalize` or `autoCorrect` say otherwise; `number` is a pad of digits,
|
|
61
|
+
* `decimal` adds the locale's decimal separator, `phone` is a dial pad. With `secure`, Android brings up its password
|
|
62
|
+
* keyboard (a numeric one for `number`).
|
|
63
|
+
*/
|
|
64
|
+
keyboard?: TextFieldKeyboard;
|
|
17
65
|
/** Shown above the field, in `labelMedium`. */
|
|
18
66
|
label?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The most characters it takes. What is typed past it is cut off as the user types; `defaultValue` is not. No limit
|
|
69
|
+
* by default.
|
|
70
|
+
*/
|
|
71
|
+
maxLength?: number;
|
|
19
72
|
/** Wraps the text and grows the field to fit it. iOS ignores it when `secure`. */
|
|
20
73
|
multiline?: boolean;
|
|
21
|
-
/** Called with the whole text after every change. */
|
|
74
|
+
/** Called with the whole text after every change, `clear()` included. */
|
|
22
75
|
onChangeText?: (text: string) => void;
|
|
76
|
+
/** Called with `true` when the field starts editing and `false` when it stops. */
|
|
77
|
+
onFocusChange?: (focused: boolean) => void;
|
|
78
|
+
/**
|
|
79
|
+
* Called with the whole text when the return key is pressed. A `multiline` field never calls it: its return key
|
|
80
|
+
* starts a new line. Neither do iOS's `number`, `decimal` and `phone` pads, which have no return key.
|
|
81
|
+
*/
|
|
82
|
+
onSubmit?: (text: string) => void;
|
|
83
|
+
/**
|
|
84
|
+
* Shown in the empty field, in the platform's placeholder colour, until the first character is typed. A hint at
|
|
85
|
+
* the format (`name@example.com`), not a replacement for `label`.
|
|
86
|
+
*/
|
|
87
|
+
placeholder?: string;
|
|
88
|
+
/** `focus`, `blur` and `clear`, for moving to the next field from `onSubmit`. */
|
|
89
|
+
ref?: Ref<TextFieldRef>;
|
|
23
90
|
/** Masks the text, for a password. */
|
|
24
91
|
secure?: boolean;
|
|
25
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* What the return key says. Unset, each platform's own (`return` on iOS). It names the key; what pressing it does
|
|
94
|
+
* is up to `onSubmit`, so a `next` focuses the following field through its `ref`. Android's `next` also moves focus
|
|
95
|
+
* to the next field in the same host by itself. A `multiline` field ignores it: its return key starts a new line.
|
|
96
|
+
*/
|
|
97
|
+
submitLabel?: TextFieldSubmitLabel;
|
|
98
|
+
/** Test identifier, forwarded to the native field (not the label above it). */
|
|
26
99
|
testID?: string;
|
|
27
100
|
};
|
|
28
101
|
export type TextFieldComponent = (props: TextFieldProps) => ReactNode;
|
|
@@ -12,5 +12,7 @@ export type CollapsibleProps = {
|
|
|
12
12
|
label: string;
|
|
13
13
|
/** The user pressed the row; it passes the state the row asks for. */
|
|
14
14
|
onExpandedChange: (expanded: boolean) => void;
|
|
15
|
+
/** Test identifier: on SwiftUI's `DisclosureGroup` on iOS, on the pressable row on Android. */
|
|
16
|
+
testID?: string;
|
|
15
17
|
};
|
|
16
18
|
export type CollapsibleComponent = (props: CollapsibleProps) => ReactNode;
|
|
@@ -11,10 +11,14 @@ export type RadioOption = {
|
|
|
11
11
|
* native atom: it renders inside `<Screen>` or `<Island>`.
|
|
12
12
|
*/
|
|
13
13
|
export type RadioGroupProps = {
|
|
14
|
+
/** Stops the user changing the selection, and dims the options. */
|
|
15
|
+
disabled?: boolean;
|
|
14
16
|
/** The user pressed an option; it passes that option's `value`. */
|
|
15
17
|
onValueChange?: (value: string) => void;
|
|
16
18
|
/** The choices, top to bottom. */
|
|
17
19
|
options: RadioOption[];
|
|
20
|
+
/** Test identifier, on the whole group. Reach an option by its label. */
|
|
21
|
+
testID?: string;
|
|
18
22
|
/** The selected option's `value`. Update it from `onValueChange`. */
|
|
19
23
|
value: string;
|
|
20
24
|
};
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { TextFieldAutoCapitalize } from '../../atoms/text-field';
|
|
2
3
|
/**
|
|
3
4
|
* A search field drawn the same on both platforms: a rounded box with a magnifier, a `TextInput` and a clear button
|
|
4
|
-
* while there is text. The
|
|
5
|
+
* while there is text. The keyboard's return key reads Search, and a query is taken as typed: no capitals and no
|
|
6
|
+
* corrections unless asked for. The React Native tier: it needs no host.
|
|
5
7
|
*/
|
|
6
8
|
export type SearchBarProps = {
|
|
9
|
+
/** When the keyboard turns Shift on by itself. `none` by default: a query is not a sentence. */
|
|
10
|
+
autoCapitalize?: TextFieldAutoCapitalize;
|
|
11
|
+
/** Lets the keyboard correct words as they are typed. Off by default, so a query is searched as typed. */
|
|
12
|
+
autoCorrect?: boolean;
|
|
13
|
+
/** Takes focus, and brings up the keyboard, as soon as the field appears. Off by default. */
|
|
14
|
+
autoFocus?: boolean;
|
|
7
15
|
/** Every edit, and `''` when the clear button is pressed. */
|
|
8
16
|
onChangeText?: (text: string) => void;
|
|
17
|
+
/** Called with the query when the keyboard's Search key is pressed. The keyboard goes down after it. */
|
|
18
|
+
onSubmit?: (text: string) => void;
|
|
9
19
|
/** Shown while the field is empty. `Search` by default. */
|
|
10
20
|
placeholder?: string;
|
|
11
21
|
/** Test identifier, put on the field's container view rather than on the `TextInput`. */
|
|
@@ -2,6 +2,7 @@ import type { StepperComponent } from './stepper.types';
|
|
|
2
2
|
/**
|
|
3
3
|
* SwiftUI's `Stepper` at the end of a row, the label leading and the value just before the control — the way iOS's own
|
|
4
4
|
* settings show a stepped value. The value rolls to the next one (`numericText`), up or down with the change, in
|
|
5
|
-
* monospaced digits so the row does not jump. With motion reduced it cross-fades instead.
|
|
5
|
+
* monospaced digits so the row does not jump. With motion reduced it cross-fades instead. Its buttons are labelled by
|
|
6
|
+
* the system, so `decrementLabel` and `incrementLabel` are Android's alone.
|
|
6
7
|
*/
|
|
7
8
|
export declare const Stepper: StepperComponent;
|
|
@@ -4,6 +4,18 @@ import type { ReactNode } from 'react';
|
|
|
4
4
|
* two icon buttons in a row. A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
5
5
|
*/
|
|
6
6
|
export type StepperProps = {
|
|
7
|
+
/**
|
|
8
|
+
* What TalkBack says for the minus button. `Decrement` by default: pass the app's own words, in its language.
|
|
9
|
+
* Android only. iOS ignores it: its native stepper labels its buttons itself, in the device's language.
|
|
10
|
+
*/
|
|
11
|
+
decrementLabel?: string;
|
|
12
|
+
/** Stops the user changing the value, and dims the row. */
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* What TalkBack says for the plus button. `Increment` by default: pass the app's own words, in its language. Android
|
|
16
|
+
* only. iOS ignores it: its native stepper labels its buttons itself, in the device's language.
|
|
17
|
+
*/
|
|
18
|
+
incrementLabel?: string;
|
|
7
19
|
/** The leading text. The value is shown too: before the control on iOS, between the buttons on Android. */
|
|
8
20
|
label: string;
|
|
9
21
|
/** The highest value. When unset, Android has no upper limit and iOS stops at `100`. */
|
|
@@ -14,6 +26,11 @@ export type StepperProps = {
|
|
|
14
26
|
onValueChange: (value: number) => void;
|
|
15
27
|
/** How much one press adds or takes away. `1` by default. */
|
|
16
28
|
step?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Test identifier, on the control rather than the row: SwiftUI's `Stepper` on iOS, the minus and plus buttons with
|
|
31
|
+
* the value between them on Android. Reach a button by what screen readers say for it.
|
|
32
|
+
*/
|
|
33
|
+
testID?: string;
|
|
17
34
|
/** The current value. Update it from `onValueChange`. iOS's native stepper counts in whole numbers. */
|
|
18
35
|
value: number;
|
|
19
36
|
};
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { ActionSheetComponent } from './action-sheet.types';
|
|
2
|
+
/**
|
|
3
|
+
* `titleVisibility="visible"`: left to `automatic`, SwiftUI drops the title of a dialog presented as an action sheet,
|
|
4
|
+
* and the actions lose what they are about.
|
|
5
|
+
*/
|
|
2
6
|
export declare const ActionSheet: ActionSheetComponent;
|
|
@@ -2,14 +2,19 @@ import type { ReactNode } from 'react';
|
|
|
2
2
|
import type { MenuAction } from '../menu';
|
|
3
3
|
/**
|
|
4
4
|
* A list of actions to pick one from: SwiftUI's confirmation dialog on iOS, a `Sheet` of Material rows (icon and label)
|
|
5
|
-
* under the title on Android. A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
5
|
+
* under the title and message on Android. A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
6
6
|
*/
|
|
7
7
|
export type ActionSheetProps = {
|
|
8
|
-
/** The choices, top to bottom. Picking one closes the sheet. */
|
|
8
|
+
/** The choices, top to bottom. Picking one closes the sheet. A `disabled` one stays in the list, greyed. */
|
|
9
9
|
actions: MenuAction[];
|
|
10
|
+
/**
|
|
11
|
+
* A line under the title saying what the choice is about: "The photo will be removed from all your devices." Unset,
|
|
12
|
+
* the title stands alone.
|
|
13
|
+
*/
|
|
14
|
+
message?: string;
|
|
10
15
|
/** The sheet closed (after a pick too), so turn `visible` off. */
|
|
11
16
|
onDismiss: () => void;
|
|
12
|
-
/** The heading above the actions
|
|
17
|
+
/** The heading above the actions, shown on both platforms. */
|
|
13
18
|
title: string;
|
|
14
19
|
/** Presents the sheet while true. */
|
|
15
20
|
visible: boolean;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
/**
|
|
3
|
-
* A modal alert with a confirm button and
|
|
4
|
-
* atom: it renders inside `<Screen>` or `<Island>`.
|
|
3
|
+
* A modal alert with a confirm button and a cancel button (or the confirm button alone): SwiftUI's alert on iOS,
|
|
4
|
+
* Material's `AlertDialog` on Android. A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
5
5
|
*/
|
|
6
6
|
export type AlertProps = {
|
|
7
|
+
/**
|
|
8
|
+
* The cancel button's label, in the app's language: "Zrušit". `Cancel` by default. `null` leaves the button out, for
|
|
9
|
+
* an alert that only informs and is closed by its confirm button alone ("OK").
|
|
10
|
+
*/
|
|
11
|
+
cancelLabel?: string | null;
|
|
7
12
|
/** The confirm button's label. `OK` by default. */
|
|
8
13
|
confirmLabel?: string;
|
|
9
14
|
/** Marks the confirm button destructive: the system's destructive role on iOS, the destructive colour on Android. */
|
|
@@ -12,7 +17,7 @@ export type AlertProps = {
|
|
|
12
17
|
message?: string;
|
|
13
18
|
/** The confirm button was pressed. `onDismiss` follows, as it does for every way the alert closes. */
|
|
14
19
|
onConfirm?: () => void;
|
|
15
|
-
/** The alert closed (
|
|
20
|
+
/** The alert closed (any of its buttons, or on Android a tap outside or back), so turn `visible` off. */
|
|
16
21
|
onDismiss: () => void;
|
|
17
22
|
/** The alert's heading. */
|
|
18
23
|
title: string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ColorToken } from '@hzblj/kara-theme';
|
|
2
|
+
import type { IconName } from '../../atoms/icon';
|
|
3
|
+
import type { MenuAction, MenuProps } from './menu.types';
|
|
4
|
+
/**
|
|
5
|
+
* An action's colour in a row kara draws itself (the Android menus and sheets): greyed when disabled, as `IconButton`
|
|
6
|
+
* greys, red when destructive. A Compose `DropdownMenuItem` cannot grey its own text here, since the text sets its
|
|
7
|
+
* colour explicitly.
|
|
8
|
+
*/
|
|
9
|
+
export declare const menuActionColor: ({ destructive, disabled }: MenuAction) => ColorToken;
|
|
10
|
+
/** A trigger with no text: the props' union guarantees its icon and the words a screen reader says for it. */
|
|
11
|
+
export declare const isIconOnly: (props: MenuProps) => props is MenuProps & {
|
|
12
|
+
accessibilityLabel: string;
|
|
13
|
+
icon: IconName;
|
|
14
|
+
};
|
|
@@ -8,8 +8,13 @@ export type MenuAction = {
|
|
|
8
8
|
*/
|
|
9
9
|
destructive?: boolean;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
11
|
+
* Greys the action out and stops it responding, for one that does not apply right now: "Paste" with nothing copied.
|
|
12
|
+
* Off by default. `FabMenu` ignores it on Android, where Material's FAB menu items have no disabled state.
|
|
13
|
+
*/
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* One of the app's icons, before the label. `Menu`, `ContextMenu` and `FabMenu` show it on both platforms,
|
|
17
|
+
* `ActionSheet` on Android only: an iOS confirmation dialog draws text alone.
|
|
13
18
|
*/
|
|
14
19
|
icon?: IconName;
|
|
15
20
|
/** The action's text. Rows are keyed by it, so keep labels unique within one list. */
|
|
@@ -17,14 +22,37 @@ export type MenuAction = {
|
|
|
17
22
|
/** Called when the action is chosen; the menu closes by itself. */
|
|
18
23
|
onPress: () => void;
|
|
19
24
|
};
|
|
25
|
+
/** What the trigger needs: its text, or an icon and the words a screen reader says for it. */
|
|
26
|
+
type MenuTrigger = {
|
|
27
|
+
label: string;
|
|
28
|
+
} | {
|
|
29
|
+
accessibilityLabel: string;
|
|
30
|
+
icon: IconName;
|
|
31
|
+
};
|
|
20
32
|
/**
|
|
21
|
-
* A
|
|
22
|
-
* A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
33
|
+
* A button that opens a list of actions: a SwiftUI `Menu` on iOS, a Material dropdown anchored to it on Android. The
|
|
34
|
+
* trigger is text, an icon and text, or an icon alone. A native atom: it renders inside `<Screen>` or `<Island>`.
|
|
23
35
|
*/
|
|
24
|
-
export type MenuProps = {
|
|
36
|
+
export type MenuProps = MenuTrigger & {
|
|
37
|
+
/**
|
|
38
|
+
* What VoiceOver and TalkBack say for the trigger: "More options". Required with an `icon` and no `label`, since an
|
|
39
|
+
* icon has no words of its own. With a label, it replaces the label for screen readers.
|
|
40
|
+
*/
|
|
41
|
+
accessibilityLabel?: string;
|
|
25
42
|
/** The menu's actions, top to bottom. */
|
|
26
43
|
actions: MenuAction[];
|
|
27
|
-
/**
|
|
28
|
-
|
|
44
|
+
/**
|
|
45
|
+
* One of the app's icons on the trigger. Before the `label`, in the same colour, when there is one; alone, the
|
|
46
|
+
* trigger is drawn as an `IconButton` (`content.primary`, in a 44pt target on iOS and 48dp on Android) and needs an
|
|
47
|
+
* `accessibilityLabel`.
|
|
48
|
+
*/
|
|
49
|
+
icon?: IconName;
|
|
50
|
+
/**
|
|
51
|
+
* The trigger's text. Android draws it in the accent colour, with a chevron. Required unless there is an `icon`.
|
|
52
|
+
*/
|
|
53
|
+
label?: string;
|
|
54
|
+
/** Test identifier, forwarded to the trigger. Reach an action by its label once the menu is open. */
|
|
55
|
+
testID?: string;
|
|
29
56
|
};
|
|
30
57
|
export type MenuComponent = (props: MenuProps) => ReactNode;
|
|
58
|
+
export {};
|
|
@@ -46,7 +46,7 @@ export type SheetRef = {
|
|
|
46
46
|
* one; native atoms in its content go in an `Island`.
|
|
47
47
|
*/
|
|
48
48
|
export type SheetProps = {
|
|
49
|
-
/** The sheet's colour. `surface.primary` by default
|
|
49
|
+
/** The sheet's colour. `surface.primary` by default; ignored on iOS with `systemBackground`. */
|
|
50
50
|
bg?: ColorToken;
|
|
51
51
|
/** The content. A `<Sheet.Header>` among the direct children becomes the native header instead. */
|
|
52
52
|
children: ReactNode;
|
|
@@ -75,6 +75,12 @@ export type SheetProps = {
|
|
|
75
75
|
* and a header scrolls with the iOS 26 edge effect. Off by default; set it with a scrolling body.
|
|
76
76
|
*/
|
|
77
77
|
scrollable?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* iOS: the system's own sheet background instead of `bg` — untinted Liquid Glass on iOS 26, a material blur before
|
|
80
|
+
* it — for a sheet that floats over content it should let through, like a map. Keep the content free of fills. Android
|
|
81
|
+
* keeps `bg`. Off by default.
|
|
82
|
+
*/
|
|
83
|
+
systemBackground?: boolean;
|
|
78
84
|
/** Test identifier, forwarded to the native sheet. */
|
|
79
85
|
testID?: string;
|
|
80
86
|
/** Presents the sheet while true; going false dismisses it. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hzblj/kara-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "The atoms, molecules, organisms and templates of @hzblj/kara-ui — real SwiftUI and Jetpack Compose views through @expo/ui.",
|
|
5
5
|
"author": "Jan Blazej",
|
|
6
6
|
"license": "MIT",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@hzblj/expo-native-icons": "^0.1.0",
|
|
63
63
|
"@hzblj/expo-rich-haptics": "^0.1.0",
|
|
64
|
-
"@hzblj/kara-native": "^0.
|
|
65
|
-
"@hzblj/kara-sx": "^0.
|
|
66
|
-
"@hzblj/kara-theme": "^0.
|
|
64
|
+
"@hzblj/kara-native": "^0.19.0",
|
|
65
|
+
"@hzblj/kara-sx": "^0.19.0",
|
|
66
|
+
"@hzblj/kara-theme": "^0.19.0",
|
|
67
67
|
"sf-symbols-typescript": "^2.2.0"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import {Badge as ComposeBadge, Text as ComposeText} 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
|
import {useHostGuard} from '../../host-context'
|
|
4
6
|
import type {BadgeComponent, BadgeProps} from './badge.types'
|
|
5
7
|
import {BADGE_TOKENS} from './badge-tokens'
|
|
6
8
|
|
|
7
9
|
/** Material's badge. Its content is a Compose `Text` — `@expo/ui`'s badge takes composable content, not a string. */
|
|
8
|
-
export const Badge: BadgeComponent = ({label, tone = 'neutral'}: BadgeProps) => {
|
|
10
|
+
export const Badge: BadgeComponent = ({label, testID, tone = 'neutral'}: BadgeProps) => {
|
|
9
11
|
useHostGuard('Badge')
|
|
10
12
|
const theme = useAppTheme()
|
|
11
13
|
const tokens = BADGE_TOKENS[tone]
|
|
14
|
+
const modifiers = useMemo(() => (testID ? [testIdModifier(testID)] : undefined), [testID])
|
|
12
15
|
|
|
13
16
|
return (
|
|
14
|
-
<ComposeBadge
|
|
17
|
+
<ComposeBadge
|
|
18
|
+
containerColor={theme.color(tokens.fill)}
|
|
19
|
+
contentColor={theme.color(tokens.content)}
|
|
20
|
+
modifiers={modifiers}
|
|
21
|
+
>
|
|
15
22
|
<ComposeText color={theme.colorString(tokens.content)} style={theme.typography.captionMedium}>
|
|
16
23
|
{label}
|
|
17
24
|
</ComposeText>
|
|
@@ -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={{
|