@xaui/native 0.9.1-alpha.7 → 0.9.1-alpha.9

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.
@@ -0,0 +1,192 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_native from 'react-native';
4
+ import { StyleProp, TextStyle, ViewStyle, TextProps, PressableStateCallbackType } from 'react-native';
5
+ import { a as IconContextValue, b as IconProps, P as PressableFeedbackProps, f as Recipe, n as SlotStyles } from '../../create-recipe-BjxSp9_k.cjs';
6
+ import { S as Size, R as RadiusKey, g as XAUITheme } from '../../theme.type-C9bFJKFm.cjs';
7
+ import 'react-native-reanimated';
8
+
9
+ type ButtonSlot = 'root' | 'label' | 'icon' | 'spinner';
10
+ /**
11
+ * The ten sanctioned appearances (R7). Emphasis and intention are one flat union, which
12
+ * is why there is no `themeColor` beside it: `primary` … `ghost` are the emphasis levels,
13
+ * and each intention carries its own `-soft` low-emphasis level rather than one of them
14
+ * being special-cased.
15
+ */
16
+ type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'success' | 'success-soft' | 'warning' | 'warning-soft' | 'danger' | 'danger-soft';
17
+ type ButtonSize = Size;
18
+ type ButtonProps = Omit<PressableFeedbackProps, 'isPressed' | 'isDisabled' | 'style' | 'children'> & {
19
+ variant?: ButtonVariant;
20
+ /** Height, padding, gap, radius and type. Never width. */
21
+ size?: ButtonSize;
22
+ /** Overrides the radius `size` chose. Unset, a button is the shape its size implies. */
23
+ radius?: RadiusKey;
24
+ /**
25
+ * A raw tint (`'#7c3aed'`), never a token (R7). Where it lands follows the variant: the
26
+ * background of a `primary`, the label of a `ghost`, the border and label of a
27
+ * `tertiary`. Its contrasted, soft and pressed slices are derived in OKLab, so it
28
+ * behaves exactly like `accent` — which is also why it must be a hex value.
29
+ */
30
+ color?: string;
31
+ isDisabled?: boolean;
32
+ /** Presses through as disabled, and inserts a `Button.Spinner` if none is composed. */
33
+ isLoading?: boolean;
34
+ /** Drops the horizontal padding and squares the button on its fixed height. */
35
+ isIconOnly?: boolean;
36
+ /** R9 — `Pressable`'s function form as much as an object or an array. */
37
+ style?: StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);
38
+ children?: ReactNode;
39
+ };
40
+ type ButtonLabelProps = TextProps & {
41
+ children?: ReactNode;
42
+ };
43
+ /**
44
+ * `Icon`'s three forms, plus the `style` every slot carries (R2). `size` and `color`
45
+ * default to what the root resolved; passing either wins over it.
46
+ */
47
+ type ButtonIconProps = IconProps;
48
+ type ButtonSpinnerProps = {
49
+ style?: StyleProp<ViewStyle>;
50
+ /** `false` stops the rotation. The ring stays, so the button does not change size. */
51
+ animation?: boolean;
52
+ };
53
+ /**
54
+ * R5 — resolved styles, not props for a slot to resolve a second time. Each entry is the
55
+ * cached `StyleSheet` reference with the uncached tint pass layered over it, so a slot
56
+ * merges its own `style` on top and does no work of its own.
57
+ */
58
+ type ButtonContextValue = {
59
+ labelStyle: StyleProp<TextStyle>;
60
+ spinnerStyle: StyleProp<ViewStyle>;
61
+ /**
62
+ * Values, not a style: `Icon` hands `size` and `color` to a third-party component, so
63
+ * the root flattens its icon slot once here rather than in every icon it contains.
64
+ */
65
+ icon: IconContextValue;
66
+ isDisabled: boolean;
67
+ isLoading: boolean;
68
+ };
69
+
70
+ /**
71
+ * An icon that takes the button's size and colour without being told either:
72
+ *
73
+ * ```tsx
74
+ * <Button variant="danger">
75
+ * <Button.Icon as={TrashIcon} />
76
+ * <Button.Label>Supprimer</Button.Label>
77
+ * </Button>
78
+ * ```
79
+ *
80
+ * The values come from the root, which resolved them once; an explicit `size` or `color`
81
+ * still wins, which is what `Icon` promises everywhere else in the library.
82
+ */
83
+ declare function ButtonIcon({ size, color, ...rest }: ButtonIconProps): react.JSX.Element;
84
+ declare namespace ButtonIcon {
85
+ var displayName: string;
86
+ }
87
+
88
+ /**
89
+ * The busy indicator. `isLoading` inserts one when none is composed, so
90
+ * `<Button isLoading>Envoi…</Button>` works; composing it explicitly is how you put it
91
+ * after the label instead of before it.
92
+ *
93
+ * A ring rather than RN's `ActivityIndicator`: the recipe owns its diameter and its
94
+ * colour like every other measurement, which is what makes it follow the button's `size`
95
+ * and its variant with nothing to pass. P3 extracts it into the standalone `Spinner`, and
96
+ * this slot becomes its call site.
97
+ */
98
+ declare function ButtonSpinner({ style, animation }: ButtonSpinnerProps): react.JSX.Element;
99
+ declare namespace ButtonSpinner {
100
+ var displayName: string;
101
+ }
102
+
103
+ declare const useButton: () => ButtonContextValue;
104
+
105
+ declare const buttonRecipe: Recipe<"label" | "root" | "icon" | "spinner", "success" | "warning" | "danger" | "primary" | "secondary" | "tertiary" | "ghost" | "success-soft" | "warning-soft" | "danger-soft", {
106
+ readonly size: {
107
+ readonly xs: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
108
+ readonly sm: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
109
+ readonly md: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
110
+ readonly lg: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
111
+ };
112
+ readonly radius: {
113
+ readonly xs: (t: XAUITheme) => {
114
+ root: {
115
+ borderRadius: number;
116
+ };
117
+ };
118
+ readonly sm: (t: XAUITheme) => {
119
+ root: {
120
+ borderRadius: number;
121
+ };
122
+ };
123
+ readonly md: (t: XAUITheme) => {
124
+ root: {
125
+ borderRadius: number;
126
+ };
127
+ };
128
+ readonly lg: (t: XAUITheme) => {
129
+ root: {
130
+ borderRadius: number;
131
+ };
132
+ };
133
+ readonly xl: (t: XAUITheme) => {
134
+ root: {
135
+ borderRadius: number;
136
+ };
137
+ };
138
+ readonly '2xl': (t: XAUITheme) => {
139
+ root: {
140
+ borderRadius: number;
141
+ };
142
+ };
143
+ readonly '3xl': (t: XAUITheme) => {
144
+ root: {
145
+ borderRadius: number;
146
+ };
147
+ };
148
+ readonly '4xl': (t: XAUITheme) => {
149
+ root: {
150
+ borderRadius: number;
151
+ };
152
+ };
153
+ readonly field: (t: XAUITheme) => {
154
+ root: {
155
+ borderRadius: number;
156
+ };
157
+ };
158
+ readonly full: (t: XAUITheme) => {
159
+ root: {
160
+ borderRadius: number;
161
+ };
162
+ };
163
+ };
164
+ readonly isIconOnly: {
165
+ readonly true: () => {
166
+ root: {
167
+ paddingHorizontal: number;
168
+ aspectRatio: number;
169
+ };
170
+ };
171
+ };
172
+ }>;
173
+
174
+ declare const Button: react.ForwardRefExoticComponent<Omit<PressableFeedbackProps, "children" | "style" | "isPressed" | "isDisabled"> & {
175
+ variant?: ButtonVariant;
176
+ size?: ButtonSize;
177
+ radius?: RadiusKey;
178
+ color?: string;
179
+ isDisabled?: boolean;
180
+ isLoading?: boolean;
181
+ isIconOnly?: boolean;
182
+ style?: react_native.StyleProp<react_native.ViewStyle> | ((state: react_native.PressableStateCallbackType) => react_native.StyleProp<react_native.ViewStyle>);
183
+ children?: react.ReactNode;
184
+ } & react.RefAttributes<react_native.View>> & {
185
+ Label: react.ForwardRefExoticComponent<react_native.TextProps & {
186
+ children?: react.ReactNode;
187
+ } & react.RefAttributes<react_native.Text>>;
188
+ Icon: typeof ButtonIcon;
189
+ Spinner: typeof ButtonSpinner;
190
+ };
191
+
192
+ export { Button, type ButtonContextValue, type ButtonIconProps, type ButtonLabelProps, type ButtonProps, type ButtonSize, type ButtonSlot, type ButtonSpinnerProps, type ButtonVariant, buttonRecipe, useButton };
@@ -0,0 +1,192 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import * as react_native from 'react-native';
4
+ import { StyleProp, TextStyle, ViewStyle, TextProps, PressableStateCallbackType } from 'react-native';
5
+ import { a as IconContextValue, b as IconProps, P as PressableFeedbackProps, f as Recipe, n as SlotStyles } from '../../create-recipe-M99yoHrG.js';
6
+ import { S as Size, R as RadiusKey, g as XAUITheme } from '../../theme.type-C9bFJKFm.js';
7
+ import 'react-native-reanimated';
8
+
9
+ type ButtonSlot = 'root' | 'label' | 'icon' | 'spinner';
10
+ /**
11
+ * The ten sanctioned appearances (R7). Emphasis and intention are one flat union, which
12
+ * is why there is no `themeColor` beside it: `primary` … `ghost` are the emphasis levels,
13
+ * and each intention carries its own `-soft` low-emphasis level rather than one of them
14
+ * being special-cased.
15
+ */
16
+ type ButtonVariant = 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'success' | 'success-soft' | 'warning' | 'warning-soft' | 'danger' | 'danger-soft';
17
+ type ButtonSize = Size;
18
+ type ButtonProps = Omit<PressableFeedbackProps, 'isPressed' | 'isDisabled' | 'style' | 'children'> & {
19
+ variant?: ButtonVariant;
20
+ /** Height, padding, gap, radius and type. Never width. */
21
+ size?: ButtonSize;
22
+ /** Overrides the radius `size` chose. Unset, a button is the shape its size implies. */
23
+ radius?: RadiusKey;
24
+ /**
25
+ * A raw tint (`'#7c3aed'`), never a token (R7). Where it lands follows the variant: the
26
+ * background of a `primary`, the label of a `ghost`, the border and label of a
27
+ * `tertiary`. Its contrasted, soft and pressed slices are derived in OKLab, so it
28
+ * behaves exactly like `accent` — which is also why it must be a hex value.
29
+ */
30
+ color?: string;
31
+ isDisabled?: boolean;
32
+ /** Presses through as disabled, and inserts a `Button.Spinner` if none is composed. */
33
+ isLoading?: boolean;
34
+ /** Drops the horizontal padding and squares the button on its fixed height. */
35
+ isIconOnly?: boolean;
36
+ /** R9 — `Pressable`'s function form as much as an object or an array. */
37
+ style?: StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);
38
+ children?: ReactNode;
39
+ };
40
+ type ButtonLabelProps = TextProps & {
41
+ children?: ReactNode;
42
+ };
43
+ /**
44
+ * `Icon`'s three forms, plus the `style` every slot carries (R2). `size` and `color`
45
+ * default to what the root resolved; passing either wins over it.
46
+ */
47
+ type ButtonIconProps = IconProps;
48
+ type ButtonSpinnerProps = {
49
+ style?: StyleProp<ViewStyle>;
50
+ /** `false` stops the rotation. The ring stays, so the button does not change size. */
51
+ animation?: boolean;
52
+ };
53
+ /**
54
+ * R5 — resolved styles, not props for a slot to resolve a second time. Each entry is the
55
+ * cached `StyleSheet` reference with the uncached tint pass layered over it, so a slot
56
+ * merges its own `style` on top and does no work of its own.
57
+ */
58
+ type ButtonContextValue = {
59
+ labelStyle: StyleProp<TextStyle>;
60
+ spinnerStyle: StyleProp<ViewStyle>;
61
+ /**
62
+ * Values, not a style: `Icon` hands `size` and `color` to a third-party component, so
63
+ * the root flattens its icon slot once here rather than in every icon it contains.
64
+ */
65
+ icon: IconContextValue;
66
+ isDisabled: boolean;
67
+ isLoading: boolean;
68
+ };
69
+
70
+ /**
71
+ * An icon that takes the button's size and colour without being told either:
72
+ *
73
+ * ```tsx
74
+ * <Button variant="danger">
75
+ * <Button.Icon as={TrashIcon} />
76
+ * <Button.Label>Supprimer</Button.Label>
77
+ * </Button>
78
+ * ```
79
+ *
80
+ * The values come from the root, which resolved them once; an explicit `size` or `color`
81
+ * still wins, which is what `Icon` promises everywhere else in the library.
82
+ */
83
+ declare function ButtonIcon({ size, color, ...rest }: ButtonIconProps): react.JSX.Element;
84
+ declare namespace ButtonIcon {
85
+ var displayName: string;
86
+ }
87
+
88
+ /**
89
+ * The busy indicator. `isLoading` inserts one when none is composed, so
90
+ * `<Button isLoading>Envoi…</Button>` works; composing it explicitly is how you put it
91
+ * after the label instead of before it.
92
+ *
93
+ * A ring rather than RN's `ActivityIndicator`: the recipe owns its diameter and its
94
+ * colour like every other measurement, which is what makes it follow the button's `size`
95
+ * and its variant with nothing to pass. P3 extracts it into the standalone `Spinner`, and
96
+ * this slot becomes its call site.
97
+ */
98
+ declare function ButtonSpinner({ style, animation }: ButtonSpinnerProps): react.JSX.Element;
99
+ declare namespace ButtonSpinner {
100
+ var displayName: string;
101
+ }
102
+
103
+ declare const useButton: () => ButtonContextValue;
104
+
105
+ declare const buttonRecipe: Recipe<"label" | "root" | "icon" | "spinner", "success" | "warning" | "danger" | "primary" | "secondary" | "tertiary" | "ghost" | "success-soft" | "warning-soft" | "danger-soft", {
106
+ readonly size: {
107
+ readonly xs: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
108
+ readonly sm: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
109
+ readonly md: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
110
+ readonly lg: (theme: XAUITheme) => SlotStyles<ButtonSlot>;
111
+ };
112
+ readonly radius: {
113
+ readonly xs: (t: XAUITheme) => {
114
+ root: {
115
+ borderRadius: number;
116
+ };
117
+ };
118
+ readonly sm: (t: XAUITheme) => {
119
+ root: {
120
+ borderRadius: number;
121
+ };
122
+ };
123
+ readonly md: (t: XAUITheme) => {
124
+ root: {
125
+ borderRadius: number;
126
+ };
127
+ };
128
+ readonly lg: (t: XAUITheme) => {
129
+ root: {
130
+ borderRadius: number;
131
+ };
132
+ };
133
+ readonly xl: (t: XAUITheme) => {
134
+ root: {
135
+ borderRadius: number;
136
+ };
137
+ };
138
+ readonly '2xl': (t: XAUITheme) => {
139
+ root: {
140
+ borderRadius: number;
141
+ };
142
+ };
143
+ readonly '3xl': (t: XAUITheme) => {
144
+ root: {
145
+ borderRadius: number;
146
+ };
147
+ };
148
+ readonly '4xl': (t: XAUITheme) => {
149
+ root: {
150
+ borderRadius: number;
151
+ };
152
+ };
153
+ readonly field: (t: XAUITheme) => {
154
+ root: {
155
+ borderRadius: number;
156
+ };
157
+ };
158
+ readonly full: (t: XAUITheme) => {
159
+ root: {
160
+ borderRadius: number;
161
+ };
162
+ };
163
+ };
164
+ readonly isIconOnly: {
165
+ readonly true: () => {
166
+ root: {
167
+ paddingHorizontal: number;
168
+ aspectRatio: number;
169
+ };
170
+ };
171
+ };
172
+ }>;
173
+
174
+ declare const Button: react.ForwardRefExoticComponent<Omit<PressableFeedbackProps, "children" | "style" | "isPressed" | "isDisabled"> & {
175
+ variant?: ButtonVariant;
176
+ size?: ButtonSize;
177
+ radius?: RadiusKey;
178
+ color?: string;
179
+ isDisabled?: boolean;
180
+ isLoading?: boolean;
181
+ isIconOnly?: boolean;
182
+ style?: react_native.StyleProp<react_native.ViewStyle> | ((state: react_native.PressableStateCallbackType) => react_native.StyleProp<react_native.ViewStyle>);
183
+ children?: react.ReactNode;
184
+ } & react.RefAttributes<react_native.View>> & {
185
+ Label: react.ForwardRefExoticComponent<react_native.TextProps & {
186
+ children?: react.ReactNode;
187
+ } & react.RefAttributes<react_native.Text>>;
188
+ Icon: typeof ButtonIcon;
189
+ Spinner: typeof ButtonSpinner;
190
+ };
191
+
192
+ export { Button, type ButtonContextValue, type ButtonIconProps, type ButtonLabelProps, type ButtonProps, type ButtonSize, type ButtonSlot, type ButtonSpinnerProps, type ButtonVariant, buttonRecipe, useButton };
@@ -0,0 +1,12 @@
1
+ import {
2
+ Button,
3
+ buttonRecipe,
4
+ useButton
5
+ } from "../../chunk-PBRPIZZ2.js";
6
+ import "../../chunk-U3C6VN4X.js";
7
+ import "../../chunk-X2K2FX3W.js";
8
+ export {
9
+ Button,
10
+ buttonRecipe,
11
+ useButton
12
+ };
@@ -0,0 +1,197 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ import { ImageSourcePropType, StyleProp, ImageStyle, PressableProps, ViewStyle, TextStyle } from 'react-native';
3
+ import { SharedValue } from 'react-native-reanimated';
4
+ import { g as XAUITheme, X as XAUIColors } from './theme.type-C9bFJKFm.cjs';
5
+
6
+ /**
7
+ * The props Lucide, Ionicons and `react-native-vector-icons` all accept — the shape
8
+ * `as` is handed. It is a convention rather than an interface anyone published, which is
9
+ * exactly why `Icon` exists: without it every call site computes the two values by hand.
10
+ */
11
+ type IconComponentProps = {
12
+ size?: number;
13
+ color?: string;
14
+ };
15
+ type IconProps = {
16
+ /** An icon component. `size` and `color` are injected into it. */
17
+ as?: ComponentType<IconComponentProps>;
18
+ /** A raw `react-native-svg` element, cloned with the resolved size and colour. */
19
+ children?: ReactNode;
20
+ /** An image, tinted with the resolved colour. */
21
+ source?: ImageSourcePropType;
22
+ /** Overrides what the surrounding slot asked for. */
23
+ size?: number;
24
+ /** A raw value (R7), never a token. Overrides the slot's. */
25
+ color?: string;
26
+ style?: StyleProp<ImageStyle>;
27
+ };
28
+ /** What a component root publishes so the icons inside it need no props at all. */
29
+ type IconContextValue = {
30
+ size?: number;
31
+ color?: string;
32
+ };
33
+
34
+ /**
35
+ * What the root does under the finger. `scale-highlight` and `scale-ripple` mount their
36
+ * overlay themselves; `scale` mounts none, which is what a root picks when it renders its
37
+ * own `<PressableFeedback.Highlight>` to style it (R1: no prop reaches into another
38
+ * component's insides).
39
+ */
40
+ type FeedbackVariant = 'scale-highlight' | 'scale-ripple' | 'scale' | 'none';
41
+ type AnimationConfig = {
42
+ scale?: boolean;
43
+ highlight?: boolean;
44
+ ripple?: boolean;
45
+ };
46
+ /**
47
+ * `false` and `'disabled'` turn this component's animations off. `'disable-all'` turns
48
+ * them off for its descendants too — a long list kills every row's worklets with one
49
+ * prop instead of threading it down. An object switches them off one at a time.
50
+ */
51
+ type AnimationProp = boolean | 'disabled' | 'disable-all' | AnimationConfig;
52
+ /** `animation` once normalised — what the components actually read. */
53
+ type ResolvedAnimation = {
54
+ scale: boolean;
55
+ highlight: boolean;
56
+ ripple: boolean;
57
+ /** True when every sub-animation is off: the branch that mounts no worklet at all. */
58
+ none: boolean;
59
+ /** Propagated to descendants through context. */
60
+ disableAll: boolean;
61
+ };
62
+ type PressableFeedbackProps = Omit<PressableProps, 'style' | 'children' | 'disabled'> & {
63
+ /** Controlled: the root owns the state, because its recipe resolves on it (R5). */
64
+ isPressed?: boolean;
65
+ /** R8: `disabled` is not part of the public vocabulary, `isX` is. */
66
+ isDisabled?: boolean;
67
+ /**
68
+ * Merge into the single child instead of rendering a pressable (R12) — **keeping the
69
+ * feedback**. Swapping this component out for a bare `Slot` would silently drop the
70
+ * touch feedback of every `asChild` control.
71
+ */
72
+ asChild?: boolean;
73
+ feedbackVariant?: FeedbackVariant;
74
+ animation?: AnimationProp;
75
+ style?: StyleProp<ViewStyle>;
76
+ /**
77
+ * `Pressable`'s function form is dropped on purpose. It exists to hand the press state
78
+ * to children; here the root above already owns that state and this publishes it
79
+ * through context, so the function form would be a second, quieter source of truth.
80
+ */
81
+ children?: ReactNode;
82
+ };
83
+ /**
84
+ * A slot's own animation, overriding the blanket one on the root. `false` switches that
85
+ * slot off; the object tunes it. Deliberately two knobs rather than a full timing
86
+ * surface — anything past this is a different animation, and that is a component's job,
87
+ * not a prop's.
88
+ */
89
+ type SlotAnimation = boolean | {
90
+ /** Milliseconds. Falls back to the shared press timing. */
91
+ duration?: number;
92
+ /** How far the overlay goes at full press, 0 to 1. */
93
+ opacity?: number;
94
+ };
95
+ type FeedbackContext = {
96
+ isPressed: boolean;
97
+ animation: ResolvedAnimation;
98
+ /** Absent on the static branch, where nothing animates and no worklet is mounted. */
99
+ progress?: SharedValue<number>;
100
+ /**
101
+ * Bumped on every press-in. The ripple starts from this rather than from a `useEffect`
102
+ * on `isPressed`: a one-shot driven by a boolean depends on React re-rendering between
103
+ * the two touch events, and starting it from the event that carries the coordinates is
104
+ * both simpler and impossible to miss.
105
+ */
106
+ pressCount?: SharedValue<number>;
107
+ /** Where the finger landed, and how big the root is — the ripple needs both. */
108
+ origin?: SharedValue<{
109
+ x: number;
110
+ y: number;
111
+ }>;
112
+ size?: SharedValue<{
113
+ width: number;
114
+ height: number;
115
+ }>;
116
+ };
117
+
118
+ /**
119
+ * A slot is a view or a text node, and a recipe writes one object per slot, so the two
120
+ * RN style shapes are merged rather than discriminated per slot.
121
+ */
122
+ type SlotStyle = ViewStyle & TextStyle;
123
+ type SlotStyles<Slot extends string> = Partial<Record<Slot, SlotStyle>>;
124
+ /** The roles a variant consumes. The variant names tokens; `paint` says where they land. */
125
+ type VariantRole = 'bg' | 'bgPressed' | 'fg' | 'border';
126
+ /** Token names per role — no colour value ever appears in a recipe. */
127
+ type VariantTokens = Partial<Record<VariantRole, keyof XAUIColors>>;
128
+ /** The same roles resolved: theme colours, or the slices of a raw `color`. */
129
+ type VariantColors = Partial<Record<VariantRole, string>>;
130
+ /**
131
+ * `disabled` is applied last of the three: a control that is both pressed and disabled
132
+ * has to read disabled.
133
+ */
134
+ type StateName = 'focused' | 'pressed' | 'disabled';
135
+ type States = Partial<Record<StateName, boolean>>;
136
+ /** Reads the theme and the variant's resolved colours; returns one style per slot. */
137
+ type StyleFn<Slot extends string> = (theme: XAUITheme, colors: VariantColors) => SlotStyles<Slot>;
138
+ /** Named axes of finite token values — `{ size: { sm: fn, md: fn } }`. */
139
+ type Axes<Slot extends string> = Record<string, Record<string, StyleFn<Slot>>>;
140
+ /** One value per axis, plus the variant. Missing keys fall back to `defaultVariants`. */
141
+ type Selection<Variant extends string, A extends Axes<string>> = {
142
+ variant?: Variant;
143
+ } & {
144
+ [Axis in keyof A]?: Extract<keyof A[Axis], string>;
145
+ };
146
+ type CompoundVariant<Slot extends string, Variant extends string, A extends Axes<Slot>> = {
147
+ when: Selection<Variant, A>;
148
+ style: StyleFn<Slot>;
149
+ };
150
+ type RecipeConfig<Slot extends string, Variant extends string, A extends Axes<Slot>> = {
151
+ /** Every slot the component publishes. Slots a recipe never styles resolve to `{}`. */
152
+ slots: readonly Slot[];
153
+ base?: StyleFn<Slot>;
154
+ variantTokens?: Record<Variant, VariantTokens>;
155
+ /** Where the variant's colours land — written once, and it holds for every variant. */
156
+ paint?: StyleFn<Slot>;
157
+ variants?: A;
158
+ compoundVariants?: ReadonlyArray<CompoundVariant<Slot, Variant, A>>;
159
+ states?: Partial<Record<StateName, StyleFn<Slot>>>;
160
+ defaultVariants?: Selection<Variant, A>;
161
+ };
162
+ /** Stable references: the same object for the same tokens, for the app's lifetime. */
163
+ type ResolvedStyles<Slot extends string> = Readonly<Record<Slot, SlotStyle>>;
164
+ /** A selection with `defaultVariants` already folded in, keyed by axis name. */
165
+ type ResolvedSelection = Readonly<Record<string, string | undefined>>;
166
+
167
+ type ResolveArgs<Variant extends string, A extends Axes<string>> = {
168
+ theme: XAUITheme;
169
+ selection?: Selection<Variant, A>;
170
+ states?: States;
171
+ };
172
+ type TintArgs<Variant extends string, A extends Axes<string>> = ResolveArgs<Variant, A> & {
173
+ color: string;
174
+ };
175
+ type Recipe<Slot extends string, Variant extends string, A extends Axes<Slot>> = {
176
+ readonly slots: readonly Slot[];
177
+ /** The cached pass: stable `StyleSheet` references, keyed by tokens alone. */
178
+ resolve(args: ResolveArgs<Variant, A>): ResolvedStyles<Slot>;
179
+ /**
180
+ * The tint pass: the same functions run again with `color`'s slices in place of the
181
+ * theme's tokens. Uncached and allocating, and only ever called when `color` is set.
182
+ */
183
+ tint(args: TintArgs<Variant, A>): SlotStyles<Slot>;
184
+ };
185
+ /**
186
+ * A component's style, declared once. Resolution splits in two because the two halves
187
+ * have different lifetimes: everything keyed by a finite token is cached forever, and
188
+ * an arbitrary `color` is recomputed per render — which is what keeps the cache bounded
189
+ * by the number of token combinations rather than by the palette users invent.
190
+ *
191
+ * const styles = buttonRecipe.resolve({ theme, selection: { variant, size }, states })
192
+ * const tint = color ? buttonRecipe.tint({ theme, color, selection, states }) : undefined
193
+ * <View style={[styles.root, tint?.root, style]} />
194
+ */
195
+ declare function createRecipe<Slot extends string, Variant extends string, const A extends Axes<Slot>>(config: RecipeConfig<Slot, Variant, A>): Recipe<Slot, Variant, A>;
196
+
197
+ export { type AnimationConfig as A, type CompoundVariant as C, type FeedbackContext as F, type IconComponentProps as I, type PressableFeedbackProps as P, type ResolvedAnimation as R, type SlotAnimation as S, type TintArgs as T, type VariantColors as V, type IconContextValue as a, type IconProps as b, type AnimationProp as c, type FeedbackVariant as d, createRecipe as e, type Recipe as f, type ResolveArgs as g, type Axes as h, type RecipeConfig as i, type ResolvedSelection as j, type ResolvedStyles as k, type Selection as l, type SlotStyle as m, type SlotStyles as n, type StateName as o, type States as p, type StyleFn as q, type VariantRole as r, type VariantTokens as s };