@factorialco/f0-react-native 0.34.0 → 0.35.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/README.md +7 -5
- package/lib/module/components/Button/index.js +1 -1
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/F0Button/F0Button.js +2 -0
- package/lib/module/components/F0Button/F0Button.js.map +1 -0
- package/lib/module/components/F0Button/F0Button.md +163 -0
- package/lib/module/components/F0Button/F0Button.styles.js +2 -0
- package/lib/module/components/F0Button/F0Button.styles.js.map +1 -0
- package/lib/module/components/F0Button/F0Button.types.js +2 -0
- package/lib/module/components/F0Button/F0Button.types.js.map +1 -0
- package/lib/module/components/F0Button/index.js +2 -0
- package/lib/module/components/F0Button/index.js.map +1 -0
- package/lib/module/components/Icon/index.js.map +1 -1
- package/lib/module/components/exports.js +1 -1
- package/lib/module/components/exports.js.map +1 -1
- package/lib/typescript/components/Button/index.d.ts +18 -0
- package/lib/typescript/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/components/F0Button/F0Button.d.ts +6 -0
- package/lib/typescript/components/F0Button/F0Button.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/F0Button.styles.d.ts +161 -0
- package/lib/typescript/components/F0Button/F0Button.styles.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/F0Button.types.d.ts +47 -0
- package/lib/typescript/components/F0Button/F0Button.types.d.ts.map +1 -0
- package/lib/typescript/components/F0Button/index.d.ts +4 -0
- package/lib/typescript/components/F0Button/index.d.ts.map +1 -0
- package/lib/typescript/components/Icon/index.d.ts +5 -0
- package/lib/typescript/components/Icon/index.d.ts.map +1 -1
- package/lib/typescript/components/exports.d.ts +1 -0
- package/lib/typescript/components/exports.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/__snapshots__/index.spec.tsx.snap +11 -11
- package/src/components/Button/index.tsx +22 -2
- package/src/components/F0Button/F0Button.md +163 -0
- package/src/components/F0Button/F0Button.styles.ts +141 -0
- package/src/components/F0Button/F0Button.tsx +228 -0
- package/src/components/F0Button/F0Button.types.ts +82 -0
- package/src/components/F0Button/__tests__/F0Button.spec.tsx +285 -0
- package/src/components/F0Button/__tests__/__snapshots__/F0Button.spec.tsx.snap +966 -0
- package/src/components/F0Button/index.ts +7 -0
- package/src/components/Icon/index.tsx +5 -0
- package/src/components/exports.ts +1 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { IconType } from "../primitives/F0Icon";
|
|
2
|
+
import type { PressableFeedbackProps, PressableFeedbackVariant } from "../primitives/PressableFeedback";
|
|
3
|
+
/**
|
|
4
|
+
* Button variant types
|
|
5
|
+
*/
|
|
6
|
+
export declare const BUTTON_VARIANTS: readonly ["default", "outline", "critical", "neutral", "ghost", "promote"];
|
|
7
|
+
export type ButtonVariant = (typeof BUTTON_VARIANTS)[number];
|
|
8
|
+
export type F0ButtonVariant = ButtonVariant;
|
|
9
|
+
/**
|
|
10
|
+
* Button size types
|
|
11
|
+
*/
|
|
12
|
+
export declare const BUTTON_SIZES: readonly ["sm", "md", "lg"];
|
|
13
|
+
export type ButtonSize = (typeof BUTTON_SIZES)[number];
|
|
14
|
+
export type F0ButtonSize = ButtonSize;
|
|
15
|
+
/**
|
|
16
|
+
* Props that are controlled by F0Button and must not be passed through.
|
|
17
|
+
* This preserves F0Button press-state behavior and accessibility contract.
|
|
18
|
+
*/
|
|
19
|
+
declare const F0_BUTTON_CONTROLLED_PASSTHROUGH_PROPS: readonly ["onPressIn", "onPressOut", "accessibilityLabel", "accessibilityRole", "accessibilityState"];
|
|
20
|
+
export declare const F0_BUTTON_BANNED_PROPS: readonly ["style", "className"];
|
|
21
|
+
export declare const F0_BUTTON_BLOCKED_FORWARD_PROPS: readonly ["onPressIn", "onPressOut", "accessibilityLabel", "accessibilityRole", "accessibilityState", "style", "className"];
|
|
22
|
+
interface F0ButtonPropsInternal extends Omit<PressableFeedbackProps, "children" | "variant" | "disabled" | (typeof F0_BUTTON_CONTROLLED_PASSTHROUGH_PROPS)[number]> {
|
|
23
|
+
label: string;
|
|
24
|
+
onPress?: () => void | Promise<unknown>;
|
|
25
|
+
variant?: ButtonVariant;
|
|
26
|
+
size?: ButtonSize;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
loading?: boolean;
|
|
29
|
+
icon?: IconType;
|
|
30
|
+
emoji?: string;
|
|
31
|
+
hideLabel?: boolean;
|
|
32
|
+
round?: boolean;
|
|
33
|
+
showBadge?: boolean;
|
|
34
|
+
fullWidth?: boolean;
|
|
35
|
+
accessibilityHint?: string;
|
|
36
|
+
testID?: string;
|
|
37
|
+
feedback?: PressableFeedbackVariant;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Public props for the F0Button component.
|
|
41
|
+
*
|
|
42
|
+
* Note: `className` and `style` props are NOT available.
|
|
43
|
+
* Use semantic props (variant, size, etc.) for styling.
|
|
44
|
+
*/
|
|
45
|
+
export type F0ButtonProps = Omit<F0ButtonPropsInternal, (typeof F0_BUTTON_BANNED_PROPS)[number]>;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=F0Button.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"F0Button.types.d.ts","sourceRoot":"","sources":["../../../../src/components/F0Button/F0Button.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EACV,sBAAsB,EACtB,wBAAwB,EACzB,MAAM,iCAAiC,CAAA;AAExC;;GAEG;AACH,eAAO,MAAM,eAAe,4EAOlB,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAC5D,MAAM,MAAM,eAAe,GAAG,aAAa,CAAA;AAE3C;;GAEG;AACH,eAAO,MAAM,YAAY,6BAA8B,CAAA;AAEvD,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AACtD,MAAM,MAAM,YAAY,GAAG,UAAU,CAAA;AAErC;;;GAGG;AACH,QAAA,MAAM,sCAAsC,uGAMlC,CAAA;AAEV,eAAO,MAAM,sBAAsB,iCAAkC,CAAA;AAErE,eAAO,MAAM,+BAA+B,6HAGlC,CAAA;AAEV,UAAU,qBAAsB,SAAQ,IAAI,CAC1C,sBAAsB,EACpB,UAAU,GACV,SAAS,GACT,UAAU,GACV,CAAC,OAAO,sCAAsC,CAAC,CAAC,MAAM,CAAC,CAC1D;IACC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvC,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,wBAAwB,CAAA;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,qBAAqB,EACrB,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CACxC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/F0Button/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAChE,YAAY,EACV,aAAa,EACb,eAAe,EACf,YAAY,GACb,MAAM,kBAAkB,CAAA"}
|
|
@@ -27,6 +27,11 @@ declare const iconVariants: import("tailwind-variants").TVReturnType<{
|
|
|
27
27
|
xs: string;
|
|
28
28
|
};
|
|
29
29
|
}, undefined, "shrink-0", unknown, unknown, undefined>>;
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `F0IconProps` from `../primitives/F0Icon` instead.
|
|
32
|
+
* Migration: Replace `IconProps` with `F0IconProps`.
|
|
33
|
+
* `F0Icon` supports `icon`, `size`, `testID`, and `className`, and adds semantic `color` variants.
|
|
34
|
+
*/
|
|
30
35
|
export interface IconProps extends SvgProps, VariantProps<typeof iconVariants> {
|
|
31
36
|
icon: IconType;
|
|
32
37
|
testID?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/Icon/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGzD,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAEtE,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;uDAchB,CAAA;AAEF,MAAM,WAAW,SAAU,SAAQ,QAAQ,EAAE,YAAY,CAAC,OAAO,YAAY,CAAC;IAC5E,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,uEAiBf,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/Icon/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAA;AACzC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAM,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGzD,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAEtE,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;uDAchB,CAAA;AAEF;;;;GAIG;AACH,MAAM,WAAW,SAAU,SAAQ,QAAQ,EAAE,YAAY,CAAC,OAAO,YAAY,CAAC;IAC5E,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,IAAI,uEAiBf,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../../src/components/exports.ts"],"names":[],"mappings":"AACA,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAC7C,cAAc,yBAAyB,CAAA;AACvC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,kCAAkC,CAAA;AAChD,cAAc,uCAAuC,CAAA;AAGrD,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gCAAgC,CAAA"}
|
|
1
|
+
{"version":3,"file":"exports.d.ts","sourceRoot":"","sources":["../../../src/components/exports.ts"],"names":[],"mappings":"AACA,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,WAAW,CAAA;AACzB,cAAc,oBAAoB,CAAA;AAClC,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAC7C,cAAc,yBAAyB,CAAA;AACvC,cAAc,WAAW,CAAA;AACzB,cAAc,aAAa,CAAA;AAC3B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,+BAA+B,CAAA;AAC7C,cAAc,kCAAkC,CAAA;AAChD,cAAc,uCAAuC,CAAA;AAGrD,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,gCAAgC,CAAA"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`Button Snapshot - critical variant 1`] = `
|
|
4
4
|
<View
|
|
5
|
-
className="flex
|
|
5
|
+
className="flex items-start"
|
|
6
6
|
>
|
|
7
7
|
<View
|
|
8
8
|
accessibilityLabel="Test Button"
|
|
@@ -49,7 +49,7 @@ exports[`Button Snapshot - critical variant 1`] = `
|
|
|
49
49
|
|
|
50
50
|
exports[`Button Snapshot - default button 1`] = `
|
|
51
51
|
<View
|
|
52
|
-
className="flex
|
|
52
|
+
className="flex items-start"
|
|
53
53
|
>
|
|
54
54
|
<View
|
|
55
55
|
accessibilityLabel="Test Button"
|
|
@@ -96,7 +96,7 @@ exports[`Button Snapshot - default button 1`] = `
|
|
|
96
96
|
|
|
97
97
|
exports[`Button Snapshot - different sizes 1`] = `
|
|
98
98
|
<View
|
|
99
|
-
className="flex
|
|
99
|
+
className="flex items-start"
|
|
100
100
|
>
|
|
101
101
|
<View
|
|
102
102
|
accessibilityLabel="Test Button"
|
|
@@ -143,7 +143,7 @@ exports[`Button Snapshot - different sizes 1`] = `
|
|
|
143
143
|
|
|
144
144
|
exports[`Button Snapshot - different sizes 2`] = `
|
|
145
145
|
<View
|
|
146
|
-
className="flex
|
|
146
|
+
className="flex items-start"
|
|
147
147
|
>
|
|
148
148
|
<View
|
|
149
149
|
accessibilityLabel="Test Button"
|
|
@@ -190,7 +190,7 @@ exports[`Button Snapshot - different sizes 2`] = `
|
|
|
190
190
|
|
|
191
191
|
exports[`Button Snapshot - different sizes 3`] = `
|
|
192
192
|
<View
|
|
193
|
-
className="flex
|
|
193
|
+
className="flex items-start"
|
|
194
194
|
>
|
|
195
195
|
<View
|
|
196
196
|
accessibilityLabel="Test Button"
|
|
@@ -237,7 +237,7 @@ exports[`Button Snapshot - different sizes 3`] = `
|
|
|
237
237
|
|
|
238
238
|
exports[`Button Snapshot - disabled state 1`] = `
|
|
239
239
|
<View
|
|
240
|
-
className="flex
|
|
240
|
+
className="flex items-start"
|
|
241
241
|
>
|
|
242
242
|
<View
|
|
243
243
|
accessibilityLabel="Test Button, disabled"
|
|
@@ -284,7 +284,7 @@ exports[`Button Snapshot - disabled state 1`] = `
|
|
|
284
284
|
|
|
285
285
|
exports[`Button Snapshot - loading state 1`] = `
|
|
286
286
|
<View
|
|
287
|
-
className="flex
|
|
287
|
+
className="flex items-start"
|
|
288
288
|
>
|
|
289
289
|
<View
|
|
290
290
|
accessibilityLabel="Test Button, disabled, loading"
|
|
@@ -331,7 +331,7 @@ exports[`Button Snapshot - loading state 1`] = `
|
|
|
331
331
|
|
|
332
332
|
exports[`Button Snapshot - outline variant 1`] = `
|
|
333
333
|
<View
|
|
334
|
-
className="flex
|
|
334
|
+
className="flex items-start"
|
|
335
335
|
>
|
|
336
336
|
<View
|
|
337
337
|
accessibilityLabel="Test Button"
|
|
@@ -378,7 +378,7 @@ exports[`Button Snapshot - outline variant 1`] = `
|
|
|
378
378
|
|
|
379
379
|
exports[`Button Snapshot - round button with hidden label 1`] = `
|
|
380
380
|
<View
|
|
381
|
-
className="flex
|
|
381
|
+
className="flex items-start"
|
|
382
382
|
>
|
|
383
383
|
<View
|
|
384
384
|
accessibilityLabel="Test Button"
|
|
@@ -419,7 +419,7 @@ exports[`Button Snapshot - round button with hidden label 1`] = `
|
|
|
419
419
|
|
|
420
420
|
exports[`Button Snapshot - with emoji 1`] = `
|
|
421
421
|
<View
|
|
422
|
-
className="flex
|
|
422
|
+
className="flex items-start"
|
|
423
423
|
>
|
|
424
424
|
<View
|
|
425
425
|
accessibilityLabel="Test Button"
|
|
@@ -471,7 +471,7 @@ exports[`Button Snapshot - with emoji 1`] = `
|
|
|
471
471
|
|
|
472
472
|
exports[`Button Snapshot - with icon 1`] = `
|
|
473
473
|
<View
|
|
474
|
-
className="flex
|
|
474
|
+
className="flex items-start"
|
|
475
475
|
>
|
|
476
476
|
<View
|
|
477
477
|
accessibilityLabel="Test Button"
|
|
@@ -3,8 +3,11 @@ import { Pressable, Text, View } from "react-native"
|
|
|
3
3
|
import { tv, type VariantProps } from "tailwind-variants"
|
|
4
4
|
|
|
5
5
|
import { cn } from "../../lib/utils"
|
|
6
|
-
import { F0Icon, type
|
|
6
|
+
import { F0Icon, type IconColor, type IconType } from "../primitives/F0Icon"
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Use `F0Button` from `../F0Button` instead.
|
|
10
|
+
*/
|
|
8
11
|
export const variants = [
|
|
9
12
|
"default",
|
|
10
13
|
"outline",
|
|
@@ -13,9 +16,20 @@ export const variants = [
|
|
|
13
16
|
"ghost",
|
|
14
17
|
"promote",
|
|
15
18
|
] as const
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Use `F0ButtonVariant` from `../F0Button` instead.
|
|
22
|
+
*/
|
|
16
23
|
export type ButtonVariant = (typeof variants)[number]
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated Use `BUTTON_SIZES` from `../F0Button` instead.
|
|
27
|
+
*/
|
|
18
28
|
export const sizes = ["sm", "md", "lg"] as const
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @deprecated Use `F0ButtonSize` from `../F0Button` instead.
|
|
32
|
+
*/
|
|
19
33
|
export type ButtonSize = (typeof sizes)[number]
|
|
20
34
|
|
|
21
35
|
const buttonVariants = tv({
|
|
@@ -115,6 +129,9 @@ const getTextColorClass = (variant: ButtonVariant, isPressed: boolean) => {
|
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
|
|
132
|
+
/**
|
|
133
|
+
* @deprecated Use `F0ButtonProps` from `../F0Button` instead.
|
|
134
|
+
*/
|
|
118
135
|
export interface ButtonProps extends VariantProps<typeof buttonVariants> {
|
|
119
136
|
label: string
|
|
120
137
|
onPress?: () => void | Promise<unknown>
|
|
@@ -129,6 +146,9 @@ export interface ButtonProps extends VariantProps<typeof buttonVariants> {
|
|
|
129
146
|
fullWidth?: boolean
|
|
130
147
|
}
|
|
131
148
|
|
|
149
|
+
/**
|
|
150
|
+
* @deprecated Use `F0Button` from `../F0Button` instead.
|
|
151
|
+
*/
|
|
132
152
|
export const Button = forwardRef<View, ButtonProps>(function Button(
|
|
133
153
|
{
|
|
134
154
|
label,
|
|
@@ -171,7 +191,7 @@ export const Button = forwardRef<View, ButtonProps>(function Button(
|
|
|
171
191
|
const shouldShowPressed = isPressed && !isDisabled
|
|
172
192
|
|
|
173
193
|
return (
|
|
174
|
-
<View className={`flex ${fullWidth ? "flex-1" : "
|
|
194
|
+
<View className={`flex ${fullWidth ? "flex-1" : "items-start"}`}>
|
|
175
195
|
<Pressable
|
|
176
196
|
ref={ref}
|
|
177
197
|
disabled={isDisabled}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# F0Button
|
|
2
|
+
|
|
3
|
+
Primary interactive button component for the F0 Design System.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
F0Button is the semantic button API for React Native in F0. It supports:
|
|
8
|
+
|
|
9
|
+
- Visual variants (`default`, `outline`, `critical`, `neutral`, `ghost`, `promote`)
|
|
10
|
+
- Size variants (`sm`, `md`, `lg`)
|
|
11
|
+
- Optional icon / emoji content
|
|
12
|
+
- Icon-only round mode
|
|
13
|
+
- Async press handlers with automatic loading-state handling
|
|
14
|
+
- Visible loading indicator when busy
|
|
15
|
+
- Press feedback control through `PressableFeedback`
|
|
16
|
+
|
|
17
|
+
## Architecture
|
|
18
|
+
|
|
19
|
+
- **Pattern:** Props API (Atomic) - element order is fixed: icon -> emoji -> label
|
|
20
|
+
- **Press feedback:** Uses `PressableFeedback` component for smooth animations
|
|
21
|
+
- **Press state:** Tracks pressed state with `useState` for color changes by variant
|
|
22
|
+
- **Loading state:** Supports auto-loading when `onPress` returns a Promise
|
|
23
|
+
- **Styling:** `className` and `style` are blocked in the public API and filtered at runtime
|
|
24
|
+
- **Location:** `src/components/F0Button/`
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
<!-- prettier-ignore -->
|
|
29
|
+
```tsx
|
|
30
|
+
import { F0Button } from "@factorialco/f0-react-native"
|
|
31
|
+
import { Archive } from "@factorialco/f0-react-native/icons/app"
|
|
32
|
+
|
|
33
|
+
<F0Button label="Submit" onPress={handleSubmit} />
|
|
34
|
+
|
|
35
|
+
<F0Button label="Delete" variant="critical" icon={Archive} />
|
|
36
|
+
|
|
37
|
+
<F0Button label="Add" icon={Archive} hideLabel round />
|
|
38
|
+
|
|
39
|
+
<F0Button label="Save" onPress={async () => await saveData()} />
|
|
40
|
+
|
|
41
|
+
<F0Button label="Love" emoji="🥰" variant="neutral" />
|
|
42
|
+
|
|
43
|
+
<F0Button label="Custom" feedback="scale" />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Props
|
|
47
|
+
|
|
48
|
+
| Prop | Type | Default | Description |
|
|
49
|
+
| ------------------- | -------------------------------- | ----------- | ---------------------------------------------------------- |
|
|
50
|
+
| `label` | `string` | required | Visible label and accessibility base label |
|
|
51
|
+
| `onPress` | `() => void \| Promise<unknown>` | — | Press handler; async return enables auto-loading |
|
|
52
|
+
| `variant` | `F0ButtonVariant` | `"default"` | Visual style variant |
|
|
53
|
+
| `size` | `F0ButtonSize` | `"md"` | Control height and radius |
|
|
54
|
+
| `disabled` | `boolean` | `false` | Disabled state |
|
|
55
|
+
| `loading` | `boolean` | `false` | External loading control |
|
|
56
|
+
| `icon` | `IconType` | — | Optional icon |
|
|
57
|
+
| `emoji` | `string` | — | Optional emoji |
|
|
58
|
+
| `hideLabel` | `boolean` | `false` | Hide visible label (keeps accessibility label) |
|
|
59
|
+
| `round` | `boolean` | `false` | Makes icon-only mode circular |
|
|
60
|
+
| `showBadge` | `boolean` | `false` | Notification badge (outline variant only) |
|
|
61
|
+
| `fullWidth` | `boolean` | `false` | Makes button fill available horizontal space |
|
|
62
|
+
| `feedback` | `PressableFeedbackVariant` | `"both"` | Press feedback mode (`both`, `scale`, `highlight`, `none`) |
|
|
63
|
+
| `accessibilityHint` | `string` | — | Optional screen-reader hint |
|
|
64
|
+
| `testID` | `string` | — | Test identifier |
|
|
65
|
+
|
|
66
|
+
### Variants
|
|
67
|
+
|
|
68
|
+
- `default` - primary/high-emphasis action
|
|
69
|
+
- `outline` - secondary action with border
|
|
70
|
+
- `critical` - destructive action
|
|
71
|
+
- `neutral` - neutral secondary action
|
|
72
|
+
- `ghost` - subtle transparent action
|
|
73
|
+
- `promote` - promotional/highlighted action
|
|
74
|
+
|
|
75
|
+
### Sizes
|
|
76
|
+
|
|
77
|
+
- `sm` - compact
|
|
78
|
+
- `md` - default
|
|
79
|
+
- `lg` - large
|
|
80
|
+
|
|
81
|
+
## Runtime Behavior
|
|
82
|
+
|
|
83
|
+
### Async onPress auto-loading
|
|
84
|
+
|
|
85
|
+
If `onPress` returns a Promise, F0Button enters internal loading state until it resolves.
|
|
86
|
+
|
|
87
|
+
<!-- prettier-ignore -->
|
|
88
|
+
```tsx
|
|
89
|
+
<F0Button
|
|
90
|
+
label="Save"
|
|
91
|
+
onPress={async () => {
|
|
92
|
+
await saveData()
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Loading visuals
|
|
98
|
+
|
|
99
|
+
When `loading` is `true` (external) or an async `onPress` is pending (internal):
|
|
100
|
+
|
|
101
|
+
- the button becomes non-interactive (`disabled`)
|
|
102
|
+
- a centered spinner indicator is rendered
|
|
103
|
+
- button content is visually hidden (`opacity`) to keep layout stable
|
|
104
|
+
|
|
105
|
+
### Press feedback
|
|
106
|
+
|
|
107
|
+
F0Button delegates touch feedback to `PressableFeedback` and supports semantic control via `feedback`.
|
|
108
|
+
|
|
109
|
+
<!-- prettier-ignore -->
|
|
110
|
+
```tsx
|
|
111
|
+
<F0Button label="Both" feedback="both" />
|
|
112
|
+
<F0Button label="Scale only" feedback="scale" />
|
|
113
|
+
<F0Button label="Highlight only" feedback="highlight" />
|
|
114
|
+
<F0Button label="No visual feedback" feedback="none" />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Styling Contract
|
|
118
|
+
|
|
119
|
+
F0Button is semantic-first:
|
|
120
|
+
|
|
121
|
+
- `className` is **not** part of `F0ButtonProps`
|
|
122
|
+
- `style` is **not** part of `F0ButtonProps`
|
|
123
|
+
- Both are filtered at runtime before forwarding props to internal pressable primitives
|
|
124
|
+
|
|
125
|
+
This guarantees semantic variants remain source-of-truth and prevents style overrides from bypassing the API contract.
|
|
126
|
+
|
|
127
|
+
## Accessibility
|
|
128
|
+
|
|
129
|
+
- Always sets `accessibilityRole="button"`
|
|
130
|
+
- Auto-builds `accessibilityLabel` from `label` plus state:
|
|
131
|
+
- `"label, disabled"`
|
|
132
|
+
- `"label, disabled, loading"`
|
|
133
|
+
- Exposes `accessibilityState` with:
|
|
134
|
+
- `disabled`
|
|
135
|
+
- `busy`
|
|
136
|
+
- `hideLabel` does not remove accessibility label
|
|
137
|
+
|
|
138
|
+
## Testing
|
|
139
|
+
|
|
140
|
+
Main test suite:
|
|
141
|
+
|
|
142
|
+
- `src/components/F0Button/__tests__/F0Button.spec.tsx`
|
|
143
|
+
|
|
144
|
+
Coverage includes:
|
|
145
|
+
|
|
146
|
+
- snapshots across variants/sizes/states
|
|
147
|
+
- async loading behavior
|
|
148
|
+
- accessibility label/state
|
|
149
|
+
- badge rendering rules
|
|
150
|
+
- runtime blocking of `className`/`style`
|
|
151
|
+
|
|
152
|
+
## File Structure
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
src/components/F0Button/
|
|
156
|
+
├── F0Button.tsx
|
|
157
|
+
├── F0Button.types.ts
|
|
158
|
+
├── F0Button.styles.ts
|
|
159
|
+
├── F0Button.md
|
|
160
|
+
├── __tests__/
|
|
161
|
+
│ └── F0Button.spec.tsx
|
|
162
|
+
└── index.ts
|
|
163
|
+
```
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { tv } from "tailwind-variants"
|
|
2
|
+
|
|
3
|
+
import type { IconColor } from "../primitives/F0Icon"
|
|
4
|
+
import type { TextColor } from "../primitives/F0Text"
|
|
5
|
+
|
|
6
|
+
import type { ButtonVariant } from "./F0Button.types"
|
|
7
|
+
|
|
8
|
+
export const buttonVariants = tv({
|
|
9
|
+
base: "flex-row items-center justify-center rounded border-none grow-0",
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-f0-background-accent-bold",
|
|
13
|
+
outline: "bg-f0-background-inverse-secondary border border-f0-border",
|
|
14
|
+
neutral: "bg-f0-background-secondary",
|
|
15
|
+
critical: "bg-f0-background-secondary border border-f0-border",
|
|
16
|
+
ghost: "bg-transparent",
|
|
17
|
+
promote: "bg-f0-background-promote border border-f0-border-promote",
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
sm: "h-6 rounded-sm",
|
|
21
|
+
md: "h-8 rounded",
|
|
22
|
+
lg: "h-10 rounded-md",
|
|
23
|
+
},
|
|
24
|
+
disabled: {
|
|
25
|
+
true: "opacity-50",
|
|
26
|
+
false: "",
|
|
27
|
+
},
|
|
28
|
+
round: {
|
|
29
|
+
true: "aspect-square p-0",
|
|
30
|
+
false: "gap-1 px-2 sm:px-3 lg:px-4",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "md",
|
|
36
|
+
disabled: false,
|
|
37
|
+
round: false,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const pressedVariants = tv({
|
|
42
|
+
base: "",
|
|
43
|
+
variants: {
|
|
44
|
+
variant: {
|
|
45
|
+
default: "bg-f0-background-accent-bold-hover",
|
|
46
|
+
outline: "bg-f0-background-tertiary border-opacity-70",
|
|
47
|
+
neutral: "bg-f0-background-secondary-hover",
|
|
48
|
+
critical: "bg-f0-background-critical-bold border-transparent",
|
|
49
|
+
ghost: "bg-f0-background-secondary-hover",
|
|
50
|
+
promote: "bg-f0-background-promote-hover",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
defaultVariants: {
|
|
54
|
+
variant: "default",
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export const loadingContentVariants = tv({
|
|
59
|
+
variants: {
|
|
60
|
+
loading: {
|
|
61
|
+
true: "opacity-0",
|
|
62
|
+
false: "opacity-100",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
defaultVariants: {
|
|
66
|
+
loading: false,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const loadingIndicatorVariants = tv({
|
|
71
|
+
base: "rounded-full border-solid border-t-transparent",
|
|
72
|
+
variants: {
|
|
73
|
+
variant: {
|
|
74
|
+
default: "border-f0-foreground-inverse",
|
|
75
|
+
outline: "border-f0-foreground",
|
|
76
|
+
neutral: "border-f0-foreground",
|
|
77
|
+
critical: "border-f0-icon-critical",
|
|
78
|
+
ghost: "border-f0-foreground",
|
|
79
|
+
promote: "border-f0-icon-promote",
|
|
80
|
+
},
|
|
81
|
+
size: {
|
|
82
|
+
sm: "h-3 w-3 border",
|
|
83
|
+
md: "h-4 w-4 border-2",
|
|
84
|
+
lg: "h-5 w-5 border-2",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
defaultVariants: {
|
|
88
|
+
variant: "default",
|
|
89
|
+
size: "md",
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export const getIconColor = (
|
|
94
|
+
variant: ButtonVariant,
|
|
95
|
+
isPressed: boolean
|
|
96
|
+
): IconColor => {
|
|
97
|
+
switch (variant) {
|
|
98
|
+
case "default":
|
|
99
|
+
return "inverse"
|
|
100
|
+
case "critical":
|
|
101
|
+
return isPressed ? "inverse" : "critical-bold"
|
|
102
|
+
default:
|
|
103
|
+
return "default"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const getIconOnlyColor = (
|
|
108
|
+
variant: ButtonVariant,
|
|
109
|
+
isPressed: boolean
|
|
110
|
+
): IconColor => {
|
|
111
|
+
switch (variant) {
|
|
112
|
+
case "critical":
|
|
113
|
+
return isPressed ? "inverse" : "critical-bold"
|
|
114
|
+
case "default":
|
|
115
|
+
return "inverse"
|
|
116
|
+
case "outline":
|
|
117
|
+
case "neutral":
|
|
118
|
+
case "ghost":
|
|
119
|
+
case "promote":
|
|
120
|
+
default:
|
|
121
|
+
return "bold"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const getTextColor = (
|
|
126
|
+
variant: ButtonVariant,
|
|
127
|
+
isPressed: boolean
|
|
128
|
+
): TextColor => {
|
|
129
|
+
if (isPressed && variant === "critical") {
|
|
130
|
+
return "inverse"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
switch (variant) {
|
|
134
|
+
case "default":
|
|
135
|
+
return "inverse"
|
|
136
|
+
case "critical":
|
|
137
|
+
return "critical"
|
|
138
|
+
default:
|
|
139
|
+
return "default"
|
|
140
|
+
}
|
|
141
|
+
}
|