@thewhileloop/whileui 4.2.3 → 4.4.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 +1271 -663
- package/dist/agent/catalog.json +1737 -0
- package/dist/agent/llms-components.txt +2808 -0
- package/dist/agent/llms-full.txt +2998 -0
- package/dist/agent/llms.txt +152 -0
- package/dist/blocks/chat/chat.d.ts +10 -2
- package/dist/blocks/chat/chat.js +1 -1
- package/dist/blocks/chat/index.d.ts +1 -1
- package/dist/blocks/commerce/feature-gate.js +2 -2
- package/dist/blocks/commerce/metric-card.js +1 -1
- package/dist/blocks/commerce/subscription-card.d.ts +7 -0
- package/dist/blocks/commerce/usage-bar.d.ts +4 -0
- package/dist/blocks/layout/app-shell.d.ts +4 -0
- package/dist/blocks/layout/empty-state.js +1 -1
- package/dist/blocks/layout/error-state.js +1 -1
- package/dist/blocks/layout/page-skeleton.d.ts +1 -0
- package/dist/blocks/layout/pull-to-refresh-scroll-view.d.ts +1 -0
- package/dist/blocks/layout/screen-skeleton.d.ts +5 -0
- package/dist/blocks/layout/smart-input.js +2 -2
- package/dist/blocks/lists/list-item.js +1 -1
- package/dist/blocks/lists/notification-item.js +1 -1
- package/dist/blocks/media/smart-image.js +4 -2
- package/dist/blocks/navigation/bottom-nav.js +1 -1
- package/dist/blocks/navigation/drawer-menu.js +1 -1
- package/dist/blocks/navigation/floating-bottom-nav.js +3 -1
- package/dist/blocks/navigation/tab-bar.js +1 -1
- package/dist/components/alert/alert.d.ts +7 -0
- package/dist/components/alert/alert.js +23 -4
- package/dist/components/button/button.d.ts +3 -0
- package/dist/components/button/button.js +2 -1
- package/dist/components/fab/fab.d.ts +12 -0
- package/dist/components/fab/fab.js +14 -2
- package/dist/components/fab/index.d.ts +1 -1
- package/dist/components/fab/index.js +1 -1
- package/dist/components/form-field/form-field.d.ts +18 -0
- package/dist/components/form-field/form-field.js +25 -4
- package/dist/components/input/input.d.ts +5 -0
- package/dist/components/input/input.js +4 -4
- package/dist/components/numeric-input/numeric-input.d.ts +3 -0
- package/dist/components/numeric-input/numeric-input.js +4 -4
- package/dist/components/otp-input/otp-input.d.ts +8 -0
- package/dist/components/popover/popover.js +9 -1
- package/dist/components/pressable/pressable.d.ts +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +39 -1
- package/dist/components/segmented-control/segmented-control.js +29 -5
- package/dist/components/select/select.js +7 -1
- package/dist/components/surface/surface.d.ts +5 -0
- package/dist/components/textarea/textarea.js +3 -3
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.js +53 -35
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/dismiss-keyboard-on-open.d.ts +16 -0
- package/dist/lib/dismiss-keyboard-on-open.js +22 -0
- package/dist/lib/frosted-surface.d.ts +3 -0
- package/dist/lib/recipes.d.ts +20 -0
- package/dist/lib/recipes.js +19 -2
- package/dist/lib/theme-bridge.d.ts +9 -0
- package/dist/lib/theme-bridge.js +3 -1
- package/dist/lib/theme-contract.js +12 -12
- package/dist/lib/theme-presets.js +9 -9
- package/dist/lib/theme-seeds.d.ts +40 -0
- package/dist/lib/theme-seeds.js +143 -0
- package/dist/metro.cjs +5 -4
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +1 -0
- package/package.json +3 -2
|
@@ -51,8 +51,26 @@ export interface FormFieldState {
|
|
|
51
51
|
invalid: boolean;
|
|
52
52
|
disabled: boolean;
|
|
53
53
|
required: boolean;
|
|
54
|
+
/** Set inside a FormField: `FormLabel` takes it as its id and controls point to it. */
|
|
55
|
+
labelId?: string;
|
|
54
56
|
}
|
|
55
57
|
export declare function useFormFieldState(): FormFieldState;
|
|
58
|
+
interface CallerNaming {
|
|
59
|
+
accessibilityLabel?: string;
|
|
60
|
+
'aria-label'?: string;
|
|
61
|
+
'aria-labelledby'?: string;
|
|
62
|
+
accessibilityLabelledBy?: string | string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Accessibility props that tie a text control to its FormField: named by the FormLabel
|
|
66
|
+
* unless the caller names or links it any of the four ways React Native allows, and
|
|
67
|
+
* required when the field is. Spread before the caller's props. iOS has no labelled-by;
|
|
68
|
+
* VoiceOver reads the visible label just before the field.
|
|
69
|
+
*/
|
|
70
|
+
export declare function fieldAccessibilityProps(field: FormFieldState, props: CallerNaming): {
|
|
71
|
+
'aria-labelledby': string | undefined;
|
|
72
|
+
'aria-required': true | undefined;
|
|
73
|
+
};
|
|
56
74
|
export interface FormFieldProps extends ViewProps, VariantProps<typeof formFieldVariants> {
|
|
57
75
|
invalid?: boolean;
|
|
58
76
|
disabled?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import React, { createContext, useContext, useId, useMemo } from 'react';
|
|
3
3
|
import { Text, View, } from 'react-native';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
5
|
import { tv } from '../../lib/tv';
|
|
@@ -44,16 +44,37 @@ const FormFieldContext = createContext({
|
|
|
44
44
|
export function useFormFieldState() {
|
|
45
45
|
return useContext(FormFieldContext);
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Accessibility props that tie a text control to its FormField: named by the FormLabel
|
|
49
|
+
* unless the caller names or links it any of the four ways React Native allows, and
|
|
50
|
+
* required when the field is. Spread before the caller's props. iOS has no labelled-by;
|
|
51
|
+
* VoiceOver reads the visible label just before the field.
|
|
52
|
+
*/
|
|
53
|
+
export function fieldAccessibilityProps(field, props) {
|
|
54
|
+
const named = props.accessibilityLabel ??
|
|
55
|
+
props['aria-label'] ??
|
|
56
|
+
props['aria-labelledby'] ??
|
|
57
|
+
props.accessibilityLabelledBy;
|
|
58
|
+
return {
|
|
59
|
+
'aria-labelledby': named ? undefined : field.labelId,
|
|
60
|
+
'aria-required': field.required || undefined,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
47
63
|
const FormField = React.forwardRef(({ className, density = 'default', invalid = false, disabled = false, required = false, ...props }, ref) => {
|
|
48
64
|
const interaction = useInteractionTokens();
|
|
49
|
-
const
|
|
65
|
+
const labelId = useId();
|
|
66
|
+
const contextValue = useMemo(() => ({ density, invalid, disabled, required, labelId }), [density, disabled, invalid, labelId, required]);
|
|
50
67
|
return (_jsx(FormFieldContext.Provider, { value: contextValue, children: _jsx(View, { ref: ref, className: cn(formFieldVariants({ density }), className), style: disabled ? { opacity: interaction.disabledOpacitySoft } : undefined, ...props }) }));
|
|
51
68
|
});
|
|
52
69
|
FormField.displayName = 'FormField';
|
|
53
70
|
const FormLabel = React.forwardRef(({ className, showRequiredIndicator = true, children, style, ...props }, ref) => {
|
|
54
|
-
const { invalid, disabled, required } = useContext(FormFieldContext);
|
|
71
|
+
const { invalid, disabled, required, labelId } = useContext(FormFieldContext);
|
|
55
72
|
const visual = useVisualTokens();
|
|
56
|
-
|
|
73
|
+
const indicator = required && showRequiredIndicator;
|
|
74
|
+
return (_jsxs(Text, { ref: ref, nativeID: labelId,
|
|
75
|
+
// iOS reads nested Text as one string, so hiding the asterisk alone still says
|
|
76
|
+
// "star"; a plain-text label names it once. The control announces "required".
|
|
77
|
+
accessibilityLabel: indicator && typeof children === 'string' ? children : undefined, className: cn('font-medium text-foreground', invalid && 'text-destructive', disabled && 'text-muted-foreground', className), style: [typographyStyle(visual, 'label'), style], ...props, children: [children, indicator ? (_jsx(Text, { "aria-hidden": true, className: "text-destructive", children: ' *' })) : null] }));
|
|
57
78
|
});
|
|
58
79
|
FormLabel.displayName = 'FormLabel';
|
|
59
80
|
const FormControl = React.forwardRef(({ className, style, ...props }, ref) => {
|
|
@@ -27,7 +27,12 @@ declare const inputVariants: import("tailwind-variants").TVReturnType<{
|
|
|
27
27
|
export interface InputProps extends TextInputProps, VariantProps<typeof inputVariants> {
|
|
28
28
|
'aria-invalid'?: boolean;
|
|
29
29
|
className?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Shown before the text. A decorative icon here needs `aria-hidden`; screen readers
|
|
32
|
+
* otherwise stop on it as a separate element and read out its glyph.
|
|
33
|
+
*/
|
|
30
34
|
prefix?: React.ReactNode;
|
|
35
|
+
/** Shown after the text: a unit, or a control such as a clear button. */
|
|
31
36
|
suffix?: React.ReactNode;
|
|
32
37
|
inputClassName?: string;
|
|
33
38
|
}
|
|
@@ -7,9 +7,9 @@ import { tv } from '../../lib/tv';
|
|
|
7
7
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
8
8
|
import { useInteractionTokens } from '../../lib/interaction-tokens';
|
|
9
9
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
10
|
-
import { typographyStyle } from '../../lib/recipes';
|
|
10
|
+
import { textCursorColors, typographyStyle } from '../../lib/recipes';
|
|
11
11
|
import { webAutofillField } from '../../lib/web-autofill';
|
|
12
|
-
import { useFormFieldState } from '../form-field/form-field';
|
|
12
|
+
import { fieldAccessibilityProps, useFormFieldState } from '../form-field/form-field';
|
|
13
13
|
const inputVariants = tv({
|
|
14
14
|
base: 'w-full border-border bg-muted text-foreground',
|
|
15
15
|
variants: {
|
|
@@ -43,11 +43,11 @@ const Input = React.forwardRef(({ className, prefix, suffix, inputClassName, var
|
|
|
43
43
|
paddingHorizontal: plain ? 0 : visual.controlPaddingXDefault,
|
|
44
44
|
},
|
|
45
45
|
!effectiveEditable ? { opacity: interaction.disabledOpacity } : undefined,
|
|
46
|
-
], children: [prefix && (_jsx(View, { style: { paddingRight: Math.max(8, Math.round(visual.controlPaddingXDefault * 0.5)) }, children: prefix })), _jsx(TextInput, { ref: ref, className: cn('flex-1 text-foreground placeholder:text-muted-foreground web:outline-none', !plain && webAutofillField, inputClassName), style: {
|
|
46
|
+
], children: [prefix && (_jsx(View, { style: { paddingRight: Math.max(8, Math.round(visual.controlPaddingXDefault * 0.5)) }, children: prefix })), _jsx(TextInput, { ref: ref, ...fieldAccessibilityProps(field, props), className: cn('flex-1 text-foreground placeholder:text-muted-foreground web:outline-none', !plain && webAutofillField, inputClassName), style: {
|
|
47
47
|
...typographyStyle(visual, 'label'),
|
|
48
48
|
minHeight: visual.controlHeightDefault - 2,
|
|
49
49
|
paddingVertical: visual.controlPaddingYDefault,
|
|
50
|
-
}, editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, placeholderTextColor: placeholderTextColor ?? colors.placeholder, onFocus: (event) => {
|
|
50
|
+
}, editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...textCursorColors(colors), onFocus: (event) => {
|
|
51
51
|
setFocused(true);
|
|
52
52
|
onFocus?.(event);
|
|
53
53
|
}, onBlur: (event) => {
|
|
@@ -52,12 +52,15 @@ export interface NumericInputProps extends Omit<TextInputProps, 'value' | 'defau
|
|
|
52
52
|
inputClassName?: string;
|
|
53
53
|
prefix?: React.ReactNode;
|
|
54
54
|
suffix?: React.ReactNode;
|
|
55
|
+
/** Controlled numeric value. */
|
|
55
56
|
value?: number | null;
|
|
56
57
|
defaultValue?: number;
|
|
58
|
+
/** Numeric value change callback. */
|
|
57
59
|
onValueChange?: (value: number | null) => void;
|
|
58
60
|
min?: number;
|
|
59
61
|
max?: number;
|
|
60
62
|
step?: number;
|
|
63
|
+
/** Show decrement/increment controls. */
|
|
61
64
|
showSteppers?: boolean;
|
|
62
65
|
/** Drop the field shell (border, background, radius, padding) for embedding inside another field shell like LabeledField. */
|
|
63
66
|
unstyled?: boolean;
|
|
@@ -7,9 +7,9 @@ import { tv } from '../../lib/tv';
|
|
|
7
7
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
8
8
|
import { useInteractionTokens, withInteractivePressableStyle } from '../../lib/interaction-tokens';
|
|
9
9
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
10
|
-
import { typographyStyle } from '../../lib/recipes';
|
|
10
|
+
import { textCursorColors, typographyStyle } from '../../lib/recipes';
|
|
11
11
|
import { webAutofillField } from '../../lib/web-autofill';
|
|
12
|
-
import { useFormFieldState } from '../form-field/form-field';
|
|
12
|
+
import { fieldAccessibilityProps, useFormFieldState } from '../form-field/form-field';
|
|
13
13
|
const numericInputVariants = tv({
|
|
14
14
|
base: 'w-full flex-row items-center border-border bg-muted',
|
|
15
15
|
variants: {
|
|
@@ -171,13 +171,13 @@ const NumericInput = React.forwardRef(({ className, inputClassName, prefix, suff
|
|
|
171
171
|
paddingRight: unstyled ? 0 : showSteppers ? stepInset : 0,
|
|
172
172
|
},
|
|
173
173
|
!effectiveEditable ? { opacity: interaction.disabledOpacity } : undefined,
|
|
174
|
-
], children: [_jsx(NumericAffix, { side: "prefix", visual: visual, children: prefix }), _jsx(View, { className: "min-w-0 flex-1", style: Platform.OS === 'web' ? { minWidth: 0, flex: 1 } : undefined, children: _jsx(TextInput, { ref: ref, className: cn(numericInputTextVariants({ size }), 'w-full web:outline-none', !unstyled && webAutofillField, inputClassName), style: numericInputTextStyle(visual, size, unstyled, styleProp), value: model.textValue, onChangeText: model.handleTextChange, onFocus: (event) => {
|
|
174
|
+
], children: [_jsx(NumericAffix, { side: "prefix", visual: visual, children: prefix }), _jsx(View, { className: "min-w-0 flex-1", style: Platform.OS === 'web' ? { minWidth: 0, flex: 1 } : undefined, children: _jsx(TextInput, { ref: ref, ...fieldAccessibilityProps(field, props), className: cn(numericInputTextVariants({ size }), 'w-full web:outline-none', !unstyled && webAutofillField, inputClassName), style: numericInputTextStyle(visual, size, unstyled, styleProp), value: model.textValue, onChangeText: model.handleTextChange, onFocus: (event) => {
|
|
175
175
|
setFocused(true);
|
|
176
176
|
onFocus?.(event);
|
|
177
177
|
}, onBlur: (event) => {
|
|
178
178
|
setFocused(false);
|
|
179
179
|
model.handleBlur(event);
|
|
180
|
-
}, keyboardType: "decimal-pad", editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...props }) }), _jsx(NumericAffix, { side: "suffix", visual: visual, children: suffix }), _jsx(NumericSteppers, { visible: showSteppers, inset: stepInset, size: stepSize, radius: stepRadius, model: model, interaction: interaction, visual: visual })] }));
|
|
180
|
+
}, keyboardType: "decimal-pad", editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...textCursorColors(colors), ...props }) }), _jsx(NumericAffix, { side: "suffix", visual: visual, children: suffix }), _jsx(NumericSteppers, { visible: showSteppers, inset: stepInset, size: stepSize, radius: stepRadius, model: model, interaction: interaction, visual: visual })] }));
|
|
181
181
|
});
|
|
182
182
|
NumericInput.displayName = 'NumericInput';
|
|
183
183
|
export { NumericInput, numericInputVariants, numericInputTextVariants };
|
|
@@ -34,14 +34,22 @@ declare const otpCellVariants: import("tailwind-variants").TVReturnType<{
|
|
|
34
34
|
};
|
|
35
35
|
}, undefined>>;
|
|
36
36
|
export interface OTPInputProps extends Omit<ViewProps, 'children'> {
|
|
37
|
+
/** Number of digit cells. */
|
|
37
38
|
length?: number;
|
|
39
|
+
/** Controlled value. */
|
|
38
40
|
value?: string;
|
|
41
|
+
/** Called on each digit change. */
|
|
39
42
|
onValueChange?: (value: string) => void;
|
|
43
|
+
/** Called when all digits are filled. */
|
|
40
44
|
onComplete?: (code: string) => void;
|
|
45
|
+
/** Error variant triggers shake. */
|
|
41
46
|
variant?: 'default' | 'error';
|
|
47
|
+
/** Cell size. */
|
|
42
48
|
size?: 'default' | 'compact';
|
|
49
|
+
/** Mask digits with dots. */
|
|
43
50
|
secure?: boolean;
|
|
44
51
|
disabled?: boolean;
|
|
52
|
+
/** Focus first cell on mount. */
|
|
45
53
|
autoFocus?: boolean;
|
|
46
54
|
className?: string;
|
|
47
55
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { Platform, StyleSheet } from 'react-native';
|
|
4
|
+
import { useComposedRefs } from '@rn-primitives/hooks';
|
|
4
5
|
import * as PopoverPrimitive from '@rn-primitives/popover';
|
|
5
6
|
import { cn } from '../../lib/cn';
|
|
7
|
+
import { useDismissKeyboardOnOpen } from '../../lib/dismiss-keyboard-on-open';
|
|
6
8
|
import { useFrostedSurface } from '../../lib/frosted-surface';
|
|
7
9
|
import { useNestedOverlay, useOverlayPortalTarget } from '../../lib/overlay-host';
|
|
8
10
|
import { useOverlayInsets } from '../../lib/overlay-insets';
|
|
@@ -12,7 +14,13 @@ import { shadowStyle, surfacePadding, surfaceRadius } from '../../lib/recipes';
|
|
|
12
14
|
function Popover({ onOpenChange, children }) {
|
|
13
15
|
return _jsx(PopoverPrimitive.Root, { onOpenChange: onOpenChange, children: children });
|
|
14
16
|
}
|
|
15
|
-
const PopoverTrigger = React.forwardRef(({ className, ...props }, ref) =>
|
|
17
|
+
const PopoverTrigger = React.forwardRef(({ className, ...props }, ref) => {
|
|
18
|
+
const { open, setTriggerPosition } = PopoverPrimitive.useRootContext();
|
|
19
|
+
const triggerRef = React.useRef(null);
|
|
20
|
+
const composedRef = useComposedRefs(triggerRef, ref);
|
|
21
|
+
useDismissKeyboardOnOpen(open, triggerRef, setTriggerPosition);
|
|
22
|
+
return _jsx(PopoverPrimitive.Trigger, { ref: composedRef, className: cn(className), ...props });
|
|
23
|
+
});
|
|
16
24
|
PopoverTrigger.displayName = 'PopoverTrigger';
|
|
17
25
|
const PopoverContent = React.forwardRef(({ className, align = 'center', sideOffset = 4, insets, portalHost, children, style, frosted = false, blurIntensity, blurTintToken, ...props }, ref) => {
|
|
18
26
|
const frostedSurface = useFrostedSurface({
|
|
@@ -2,6 +2,7 @@ import { type PressableInstance, type PressableProps as RNPressableProps } from
|
|
|
2
2
|
import { type DisabledVariant, type PressedVariant } from '../../lib/interaction-tokens';
|
|
3
3
|
import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
4
4
|
export interface PressableProps extends RNPressableProps {
|
|
5
|
+
/** Additional Tailwind classes. */
|
|
5
6
|
className?: string;
|
|
6
7
|
/** Semantic press feedback. `none` preserves a plain React Native Pressable. */
|
|
7
8
|
feedback?: PressedVariant;
|
|
@@ -9,6 +9,11 @@ declare const segmentedControlVariants: import("tailwind-variants").TVReturnType
|
|
|
9
9
|
true: "flex-wrap justify-center gap-1";
|
|
10
10
|
false: "flex-nowrap gap-1";
|
|
11
11
|
};
|
|
12
|
+
/** Sized to its segments inside a horizontal scroll view. */
|
|
13
|
+
scroll: {
|
|
14
|
+
true: "w-auto";
|
|
15
|
+
false: "";
|
|
16
|
+
};
|
|
12
17
|
size: {
|
|
13
18
|
default: "";
|
|
14
19
|
compact: "";
|
|
@@ -22,6 +27,11 @@ declare const segmentedControlVariants: import("tailwind-variants").TVReturnType
|
|
|
22
27
|
true: "flex-wrap justify-center gap-1";
|
|
23
28
|
false: "flex-nowrap gap-1";
|
|
24
29
|
};
|
|
30
|
+
/** Sized to its segments inside a horizontal scroll view. */
|
|
31
|
+
scroll: {
|
|
32
|
+
true: "w-auto";
|
|
33
|
+
false: "";
|
|
34
|
+
};
|
|
25
35
|
size: {
|
|
26
36
|
default: "";
|
|
27
37
|
compact: "";
|
|
@@ -35,6 +45,11 @@ declare const segmentedControlVariants: import("tailwind-variants").TVReturnType
|
|
|
35
45
|
true: "flex-wrap justify-center gap-1";
|
|
36
46
|
false: "flex-nowrap gap-1";
|
|
37
47
|
};
|
|
48
|
+
/** Sized to its segments inside a horizontal scroll view. */
|
|
49
|
+
scroll: {
|
|
50
|
+
true: "w-auto";
|
|
51
|
+
false: "";
|
|
52
|
+
};
|
|
38
53
|
size: {
|
|
39
54
|
default: "";
|
|
40
55
|
compact: "";
|
|
@@ -61,6 +76,11 @@ declare const segmentedControlItemVariants: import("tailwind-variants").TVReturn
|
|
|
61
76
|
true: "grow-0";
|
|
62
77
|
false: "flex-1";
|
|
63
78
|
};
|
|
79
|
+
/** As wide as its label, so segments overflow into the scroll view rather than shrink. */
|
|
80
|
+
scroll: {
|
|
81
|
+
true: "flex-none";
|
|
82
|
+
false: "";
|
|
83
|
+
};
|
|
64
84
|
}, undefined, "min-w-0 flex-row items-center justify-center", {
|
|
65
85
|
variant: {
|
|
66
86
|
default: "";
|
|
@@ -82,6 +102,11 @@ declare const segmentedControlItemVariants: import("tailwind-variants").TVReturn
|
|
|
82
102
|
true: "grow-0";
|
|
83
103
|
false: "flex-1";
|
|
84
104
|
};
|
|
105
|
+
/** As wide as its label, so segments overflow into the scroll view rather than shrink. */
|
|
106
|
+
scroll: {
|
|
107
|
+
true: "flex-none";
|
|
108
|
+
false: "";
|
|
109
|
+
};
|
|
85
110
|
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
86
111
|
variant: {
|
|
87
112
|
default: "";
|
|
@@ -103,6 +128,11 @@ declare const segmentedControlItemVariants: import("tailwind-variants").TVReturn
|
|
|
103
128
|
true: "grow-0";
|
|
104
129
|
false: "flex-1";
|
|
105
130
|
};
|
|
131
|
+
/** As wide as its label, so segments overflow into the scroll view rather than shrink. */
|
|
132
|
+
scroll: {
|
|
133
|
+
true: "flex-none";
|
|
134
|
+
false: "";
|
|
135
|
+
};
|
|
106
136
|
}, undefined>>;
|
|
107
137
|
declare const segmentedControlItemTextVariants: import("tailwind-variants").TVReturnType<{
|
|
108
138
|
selected: {
|
|
@@ -136,8 +166,16 @@ export interface SegmentedControlProps extends ViewProps {
|
|
|
136
166
|
value?: string;
|
|
137
167
|
defaultValue?: string;
|
|
138
168
|
onValueChange?: (value: string) => void;
|
|
169
|
+
/** Pill = rounded-full items. */
|
|
139
170
|
variant?: 'default' | 'pill';
|
|
140
|
-
|
|
171
|
+
/**
|
|
172
|
+
* `true` wraps segments onto more rows; `false` keeps one row and shares its width.
|
|
173
|
+
* `'scroll'` keeps one row with each segment as wide as its label and scrolls
|
|
174
|
+
* sideways when they do not fit — for labels a phone cannot show in full.
|
|
175
|
+
* `'auto'` wraps above three segments.
|
|
176
|
+
* @default 'auto'
|
|
177
|
+
*/
|
|
178
|
+
wrap?: boolean | 'auto' | 'scroll';
|
|
141
179
|
size?: 'default' | 'compact';
|
|
142
180
|
disabled?: boolean;
|
|
143
181
|
className?: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React, { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
|
3
|
-
import { Pressable, Text, View, } from 'react-native';
|
|
3
|
+
import { Pressable, ScrollView, Text, View, } from 'react-native';
|
|
4
4
|
import { cn } from '../../lib/cn';
|
|
5
5
|
import { focusRingInset } from '../../lib/focus-ring';
|
|
6
6
|
import { tv } from '../../lib/tv';
|
|
@@ -19,6 +19,11 @@ const segmentedControlVariants = tv({
|
|
|
19
19
|
true: 'flex-wrap justify-center gap-1',
|
|
20
20
|
false: 'flex-nowrap gap-1',
|
|
21
21
|
},
|
|
22
|
+
/** Sized to its segments inside a horizontal scroll view. */
|
|
23
|
+
scroll: {
|
|
24
|
+
true: 'w-auto',
|
|
25
|
+
false: '',
|
|
26
|
+
},
|
|
22
27
|
size: {
|
|
23
28
|
default: '',
|
|
24
29
|
compact: '',
|
|
@@ -27,6 +32,7 @@ const segmentedControlVariants = tv({
|
|
|
27
32
|
defaultVariants: {
|
|
28
33
|
variant: 'default',
|
|
29
34
|
wrap: true,
|
|
35
|
+
scroll: false,
|
|
30
36
|
size: 'default',
|
|
31
37
|
},
|
|
32
38
|
});
|
|
@@ -53,6 +59,11 @@ const segmentedControlItemVariants = tv({
|
|
|
53
59
|
true: 'grow-0',
|
|
54
60
|
false: 'flex-1',
|
|
55
61
|
},
|
|
62
|
+
/** As wide as its label, so segments overflow into the scroll view rather than shrink. */
|
|
63
|
+
scroll: {
|
|
64
|
+
true: 'flex-none',
|
|
65
|
+
false: '',
|
|
66
|
+
},
|
|
56
67
|
},
|
|
57
68
|
defaultVariants: {
|
|
58
69
|
variant: 'default',
|
|
@@ -60,6 +71,7 @@ const segmentedControlItemVariants = tv({
|
|
|
60
71
|
size: 'default',
|
|
61
72
|
disabled: false,
|
|
62
73
|
wrap: true,
|
|
74
|
+
scroll: false,
|
|
63
75
|
},
|
|
64
76
|
});
|
|
65
77
|
const segmentedControlItemTextVariants = tv({
|
|
@@ -105,6 +117,7 @@ const SegmentedControlContext = createContext({
|
|
|
105
117
|
variant: 'default',
|
|
106
118
|
size: 'default',
|
|
107
119
|
wrap: true,
|
|
120
|
+
scroll: false,
|
|
108
121
|
disabled: false,
|
|
109
122
|
});
|
|
110
123
|
const SegmentedControlItemContext = createContext({
|
|
@@ -117,7 +130,8 @@ function SegmentedControl({ value: controlledValue, defaultValue = '', onValueCh
|
|
|
117
130
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
118
131
|
const value = controlledValue ?? internalValue;
|
|
119
132
|
const itemCount = useMemo(() => React.Children.toArray(children).filter((child) => React.isValidElement(child)).length, [children]);
|
|
120
|
-
const
|
|
133
|
+
const scroll = wrap === 'scroll';
|
|
134
|
+
const shouldWrap = wrap === 'auto' ? itemCount > 3 : wrap === true;
|
|
121
135
|
const handleValueChange = useCallback((nextValue) => {
|
|
122
136
|
if (disabled || value === nextValue) {
|
|
123
137
|
return;
|
|
@@ -125,11 +139,20 @@ function SegmentedControl({ value: controlledValue, defaultValue = '', onValueCh
|
|
|
125
139
|
setInternalValue(nextValue);
|
|
126
140
|
onValueChange?.(nextValue);
|
|
127
141
|
}, [disabled, onValueChange, value]);
|
|
128
|
-
const contextValue = useMemo(() => ({
|
|
129
|
-
|
|
142
|
+
const contextValue = useMemo(() => ({
|
|
143
|
+
value,
|
|
144
|
+
onValueChange: handleValueChange,
|
|
145
|
+
variant,
|
|
146
|
+
size,
|
|
147
|
+
wrap: shouldWrap,
|
|
148
|
+
scroll,
|
|
149
|
+
disabled,
|
|
150
|
+
}), [disabled, handleValueChange, scroll, shouldWrap, size, value, variant]);
|
|
151
|
+
const group = (_jsx(View, { className: cn(segmentedControlVariants({ variant, wrap: shouldWrap, scroll, size }), className), style: [getSegmentedContainerStyle(visual, colors, variant), styleProp], accessibilityRole: "radiogroup", ...props, children: children }));
|
|
152
|
+
return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: scroll ? (_jsx(ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, children: group })) : (group) }));
|
|
130
153
|
}
|
|
131
154
|
function SegmentedControlItem({ value: itemValue, className, children, disabled: itemDisabled, style: styleProp, ...props }) {
|
|
132
|
-
const { value, onValueChange, variant, size, wrap, disabled } = useContext(SegmentedControlContext);
|
|
155
|
+
const { value, onValueChange, variant, size, wrap, scroll, disabled } = useContext(SegmentedControlContext);
|
|
133
156
|
const selected = value === itemValue;
|
|
134
157
|
const finalDisabled = disabled || Boolean(itemDisabled);
|
|
135
158
|
const interaction = useInteractionTokens();
|
|
@@ -150,6 +173,7 @@ function SegmentedControlItem({ value: itemValue, className, children, disabled:
|
|
|
150
173
|
size,
|
|
151
174
|
disabled: finalDisabled,
|
|
152
175
|
wrap,
|
|
176
|
+
scroll,
|
|
153
177
|
}), focusRingInset, className), onPress: () => onValueChange(itemValue), accessibilityRole: "radio",
|
|
154
178
|
// A radio is checked, not selected. `selected` maps to `aria-selected`, which is
|
|
155
179
|
// not a valid attribute on `role="radio"` — assistive technology could not tell
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { Platform, ScrollView, StyleSheet, useWindowDimensions, View, } from 'react-native';
|
|
4
|
+
import { useComposedRefs } from '@rn-primitives/hooks';
|
|
4
5
|
import * as SelectPrimitive from '@rn-primitives/select';
|
|
5
6
|
import { FullWindowOverlay as RNFullWindowOverlay } from 'react-native-screens';
|
|
6
7
|
import { Text } from '../text';
|
|
7
8
|
import { cn } from '../../lib/cn';
|
|
9
|
+
import { useDismissKeyboardOnOpen } from '../../lib/dismiss-keyboard-on-open';
|
|
8
10
|
import { focusRing } from '../../lib/focus-ring';
|
|
9
11
|
import { useInteractionTokens } from '../../lib/interaction-tokens';
|
|
10
12
|
import { useFrostedSurface } from '../../lib/frosted-surface';
|
|
@@ -26,7 +28,11 @@ const SelectTrigger = React.forwardRef(({ className, children, style: styleProp,
|
|
|
26
28
|
const visual = useVisualTokens();
|
|
27
29
|
const field = useFormFieldState();
|
|
28
30
|
const effectiveDisabled = disabled ?? field.disabled;
|
|
29
|
-
|
|
31
|
+
const { open, setTriggerPosition } = SelectPrimitive.useRootContext();
|
|
32
|
+
const triggerRef = React.useRef(null);
|
|
33
|
+
const composedRef = useComposedRefs(triggerRef, ref);
|
|
34
|
+
useDismissKeyboardOnOpen(open, triggerRef, setTriggerPosition);
|
|
35
|
+
return (_jsxs(SelectPrimitive.Trigger, { ref: composedRef, className: cn('border-border bg-muted flex w-full flex-row items-center justify-between gap-2 shadow-sm active:bg-state-pressed', field.invalid && 'border-destructive', focusRing, effectiveDisabled && '', className),
|
|
30
36
|
// Flattened, not an array. On web this primitive is merged through a Slot that
|
|
31
37
|
// spreads `style`, and spreading an array yields { 0: …, 1: … } — numeric keys
|
|
32
38
|
// that reach the DOM as indexed style properties and throw. The Select rendered
|
|
@@ -4,10 +4,15 @@ import { type ShadowTier, type SurfacePaddingSize } from '../../lib/recipes';
|
|
|
4
4
|
import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
5
5
|
export type SurfaceLevel = 'base' | 'raised' | 'overlay';
|
|
6
6
|
export interface SurfaceProps extends ViewProps, FrostedSurfaceProps {
|
|
7
|
+
/** Semantic background and depth tier. */
|
|
7
8
|
level?: SurfaceLevel;
|
|
9
|
+
/** Token-backed interior spacing. */
|
|
8
10
|
padding?: SurfacePaddingSize;
|
|
11
|
+
/** Token-backed corner radius. */
|
|
9
12
|
radius?: 'sm' | 'md' | 'lg' | 'xl';
|
|
13
|
+
/** Draw the theme hairline border. */
|
|
10
14
|
bordered?: boolean;
|
|
15
|
+
/** Override the level's depth. */
|
|
11
16
|
shadow?: ShadowTier;
|
|
12
17
|
}
|
|
13
18
|
declare const Surface: ForwardRefComponent<ViewInstance, SurfaceProps>;
|
|
@@ -7,9 +7,9 @@ import { tv } from '../../lib/tv';
|
|
|
7
7
|
import { useThemeColors } from '../../lib/theme-colors';
|
|
8
8
|
import { useInteractionTokens } from '../../lib/interaction-tokens';
|
|
9
9
|
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
10
|
-
import { typographyStyle } from '../../lib/recipes';
|
|
10
|
+
import { textCursorColors, typographyStyle } from '../../lib/recipes';
|
|
11
11
|
import { webAutofillField } from '../../lib/web-autofill';
|
|
12
|
-
import { useFormFieldState } from '../form-field/form-field';
|
|
12
|
+
import { fieldAccessibilityProps, useFormFieldState } from '../form-field/form-field';
|
|
13
13
|
const textareaVariants = tv({
|
|
14
14
|
base: 'w-full border-border bg-muted text-foreground',
|
|
15
15
|
variants: {
|
|
@@ -48,7 +48,7 @@ const Textarea = React.forwardRef(({ className, size = 'default', placeholderTex
|
|
|
48
48
|
paddingVertical: visual.controlPaddingYDefault,
|
|
49
49
|
...(!effectiveEditable ? { opacity: interaction.disabledOpacity } : null),
|
|
50
50
|
};
|
|
51
|
-
return (_jsx(TextInput, { ref: ref, className: cn(textareaVariants({ size }), field.invalid && 'border-destructive', focusField, webAutofillField, className), style: style, multiline: true, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, ...props }));
|
|
51
|
+
return (_jsx(TextInput, { ref: ref, ...fieldAccessibilityProps(field, props), className: cn(textareaVariants({ size }), field.invalid && 'border-destructive', focusField, webAutofillField, className), style: style, multiline: true, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, ...textCursorColors(colors), editable: effectiveEditable, "aria-invalid": ariaInvalid ?? field.invalid, ...props }));
|
|
52
52
|
});
|
|
53
53
|
Textarea.displayName = 'Textarea';
|
|
54
54
|
export { Textarea, textareaVariants };
|
|
@@ -25,21 +25,21 @@ export interface ToastProviderProps {
|
|
|
25
25
|
export declare function ToastProvider({ children }: ToastProviderProps): React.JSX.Element;
|
|
26
26
|
declare const toastVariants: import("tailwind-variants").TVReturnType<{
|
|
27
27
|
variant: {
|
|
28
|
-
default: "border-border
|
|
29
|
-
destructive: "border-destructive
|
|
30
|
-
success: "border-success
|
|
28
|
+
default: "border-border";
|
|
29
|
+
destructive: "border-destructive-soft-border";
|
|
30
|
+
success: "border-success-soft-border";
|
|
31
31
|
};
|
|
32
|
-
}, undefined, "w-full flex-row items-center
|
|
32
|
+
}, undefined, "w-full flex-row items-center gap-3 border bg-popover", {
|
|
33
33
|
variant: {
|
|
34
|
-
default: "border-border
|
|
35
|
-
destructive: "border-destructive
|
|
36
|
-
success: "border-success
|
|
34
|
+
default: "border-border";
|
|
35
|
+
destructive: "border-destructive-soft-border";
|
|
36
|
+
success: "border-success-soft-border";
|
|
37
37
|
};
|
|
38
38
|
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
39
39
|
variant: {
|
|
40
|
-
default: "border-border
|
|
41
|
-
destructive: "border-destructive
|
|
42
|
-
success: "border-success
|
|
40
|
+
default: "border-border";
|
|
41
|
+
destructive: "border-destructive-soft-border";
|
|
42
|
+
success: "border-success-soft-border";
|
|
43
43
|
};
|
|
44
44
|
}, undefined>>;
|
|
45
45
|
export interface ToastProps extends ViewProps, VariantProps<typeof toastVariants> {
|