@studio-piot/native-ui 0.16.0 → 0.17.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/dist/components/Choice/Choice.d.ts +6 -4
- package/dist/components/Choice/Choice.d.ts.map +1 -1
- package/dist/components/Choice/Choice.js +11 -5
- package/dist/components/Choice/Choice.js.map +1 -1
- package/dist/components/Choice/choiceLayout.d.ts +16 -0
- package/dist/components/Choice/choiceLayout.d.ts.map +1 -1
- package/dist/components/Choice/choiceLayout.js +20 -1
- package/dist/components/Choice/choiceLayout.js.map +1 -1
- package/dist/components/Choice/choiceThemeRoles.d.ts +1 -0
- package/dist/components/Choice/choiceThemeRoles.d.ts.map +1 -1
- package/dist/components/Choice/choiceThemeRoles.js +1 -0
- package/dist/components/Choice/choiceThemeRoles.js.map +1 -1
- package/dist/components/Choice/types.d.ts +7 -0
- package/dist/components/Choice/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/components/choice.md +26 -3
- package/package.json +1 -1
- package/src/components/Choice/Choice.tsx +17 -6
- package/src/components/Choice/choiceLayout.test.ts +36 -1
- package/src/components/Choice/choiceLayout.ts +25 -0
- package/src/components/Choice/choiceThemeRoles.test.ts +1 -0
- package/src/components/Choice/choiceThemeRoles.ts +1 -0
- package/src/components/Choice/types.ts +7 -0
- package/src/index.ts +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ChoiceProps } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
* One option in a list
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* One option in a list: an icon, a title and an optional description, in a
|
|
4
|
+
* filled row. Tapping it acts straight away (open the next screen), or,
|
|
5
|
+
* with `selected`, selects it among other choices (e.g. candidate routes,
|
|
6
|
+
* each with a drawing in the icon slot). For a form value, use RadioField
|
|
7
|
+
* or Select.
|
|
6
8
|
*/
|
|
7
|
-
export declare function Choice({ title, description, icon, onPress, disabled, iconSize, radius, backgroundColor: backgroundColorProp, titleColor: titleColorProp, descriptionColor: descriptionColorProp, iconColor: iconColorProp, style, }: ChoiceProps): import("react").JSX.Element;
|
|
9
|
+
export declare function Choice({ title, description, icon, onPress, selected, disabled, iconSize, radius, backgroundColor: backgroundColorProp, titleColor: titleColorProp, descriptionColor: descriptionColorProp, iconColor: iconColorProp, selectedColor: selectedColorProp, style, }: ChoiceProps): import("react").JSX.Element;
|
|
8
10
|
//# sourceMappingURL=Choice.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Choice.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/Choice.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C
|
|
1
|
+
{"version":3,"file":"Choice.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/Choice.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,QAAgB,EAChB,QAA2B,EAC3B,MAAa,EACb,eAAe,EAAE,mBAAmB,EACpC,UAAU,EAAE,cAAc,EAC1B,gBAAgB,EAAE,oBAAoB,EACtC,SAAS,EAAE,aAAa,EACxB,aAAa,EAAE,iBAAiB,EAChC,KAAK,GACN,EAAE,WAAW,+BA+Db"}
|
|
@@ -9,23 +9,29 @@ const useResolvedFieldColor_1 = require("../Field/useResolvedFieldColor");
|
|
|
9
9
|
const choiceLayout_1 = require("./choiceLayout");
|
|
10
10
|
const choiceThemeRoles_1 = require("./choiceThemeRoles");
|
|
11
11
|
/**
|
|
12
|
-
* One option in a list
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* One option in a list: an icon, a title and an optional description, in a
|
|
13
|
+
* filled row. Tapping it acts straight away (open the next screen), or,
|
|
14
|
+
* with `selected`, selects it among other choices (e.g. candidate routes,
|
|
15
|
+
* each with a drawing in the icon slot). For a form value, use RadioField
|
|
16
|
+
* or Select.
|
|
15
17
|
*/
|
|
16
|
-
function Choice({ title, description, icon, onPress, disabled = false, iconSize = choiceLayout_1.CHOICE_ICON_SIZE, radius = "xl", backgroundColor: backgroundColorProp, titleColor: titleColorProp, descriptionColor: descriptionColorProp, iconColor: iconColorProp, style, }) {
|
|
18
|
+
function Choice({ title, description, icon, onPress, selected, disabled = false, iconSize = choiceLayout_1.CHOICE_ICON_SIZE, radius = "xl", backgroundColor: backgroundColorProp, titleColor: titleColorProp, descriptionColor: descriptionColorProp, iconColor: iconColorProp, selectedColor: selectedColorProp, style, }) {
|
|
17
19
|
const backgroundColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(backgroundColorProp, choiceThemeRoles_1.CHOICE_THEME_ROLES.backgroundColor);
|
|
18
20
|
const titleColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(titleColorProp, choiceThemeRoles_1.CHOICE_THEME_ROLES.titleColor);
|
|
19
21
|
const descriptionColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(descriptionColorProp, choiceThemeRoles_1.CHOICE_THEME_ROLES.descriptionColor);
|
|
20
22
|
const iconColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(iconColorProp, choiceThemeRoles_1.CHOICE_THEME_ROLES.iconColor);
|
|
23
|
+
const selectedColor = (0, useResolvedFieldColor_1.useResolvedFieldColor)(selectedColorProp, choiceThemeRoles_1.CHOICE_THEME_ROLES.selectedColor);
|
|
21
24
|
const slot = (0, choiceLayout_1.choiceIconSlot)(iconSize);
|
|
25
|
+
const selection = (0, choiceLayout_1.choiceSelection)(selected);
|
|
22
26
|
return (
|
|
23
27
|
// No explicit label: the row groups its text, so VoiceOver reads the
|
|
24
28
|
// title and description together ("Running, Outdoors, with GPS, button").
|
|
25
|
-
(0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { accessibilityRole:
|
|
29
|
+
(0, jsx_runtime_1.jsxs)(react_native_1.Pressable, { accessibilityRole: selection.role, accessibilityState: { ...selection.state, disabled }, disabled: disabled, onPress: onPress, style: ({ pressed }) => [
|
|
26
30
|
styles.row,
|
|
27
31
|
{
|
|
28
32
|
backgroundColor,
|
|
33
|
+
borderColor: selection.highlighted ? selectedColor : "transparent",
|
|
34
|
+
borderWidth: selection.borderWidth,
|
|
29
35
|
borderRadius: (0, buttonRadius_1.buttonRadiusValue)(radius),
|
|
30
36
|
opacity: (0, buttonAppearance_1.buttonOpacity)({ disabled, loading: false, pressed }),
|
|
31
37
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Choice.js","sourceRoot":"","sources":["../../../src/components/Choice/Choice.tsx"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"Choice.js","sourceRoot":"","sources":["../../../src/components/Choice/Choice.tsx"],"names":[],"mappings":"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"}
|
|
@@ -4,4 +4,20 @@ export declare const CHOICE_ICON_SIZE = 28;
|
|
|
4
4
|
export declare const CHOICE_ICON_SLOT = 32;
|
|
5
5
|
/** The icon column: the steady slot, or the icon itself when it is larger. Pure so it can be unit-tested. */
|
|
6
6
|
export declare function choiceIconSlot(iconSize: number): number;
|
|
7
|
+
/** Border around a selectable choice; drawn transparent when not selected. */
|
|
8
|
+
export declare const CHOICE_SELECTED_BORDER = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Role, state and border for a choice. Without `selected` it is a plain
|
|
11
|
+
* button. With it, a radio option that keeps its border width either way,
|
|
12
|
+
* so rows do not shift when the selection moves. Pure so it can be unit-tested.
|
|
13
|
+
*/
|
|
14
|
+
export declare function choiceSelection(selected: boolean | undefined): {
|
|
15
|
+
role: "button" | "radio";
|
|
16
|
+
state: {
|
|
17
|
+
selected?: boolean;
|
|
18
|
+
checked?: boolean;
|
|
19
|
+
};
|
|
20
|
+
borderWidth: number;
|
|
21
|
+
highlighted: boolean;
|
|
22
|
+
};
|
|
7
23
|
//# sourceMappingURL=choiceLayout.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choiceLayout.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/choiceLayout.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,6GAA6G;AAC7G,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD"}
|
|
1
|
+
{"version":3,"file":"choiceLayout.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/choiceLayout.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,6GAA6G;AAC7G,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG;IAC9D,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC;IACzB,KAAK,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;CACtB,CAUA"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CHOICE_ICON_SLOT = exports.CHOICE_ICON_SIZE = void 0;
|
|
3
|
+
exports.CHOICE_SELECTED_BORDER = exports.CHOICE_ICON_SLOT = exports.CHOICE_ICON_SIZE = void 0;
|
|
4
4
|
exports.choiceIconSlot = choiceIconSlot;
|
|
5
|
+
exports.choiceSelection = choiceSelection;
|
|
5
6
|
/** Default icon box, sized for an SF Symbol next to a two-line label. */
|
|
6
7
|
exports.CHOICE_ICON_SIZE = 28;
|
|
7
8
|
/** Icon column width, so titles line up across a list of choices. */
|
|
@@ -10,4 +11,22 @@ exports.CHOICE_ICON_SLOT = 32;
|
|
|
10
11
|
function choiceIconSlot(iconSize) {
|
|
11
12
|
return Math.max(exports.CHOICE_ICON_SLOT, iconSize);
|
|
12
13
|
}
|
|
14
|
+
/** Border around a selectable choice; drawn transparent when not selected. */
|
|
15
|
+
exports.CHOICE_SELECTED_BORDER = 2;
|
|
16
|
+
/**
|
|
17
|
+
* Role, state and border for a choice. Without `selected` it is a plain
|
|
18
|
+
* button. With it, a radio option that keeps its border width either way,
|
|
19
|
+
* so rows do not shift when the selection moves. Pure so it can be unit-tested.
|
|
20
|
+
*/
|
|
21
|
+
function choiceSelection(selected) {
|
|
22
|
+
if (selected === undefined) {
|
|
23
|
+
return { role: "button", state: {}, borderWidth: 0, highlighted: false };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
role: "radio",
|
|
27
|
+
state: { selected, checked: selected },
|
|
28
|
+
borderWidth: exports.CHOICE_SELECTED_BORDER,
|
|
29
|
+
highlighted: selected,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
13
32
|
//# sourceMappingURL=choiceLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choiceLayout.js","sourceRoot":"","sources":["../../../src/components/Choice/choiceLayout.ts"],"names":[],"mappings":";;;AAMA,wCAEC;
|
|
1
|
+
{"version":3,"file":"choiceLayout.js","sourceRoot":"","sources":["../../../src/components/Choice/choiceLayout.ts"],"names":[],"mappings":";;;AAMA,wCAEC;AAUD,0CAeC;AAjCD,yEAAyE;AAC5D,QAAA,gBAAgB,GAAG,EAAE,CAAC;AACnC,qEAAqE;AACxD,QAAA,gBAAgB,GAAG,EAAE,CAAC;AAEnC,6GAA6G;AAC7G,SAAgB,cAAc,CAAC,QAAgB;IAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,wBAAgB,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,8EAA8E;AACjE,QAAA,sBAAsB,GAAG,CAAC,CAAC;AAExC;;;;GAIG;AACH,SAAgB,eAAe,CAAC,QAA6B;IAM3D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3E,CAAC;IACD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;QACtC,WAAW,EAAE,8BAAsB;QACnC,WAAW,EAAE,QAAQ;KACtB,CAAC;AACJ,CAAC"}
|
|
@@ -3,6 +3,7 @@ export declare const CHOICE_THEME_ROLES: {
|
|
|
3
3
|
readonly titleColor: "choiceTitle";
|
|
4
4
|
readonly descriptionColor: "choiceDescription";
|
|
5
5
|
readonly iconColor: "choiceIcon";
|
|
6
|
+
readonly selectedColor: "choiceSelected";
|
|
6
7
|
};
|
|
7
8
|
export type ChoiceThemeColorProp = keyof typeof CHOICE_THEME_ROLES;
|
|
8
9
|
//# sourceMappingURL=choiceThemeRoles.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choiceThemeRoles.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/choiceThemeRoles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"choiceThemeRoles.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/choiceThemeRoles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB;;;;;;CAMrB,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,MAAM,OAAO,kBAAkB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"choiceThemeRoles.js","sourceRoot":"","sources":["../../../src/components/Choice/choiceThemeRoles.ts"],"names":[],"mappings":";;;AAAa,QAAA,kBAAkB,GAAG;IAChC,eAAe,EAAE,kBAAkB;IACnC,UAAU,EAAE,aAAa;IACzB,gBAAgB,EAAE,mBAAmB;IACrC,SAAS,EAAE,YAAY;
|
|
1
|
+
{"version":3,"file":"choiceThemeRoles.js","sourceRoot":"","sources":["../../../src/components/Choice/choiceThemeRoles.ts"],"names":[],"mappings":";;;AAAa,QAAA,kBAAkB,GAAG;IAChC,eAAe,EAAE,kBAAkB;IACnC,UAAU,EAAE,aAAa;IACzB,gBAAgB,EAAE,mBAAmB;IACrC,SAAS,EAAE,YAAY;IACvB,aAAa,EAAE,gBAAgB;CACvB,CAAC"}
|
|
@@ -8,6 +8,11 @@ export type ChoiceProps = {
|
|
|
8
8
|
/** `({ color, size }) => ReactNode`, e.g. an expo-symbols `SymbolView`. */
|
|
9
9
|
icon?: ButtonIcon;
|
|
10
10
|
onPress: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* Makes the choice a selectable option: a highlight border when true,
|
|
13
|
+
* read as a selected radio. Leave it out for a choice that just acts.
|
|
14
|
+
*/
|
|
15
|
+
selected?: boolean;
|
|
11
16
|
disabled?: boolean;
|
|
12
17
|
/** Icon box. Default 28. */
|
|
13
18
|
iconSize?: number;
|
|
@@ -21,6 +26,8 @@ export type ChoiceProps = {
|
|
|
21
26
|
descriptionColor?: string;
|
|
22
27
|
/** Defaults to theme role `choiceIcon`. */
|
|
23
28
|
iconColor?: string;
|
|
29
|
+
/** Border of a selected choice. Defaults to theme role `choiceSelected`. */
|
|
30
|
+
selectedColor?: string;
|
|
24
31
|
style?: StyleProp<ViewStyle>;
|
|
25
32
|
};
|
|
26
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/Choice/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const version = "0.
|
|
1
|
+
export declare const version = "0.17.0";
|
|
2
2
|
export * from "./theme";
|
|
3
3
|
export { SHEET_TOP_RADIUS, screenPaddingHorizontal } from "./layout";
|
|
4
4
|
export { Button, BUTTON_ICON_SIZE, BUTTON_THEME_ROLES, buttonRadiusValue, type ButtonIcon, type ButtonIconPosition, type ButtonProps, type ButtonRadius, type ButtonThemeColorProp, type ButtonVariant, } from "./components/Button";
|
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.TAB_ACCESSORY_SCREEN_INSET = exports.TabAccessoryButton = exports.HeaderWash = exports.supportsLiquidGlass = exports.HEADER_BUTTON_THEME_ROLES = exports.HeaderIcon = exports.HeaderGroup = exports.HeaderCapsule = exports.SLIDER_THEME_ROLES = exports.Slider = exports.SEGMENTED_THEME_ROLES = exports.SegmentedControl = exports.resolveSelectDetentIndex = exports.detentsToSnapPoints = exports.DEFAULT_SELECT_INITIAL_DETENT = exports.DEFAULT_SELECT_DETENTS = exports.SELECT_THEME_ROLES = exports.SelectCloseButton = exports.Select = exports.RADIO_THEME_ROLES = exports.RadioField = exports.useErrorBannerField = exports.useErrorBannerDismiss = exports.useErrorBannerContext = exports.useErrorBanner = exports.ERROR_BANNER_THEME_ROLES = exports.ErrorBannerProvider = exports.ErrorBannerField = exports.ErrorBanner = exports.formSheetSnapPoints = exports.FORM_SHEET_THEME_ROLES = exports.FormSheet = exports.FORM_THEME_ROLES = exports.FormCloseButton = exports.Form = exports.FIELD_THEME_ROLES = exports.FieldTextarea = exports.FieldText = exports.filterLinkBlockGroups = exports.LinkBlocks = exports.CHOICE_THEME_ROLES = exports.CHOICE_ICON_SIZE = exports.Choice = exports.buttonRadiusValue = exports.BUTTON_THEME_ROLES = exports.BUTTON_ICON_SIZE = exports.Button = exports.screenPaddingHorizontal = exports.SHEET_TOP_RADIUS = exports.version = void 0;
|
|
40
40
|
exports.components = exports.PAGE_THEME_ROLES = exports.PageBleed = exports.Page = exports.tabAccessoryScreenInset = exports.TAB_ACCESSORY_THEME_ROLES = void 0;
|
|
41
|
-
exports.version = "0.
|
|
41
|
+
exports.version = "0.17.0";
|
|
42
42
|
__exportStar(require("./theme"), exports);
|
|
43
43
|
var layout_1 = require("./layout");
|
|
44
44
|
Object.defineProperty(exports, "SHEET_TOP_RADIUS", { enumerable: true, get: function () { return layout_1.SHEET_TOP_RADIUS; } });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
One option in a list of next steps: an icon, a title and an optional description, in a filled row. Tapping it acts straight away, usually to open the next screen, as in "What are you doing? Running / HIIT".
|
|
4
4
|
|
|
5
|
-
Use `Choice` for a short list of destinations. To pick a value that stays on the screen, use [RadioField](./radio-field.md), [SegmentedControl](./segmented-control.md) or [Select](./select.md). For a settings list of links, use [LinkBlocks](./link-blocks.md).
|
|
5
|
+
Use `Choice` for a short list of destinations, or, with `selected`, for picking one of a few visual options such as candidate routes. To pick a value that stays on the screen, use [RadioField](./radio-field.md), [SegmentedControl](./segmented-control.md) or [Select](./select.md). For a settings list of links, use [LinkBlocks](./link-blocks.md).
|
|
6
6
|
|
|
7
7
|
## Basic usage
|
|
8
8
|
|
|
@@ -42,9 +42,29 @@ export default function NewActivity() {
|
|
|
42
42
|
- **Title only.** `description` is optional.
|
|
43
43
|
- **Spacing.** Put choices in a stack with a 16pt gap. The row has no margin of its own.
|
|
44
44
|
|
|
45
|
+
## Selectable choices
|
|
46
|
+
|
|
47
|
+
Pass `selected` to make the choices a set where one is picked, for example candidate routes with a drawing of each in the icon slot. The selected row gets a highlight border (`choiceSelected`); the others keep a transparent border of the same width, so nothing shifts when the selection moves. Tapping still calls `onPress`, where you update the selection.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
{routes.map((route) => (
|
|
51
|
+
<Choice
|
|
52
|
+
key={route.id}
|
|
53
|
+
title={route.name}
|
|
54
|
+
description={`${route.km} km`}
|
|
55
|
+
icon={({ color, size }) => <RouteThumbnail route={route} color={color} size={size} />}
|
|
56
|
+
iconSize={48}
|
|
57
|
+
selected={route.id === selectedId}
|
|
58
|
+
onPress={() => setSelectedId(route.id)}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Pair it with one action that uses the selection, e.g. **Use this route**. Leave `selected` out when tapping should just act.
|
|
64
|
+
|
|
45
65
|
## Accessibility
|
|
46
66
|
|
|
47
|
-
The row is one button. VoiceOver reads its text together, for example "Running, Outdoors, with GPS, button". The icon is decorative and hidden from screen readers. `disabled` sets the disabled state, fades the row to 40% and blocks presses.
|
|
67
|
+
The row is one button. VoiceOver reads its text together, for example "Running, Outdoors, with GPS, button". With `selected`, it is a radio option instead, read as selected or not. The icon is decorative and hidden from screen readers. `disabled` sets the disabled state, fades the row to 40% and blocks presses.
|
|
48
68
|
|
|
49
69
|
## Theme roles
|
|
50
70
|
|
|
@@ -56,6 +76,7 @@ Add these keys to both the light and dark maps.
|
|
|
56
76
|
| Title | `choiceTitle` |
|
|
57
77
|
| Description | `choiceDescription` |
|
|
58
78
|
| Icon | `choiceIcon` |
|
|
79
|
+
| Selected border (with `selected`) | `choiceSelected` |
|
|
59
80
|
|
|
60
81
|
## Props
|
|
61
82
|
|
|
@@ -63,7 +84,8 @@ Add these keys to both the light and dark maps.
|
|
|
63
84
|
|---|---|---|---|
|
|
64
85
|
| `title` | `string` | — | Required |
|
|
65
86
|
| `onPress` | `() => void` | — | Required |
|
|
66
|
-
| `description` | `string` | — | Second line under the title |
|
|
87
|
+
| `description` | `string` | — | Second line under the title; may wrap to more lines |
|
|
88
|
+
| `selected` | `boolean` | — | Makes it a selectable option: highlight border when true, radio semantics |
|
|
67
89
|
| `icon` | `ButtonIcon` | — | `({ color, size }) => ReactNode` |
|
|
68
90
|
| `iconSize` | `number` | `28` | Icon box |
|
|
69
91
|
| `disabled` | `boolean` | `false` | 40% opacity; not pressable |
|
|
@@ -72,6 +94,7 @@ Add these keys to both the light and dark maps.
|
|
|
72
94
|
| `titleColor` | `string` | theme `choiceTitle` | Optional override |
|
|
73
95
|
| `descriptionColor` | `string` | theme `choiceDescription` | Optional override |
|
|
74
96
|
| `iconColor` | `string` | theme `choiceIcon` | Optional override |
|
|
97
|
+
| `selectedColor` | `string` | theme `choiceSelected` | Optional override |
|
|
75
98
|
| `style` | view style | — | Merged after the row |
|
|
76
99
|
|
|
77
100
|
Layout: 72pt minimum height, 16pt horizontal and 14pt vertical padding, 12pt between the icon and the text. The title is 17pt semibold and the description 13pt, the same as the rest of the package.
|
package/package.json
CHANGED
|
@@ -2,20 +2,23 @@ import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
|
2
2
|
import { buttonOpacity } from "../Button/buttonAppearance";
|
|
3
3
|
import { buttonRadiusValue } from "../Button/buttonRadius";
|
|
4
4
|
import { useResolvedFieldColor } from "../Field/useResolvedFieldColor";
|
|
5
|
-
import { CHOICE_ICON_SIZE, choiceIconSlot } from "./choiceLayout";
|
|
5
|
+
import { CHOICE_ICON_SIZE, choiceIconSlot, choiceSelection } from "./choiceLayout";
|
|
6
6
|
import { CHOICE_THEME_ROLES } from "./choiceThemeRoles";
|
|
7
7
|
import type { ChoiceProps } from "./types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* One option in a list
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* One option in a list: an icon, a title and an optional description, in a
|
|
11
|
+
* filled row. Tapping it acts straight away (open the next screen), or,
|
|
12
|
+
* with `selected`, selects it among other choices (e.g. candidate routes,
|
|
13
|
+
* each with a drawing in the icon slot). For a form value, use RadioField
|
|
14
|
+
* or Select.
|
|
13
15
|
*/
|
|
14
16
|
export function Choice({
|
|
15
17
|
title,
|
|
16
18
|
description,
|
|
17
19
|
icon,
|
|
18
20
|
onPress,
|
|
21
|
+
selected,
|
|
19
22
|
disabled = false,
|
|
20
23
|
iconSize = CHOICE_ICON_SIZE,
|
|
21
24
|
radius = "xl",
|
|
@@ -23,6 +26,7 @@ export function Choice({
|
|
|
23
26
|
titleColor: titleColorProp,
|
|
24
27
|
descriptionColor: descriptionColorProp,
|
|
25
28
|
iconColor: iconColorProp,
|
|
29
|
+
selectedColor: selectedColorProp,
|
|
26
30
|
style,
|
|
27
31
|
}: ChoiceProps) {
|
|
28
32
|
const backgroundColor = useResolvedFieldColor(
|
|
@@ -41,20 +45,27 @@ export function Choice({
|
|
|
41
45
|
iconColorProp,
|
|
42
46
|
CHOICE_THEME_ROLES.iconColor,
|
|
43
47
|
);
|
|
48
|
+
const selectedColor = useResolvedFieldColor(
|
|
49
|
+
selectedColorProp,
|
|
50
|
+
CHOICE_THEME_ROLES.selectedColor,
|
|
51
|
+
);
|
|
44
52
|
const slot = choiceIconSlot(iconSize);
|
|
53
|
+
const selection = choiceSelection(selected);
|
|
45
54
|
|
|
46
55
|
return (
|
|
47
56
|
// No explicit label: the row groups its text, so VoiceOver reads the
|
|
48
57
|
// title and description together ("Running, Outdoors, with GPS, button").
|
|
49
58
|
<Pressable
|
|
50
|
-
accessibilityRole=
|
|
51
|
-
accessibilityState={{ disabled }}
|
|
59
|
+
accessibilityRole={selection.role}
|
|
60
|
+
accessibilityState={{ ...selection.state, disabled }}
|
|
52
61
|
disabled={disabled}
|
|
53
62
|
onPress={onPress}
|
|
54
63
|
style={({ pressed }) => [
|
|
55
64
|
styles.row,
|
|
56
65
|
{
|
|
57
66
|
backgroundColor,
|
|
67
|
+
borderColor: selection.highlighted ? selectedColor : "transparent",
|
|
68
|
+
borderWidth: selection.borderWidth,
|
|
58
69
|
borderRadius: buttonRadiusValue(radius),
|
|
59
70
|
opacity: buttonOpacity({ disabled, loading: false, pressed }),
|
|
60
71
|
},
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CHOICE_ICON_SIZE,
|
|
5
|
+
CHOICE_ICON_SLOT,
|
|
6
|
+
CHOICE_SELECTED_BORDER,
|
|
7
|
+
choiceIconSlot,
|
|
8
|
+
choiceSelection,
|
|
9
|
+
} from "./choiceLayout.ts";
|
|
4
10
|
|
|
5
11
|
describe("choiceIconSlot", () => {
|
|
6
12
|
it("keeps a steady slot so titles line up across rows", () => {
|
|
@@ -12,3 +18,32 @@ describe("choiceIconSlot", () => {
|
|
|
12
18
|
assert.equal(choiceIconSlot(40), 40);
|
|
13
19
|
});
|
|
14
20
|
});
|
|
21
|
+
|
|
22
|
+
describe("choiceSelection", () => {
|
|
23
|
+
it("is a plain button with no border when selection is not used", () => {
|
|
24
|
+
assert.deepEqual(choiceSelection(undefined), {
|
|
25
|
+
role: "button",
|
|
26
|
+
state: {},
|
|
27
|
+
borderWidth: 0,
|
|
28
|
+
highlighted: false,
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("is a selected radio with the highlight border", () => {
|
|
33
|
+
assert.deepEqual(choiceSelection(true), {
|
|
34
|
+
role: "radio",
|
|
35
|
+
state: { selected: true, checked: true },
|
|
36
|
+
borderWidth: CHOICE_SELECTED_BORDER,
|
|
37
|
+
highlighted: true,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps the border width when not selected, so rows do not jump", () => {
|
|
42
|
+
assert.deepEqual(choiceSelection(false), {
|
|
43
|
+
role: "radio",
|
|
44
|
+
state: { selected: false, checked: false },
|
|
45
|
+
borderWidth: CHOICE_SELECTED_BORDER,
|
|
46
|
+
highlighted: false,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -7,3 +7,28 @@ export const CHOICE_ICON_SLOT = 32;
|
|
|
7
7
|
export function choiceIconSlot(iconSize: number): number {
|
|
8
8
|
return Math.max(CHOICE_ICON_SLOT, iconSize);
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/** Border around a selectable choice; drawn transparent when not selected. */
|
|
12
|
+
export const CHOICE_SELECTED_BORDER = 2;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Role, state and border for a choice. Without `selected` it is a plain
|
|
16
|
+
* button. With it, a radio option that keeps its border width either way,
|
|
17
|
+
* so rows do not shift when the selection moves. Pure so it can be unit-tested.
|
|
18
|
+
*/
|
|
19
|
+
export function choiceSelection(selected: boolean | undefined): {
|
|
20
|
+
role: "button" | "radio";
|
|
21
|
+
state: { selected?: boolean; checked?: boolean };
|
|
22
|
+
borderWidth: number;
|
|
23
|
+
highlighted: boolean;
|
|
24
|
+
} {
|
|
25
|
+
if (selected === undefined) {
|
|
26
|
+
return { role: "button", state: {}, borderWidth: 0, highlighted: false };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
role: "radio",
|
|
30
|
+
state: { selected, checked: selected },
|
|
31
|
+
borderWidth: CHOICE_SELECTED_BORDER,
|
|
32
|
+
highlighted: selected,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -9,6 +9,11 @@ export type ChoiceProps = {
|
|
|
9
9
|
/** `({ color, size }) => ReactNode`, e.g. an expo-symbols `SymbolView`. */
|
|
10
10
|
icon?: ButtonIcon;
|
|
11
11
|
onPress: () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Makes the choice a selectable option: a highlight border when true,
|
|
14
|
+
* read as a selected radio. Leave it out for a choice that just acts.
|
|
15
|
+
*/
|
|
16
|
+
selected?: boolean;
|
|
12
17
|
disabled?: boolean;
|
|
13
18
|
/** Icon box. Default 28. */
|
|
14
19
|
iconSize?: number;
|
|
@@ -22,5 +27,7 @@ export type ChoiceProps = {
|
|
|
22
27
|
descriptionColor?: string;
|
|
23
28
|
/** Defaults to theme role `choiceIcon`. */
|
|
24
29
|
iconColor?: string;
|
|
30
|
+
/** Border of a selected choice. Defaults to theme role `choiceSelected`. */
|
|
31
|
+
selectedColor?: string;
|
|
25
32
|
style?: StyleProp<ViewStyle>;
|
|
26
33
|
};
|
package/src/index.ts
CHANGED