@thewhileloop/whileui 2.0.0 → 4.0.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 +135 -70
- package/dist/blocks/commerce/metric-card.d.ts +9 -29
- package/dist/blocks/datepicker/date-range-picker-modal.d.ts +1 -1
- package/dist/blocks/datepicker/date-range-picker-modal.js +6 -14
- package/dist/blocks/datepicker/datepicker-modal.d.ts +1 -1
- package/dist/blocks/datepicker/datepicker-modal.js +6 -12
- package/dist/blocks/datepicker/index.d.ts +1 -0
- package/dist/blocks/datepicker/index.js +1 -0
- package/dist/blocks/datepicker/time-picker-modal.d.ts +43 -0
- package/dist/blocks/datepicker/time-picker-modal.js +187 -0
- package/dist/blocks/layout/smart-input.d.ts +3 -2
- package/dist/blocks/layout/smart-input.js +33 -18
- package/dist/blocks/media/smart-image.d.ts +39 -98
- package/dist/blocks/navigation/drawer-menu.js +88 -72
- package/dist/components/alert/alert.d.ts +23 -55
- package/dist/components/alert/alert.js +1 -1
- package/dist/components/alert-dialog/alert-dialog.js +1 -4
- package/dist/components/avatar/avatar.d.ts +18 -45
- package/dist/components/avatar/avatar.js +1 -1
- package/dist/components/badge/badge.d.ts +84 -212
- package/dist/components/button/button.d.ts +69 -188
- package/dist/components/card/card.d.ts +29 -73
- package/dist/components/card/card.js +1 -1
- package/dist/components/container/container.d.ts +24 -96
- package/dist/components/data-row/data-row.d.ts +18 -38
- package/dist/components/data-row/data-row.js +1 -1
- package/dist/components/fab/fab.d.ts +87 -0
- package/dist/components/fab/fab.js +59 -0
- package/dist/components/fab/index.d.ts +1 -0
- package/dist/components/fab/index.js +1 -0
- package/dist/components/form-field/form-field.d.ts +31 -87
- package/dist/components/form-field/form-field.js +1 -1
- package/dist/components/hover-card/hover-card.d.ts +3 -2
- package/dist/components/input/input.d.ts +18 -31
- package/dist/components/input/input.js +20 -8
- package/dist/components/label/label.d.ts +3 -3
- package/dist/components/labeled-field/labeled-field.d.ts +3 -2
- package/dist/components/numeric-input/numeric-input.d.ts +27 -82
- package/dist/components/numeric-input/numeric-input.js +79 -80
- package/dist/components/otp-input/otp-input.d.ts +21 -65
- package/dist/components/popover/popover.d.ts +4 -3
- package/dist/components/pressable/pressable.d.ts +3 -3
- package/dist/components/progress/progress.d.ts +15 -39
- package/dist/components/row/row.d.ts +63 -163
- package/dist/components/segmented-control/segmented-control.d.ts +69 -241
- package/dist/components/select/select.d.ts +10 -22
- package/dist/components/select/select.js +2 -7
- package/dist/components/separator/separator.d.ts +3 -3
- package/dist/components/skeleton/skeleton.d.ts +9 -29
- package/dist/components/spinner/spinner.d.ts +12 -36
- package/dist/components/stack/stack.d.ts +63 -163
- package/dist/components/tabs/tabs.d.ts +9 -29
- package/dist/components/text/text.d.ts +51 -131
- package/dist/components/textarea/textarea.d.ts +15 -39
- package/dist/components/textarea/textarea.js +3 -3
- package/dist/components/toast/toast.d.ts +12 -36
- package/dist/components/toggle/toggle.d.ts +42 -138
- package/dist/components/toggle-group/toggle-group.d.ts +42 -138
- package/dist/components/tooltip/tooltip.d.ts +3 -2
- package/dist/components/view/view.d.ts +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/focus-ring.d.ts +10 -0
- package/dist/lib/focus-ring.js +10 -0
- package/dist/lib/interaction-tokens.d.ts +24 -1
- package/dist/lib/interaction-tokens.js +26 -44
- package/dist/lib/motion-policy.js +2 -2
- package/dist/lib/ref-types.d.ts +3 -0
- package/dist/lib/ref-types.js +1 -0
- package/dist/lib/theme-bridge.d.ts +4 -2
- package/dist/lib/theme-bridge.js +13 -9
- package/dist/lib/theme-presets.d.ts +26 -0
- package/dist/lib/theme-presets.js +28 -0
- package/package.json +29 -28
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Modal, Platform, Pressable, ScrollView, View, } from 'react-native';
|
|
4
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
|
+
import { Text } from '../../components/text';
|
|
6
|
+
import { Button, ButtonText } from '../../components/button';
|
|
7
|
+
import { cn } from '../../lib/cn';
|
|
8
|
+
import { focusRingInset } from '../../lib/focus-ring';
|
|
9
|
+
import { useThemeColors } from '../../lib/theme-colors';
|
|
10
|
+
import { useVisualTokens } from '../../lib/visual-tokens';
|
|
11
|
+
import { fieldRecipe, surfacePadding, surfaceRadius, typographyStyle } from '../../lib/recipes';
|
|
12
|
+
import { useFrostedSurface } from '../../lib/frosted-surface';
|
|
13
|
+
const ITEM_HEIGHT = 44;
|
|
14
|
+
/** Rows visible in a wheel. Odd, so one row sits centred under the selection band. */
|
|
15
|
+
const VISIBLE_ROWS = 5;
|
|
16
|
+
const WHEEL_HEIGHT = ITEM_HEIGHT * VISIBLE_ROWS;
|
|
17
|
+
const WHEEL_PADDING = ITEM_HEIGHT * Math.floor(VISIBLE_ROWS / 2);
|
|
18
|
+
const pad2 = (n) => String(n).padStart(2, '0');
|
|
19
|
+
/** The locale's clock, so a US device gets AM/PM and most of the world does not. */
|
|
20
|
+
function localeHourCycle() {
|
|
21
|
+
try {
|
|
22
|
+
const cycle = new Intl.DateTimeFormat(undefined, { hour: 'numeric' }).resolvedOptions()
|
|
23
|
+
.hourCycle;
|
|
24
|
+
return cycle === 'h11' || cycle === 'h12' ? 'h12' : 'h23';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return 'h23';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function parseTime(value) {
|
|
31
|
+
if (!value)
|
|
32
|
+
return null;
|
|
33
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
|
|
34
|
+
if (!match)
|
|
35
|
+
return null;
|
|
36
|
+
const hour = Number(match[1]);
|
|
37
|
+
const minute = Number(match[2]);
|
|
38
|
+
if (hour > 23 || minute > 59)
|
|
39
|
+
return null;
|
|
40
|
+
return { hour, minute };
|
|
41
|
+
}
|
|
42
|
+
/** `HH:MM` as the locale writes it — "10:30 PM" or "22:30". */
|
|
43
|
+
function formatDisplayTime(value, hourCycle) {
|
|
44
|
+
const parsed = parseTime(value);
|
|
45
|
+
if (!parsed)
|
|
46
|
+
return 'Select time';
|
|
47
|
+
const date = new Date(2000, 0, 1, parsed.hour, parsed.minute);
|
|
48
|
+
return date.toLocaleTimeString(undefined, {
|
|
49
|
+
hour: 'numeric',
|
|
50
|
+
minute: '2-digit',
|
|
51
|
+
...(hourCycle ? { hour12: hourCycle === 'h12' } : {}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One scrolling column. Scrolls snap to a row, a tap selects a row, and the whole
|
|
56
|
+
* column is a single adjustable element for assistive tech and the keyboard — sixty
|
|
57
|
+
* minute rows as sixty tab stops would be nobody's idea of operable.
|
|
58
|
+
*/
|
|
59
|
+
function Wheel({ label, options, selected, onSelect }) {
|
|
60
|
+
const visual = useVisualTokens();
|
|
61
|
+
const colors = useThemeColors();
|
|
62
|
+
const ref = useRef(null);
|
|
63
|
+
const index = Math.max(0, options.findIndex((option) => option.value === selected));
|
|
64
|
+
// Keep the band on the selected row when the value changes from outside the wheel
|
|
65
|
+
// (a tap, a keyboard step, the initial value), not after the user's own scroll.
|
|
66
|
+
const lastScrolledIndex = useRef(-1);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (lastScrolledIndex.current === index)
|
|
69
|
+
return;
|
|
70
|
+
lastScrolledIndex.current = index;
|
|
71
|
+
ref.current?.scrollTo({ y: index * ITEM_HEIGHT, animated: Platform.OS !== 'web' });
|
|
72
|
+
}, [index]);
|
|
73
|
+
const settle = useCallback((event) => {
|
|
74
|
+
const next = Math.round(event.nativeEvent.contentOffset.y / ITEM_HEIGHT);
|
|
75
|
+
const clamped = Math.min(options.length - 1, Math.max(0, next));
|
|
76
|
+
lastScrolledIndex.current = clamped;
|
|
77
|
+
const option = options[clamped];
|
|
78
|
+
if (option && option.value !== selected)
|
|
79
|
+
onSelect(option.value);
|
|
80
|
+
}, [onSelect, options, selected]);
|
|
81
|
+
const step = useCallback((delta) => {
|
|
82
|
+
const next = (index + delta + options.length) % options.length;
|
|
83
|
+
onSelect(options[next].value);
|
|
84
|
+
}, [index, onSelect, options]);
|
|
85
|
+
const current = options[index]?.label ?? '';
|
|
86
|
+
return (_jsxs(View, { accessible: true, accessibilityRole: "adjustable", accessibilityLabel: label, accessibilityValue: { text: current }, accessibilityActions: [{ name: 'increment' }, { name: 'decrement' }], onAccessibilityAction: (event) => {
|
|
87
|
+
if (event.nativeEvent.actionName === 'increment')
|
|
88
|
+
step(1);
|
|
89
|
+
if (event.nativeEvent.actionName === 'decrement')
|
|
90
|
+
step(-1);
|
|
91
|
+
}, ...(Platform.OS === 'web'
|
|
92
|
+
? {
|
|
93
|
+
focusable: true,
|
|
94
|
+
onKeyDown: (event) => {
|
|
95
|
+
if (event.nativeEvent.key === 'ArrowUp') {
|
|
96
|
+
event.preventDefault();
|
|
97
|
+
step(-1);
|
|
98
|
+
}
|
|
99
|
+
else if (event.nativeEvent.key === 'ArrowDown') {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
step(1);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
: null), className: cn('flex-1 overflow-hidden', focusRingInset), style: { height: WHEEL_HEIGHT, borderRadius: surfaceRadius(visual, 'lg') }, children: [_jsx(View, { pointerEvents: "none", className: "absolute left-0 right-0 bg-muted", style: {
|
|
106
|
+
top: WHEEL_PADDING,
|
|
107
|
+
height: ITEM_HEIGHT,
|
|
108
|
+
borderRadius: surfaceRadius(visual, 'md'),
|
|
109
|
+
} }), _jsx(ScrollView, { ref: ref, showsVerticalScrollIndicator: false, snapToInterval: ITEM_HEIGHT, decelerationRate: "fast", nestedScrollEnabled: true, contentContainerStyle: { paddingVertical: WHEEL_PADDING }, contentOffset: { x: 0, y: index * ITEM_HEIGHT }, onMomentumScrollEnd: settle, onScrollEndDrag: Platform.OS === 'web' ? settle : undefined, importantForAccessibility: "no-hide-descendants", accessibilityElementsHidden: true, children: options.map((option) => {
|
|
110
|
+
const on = option.value === selected;
|
|
111
|
+
return (_jsx(Pressable, { onPress: () => onSelect(option.value), focusable: false, className: "items-center justify-center", style: { height: ITEM_HEIGHT }, children: _jsx(Text, { className: on ? 'font-medium text-foreground' : 'text-muted-foreground', style: {
|
|
112
|
+
...typographyStyle(visual, on ? 'title' : 'body'),
|
|
113
|
+
color: on ? colors.foreground : colors.mutedForeground,
|
|
114
|
+
}, children: option.label }) }, option.value));
|
|
115
|
+
}) })] }));
|
|
116
|
+
}
|
|
117
|
+
function TimePickerModalSession({ value, onValueChange, open, onOpenChange, minuteStep = 1, hourCycle: hourCycleProp, trigger, title = 'Select time', confirmLabel = 'Done', className, frosted = false, blurIntensity, blurTintToken, }) {
|
|
118
|
+
const insets = useSafeAreaInsets();
|
|
119
|
+
const colors = useThemeColors();
|
|
120
|
+
const visual = useVisualTokens();
|
|
121
|
+
const frostedSurface = useFrostedSurface({
|
|
122
|
+
frosted,
|
|
123
|
+
blurIntensity,
|
|
124
|
+
blurTintToken,
|
|
125
|
+
defaultTintToken: 'surfaceTranslucent',
|
|
126
|
+
defaultBlurPreset: 'medium',
|
|
127
|
+
});
|
|
128
|
+
const hourCycle = hourCycleProp ?? localeHourCycle();
|
|
129
|
+
const step = Math.min(30, Math.max(1, Math.round(minuteStep)));
|
|
130
|
+
// The draft opens on the current value, or on the current clock time rounded to the
|
|
131
|
+
// step, so the first confirm without touching anything records "now".
|
|
132
|
+
const initial = (() => {
|
|
133
|
+
const parsed = parseTime(value);
|
|
134
|
+
if (parsed)
|
|
135
|
+
return parsed;
|
|
136
|
+
const now = new Date();
|
|
137
|
+
return { hour: now.getHours(), minute: Math.floor(now.getMinutes() / step) * step };
|
|
138
|
+
})();
|
|
139
|
+
const [hour, setHour] = useState(initial.hour);
|
|
140
|
+
const [minute, setMinute] = useState(initial.minute);
|
|
141
|
+
const hours = hourCycle === 'h12'
|
|
142
|
+
? Array.from({ length: 12 }, (_, i) => ({ value: i + 1, label: String(i + 1) }))
|
|
143
|
+
: Array.from({ length: 24 }, (_, i) => ({ value: i, label: pad2(i) }));
|
|
144
|
+
const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => ({
|
|
145
|
+
value: i * step,
|
|
146
|
+
label: pad2(i * step),
|
|
147
|
+
}));
|
|
148
|
+
const periods = [
|
|
149
|
+
{ value: 0, label: 'AM' },
|
|
150
|
+
{ value: 1, label: 'PM' },
|
|
151
|
+
];
|
|
152
|
+
const period = hour >= 12 ? 1 : 0;
|
|
153
|
+
const hour12 = hour % 12 === 0 ? 12 : hour % 12;
|
|
154
|
+
const handleConfirm = useCallback(() => {
|
|
155
|
+
onValueChange?.(`${pad2(hour)}:${pad2(minute)}`);
|
|
156
|
+
onOpenChange(false);
|
|
157
|
+
}, [hour, minute, onOpenChange, onValueChange]);
|
|
158
|
+
const handleDismiss = useCallback(() => onOpenChange(false), [onOpenChange]);
|
|
159
|
+
const handleOpen = useCallback(() => onOpenChange(true), [onOpenChange]);
|
|
160
|
+
return (_jsxs(_Fragment, { children: [trigger ? (_jsx(Pressable, { accessibilityRole: "button", onPress: handleOpen, className: cn('w-full flex-row items-center border-border bg-muted', className), style: fieldRecipe(visual, 'default'), children: trigger })) : null, _jsx(Modal, { visible: open, transparent: true, animationType: "slide", statusBarTranslucent: true, navigationBarTranslucent: true, onRequestClose: handleDismiss, children: _jsx(Pressable, { accessible: false, importantForAccessibility: "no", className: "flex-1 justify-end", style: { backgroundColor: colors.overlay }, onPress: handleDismiss, children: _jsxs(Pressable, { accessible: false, className: cn('border border-border relative overflow-hidden', frosted ? 'bg-transparent' : 'bg-background'), style: [
|
|
161
|
+
{
|
|
162
|
+
borderTopLeftRadius: surfaceRadius(visual, 'xl'),
|
|
163
|
+
borderTopRightRadius: surfaceRadius(visual, 'xl'),
|
|
164
|
+
paddingBottom: Math.max(insets.bottom, surfacePadding(visual, 'sm')),
|
|
165
|
+
},
|
|
166
|
+
frostedSurface.surfaceStyle,
|
|
167
|
+
], onPress: (e) => e.stopPropagation(), children: [frostedSurface.overlay, _jsxs(View, { className: "flex-row items-center justify-between border-b border-border", style: {
|
|
168
|
+
paddingHorizontal: surfacePadding(visual, 'sm'),
|
|
169
|
+
paddingVertical: visual.controlPaddingYDefault,
|
|
170
|
+
}, children: [_jsx(Text, { accessibilityRole: "header", className: "font-medium text-foreground", style: typographyStyle(visual, 'body'), children: title }), _jsx(Button, { size: "sm", onPress: handleConfirm, children: _jsx(ButtonText, { children: confirmLabel }) })] }), _jsxs(View, { className: "flex-row items-center", style: { padding: surfacePadding(visual, 'sm'), gap: visual.controlPaddingYDefault }, children: [_jsx(Wheel, { label: "Hour", options: hours, selected: hourCycle === 'h12' ? hour12 : hour, onSelect: (next) => setHour(hourCycle === 'h12' ? (next % 12) + (period === 1 ? 12 : 0) : next) }), _jsx(Text, { className: "text-muted-foreground", style: typographyStyle(visual, 'title'), accessibilityElementsHidden: true, importantForAccessibility: "no", children: ":" }), _jsx(Wheel, { label: "Minute", options: minutes, selected: minute, onSelect: setMinute }), hourCycle === 'h12' ? (_jsx(Wheel, { label: "AM or PM", options: periods, selected: period, onSelect: (next) => setHour((hour % 12) + (next === 1 ? 12 : 0)) })) : null] })] }) }) })] }));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* A time chosen on two wheels — three with AM/PM — in the same bottom sheet as
|
|
174
|
+
* `DatePickerModal`, so a form's date and time fields open the same way.
|
|
175
|
+
*/
|
|
176
|
+
export function TimePickerModal(props) {
|
|
177
|
+
// Remount per opening so the draft starts from the value each time, as the date
|
|
178
|
+
// picker does; a draft that outlives a cancel would reopen on the abandoned pick.
|
|
179
|
+
const sessionKey = props.open ? 'open' : 'closed';
|
|
180
|
+
return _jsx(TimePickerModalSession, { ...props }, sessionKey);
|
|
181
|
+
}
|
|
182
|
+
/** Compact trigger showing the formatted time. Use as TimePickerModal's trigger prop. */
|
|
183
|
+
export function TimePickerTrigger({ value, placeholder = 'Select time', hourCycle, className, }) {
|
|
184
|
+
const visual = useVisualTokens();
|
|
185
|
+
return (_jsxs(View, { className: cn('flex-1 flex-row items-center min-w-0', className), children: [_jsx(Text, { className: cn('flex-1', value ? 'text-foreground' : 'text-muted-foreground'), style: typographyStyle(visual, 'body'), numberOfLines: 1, children: value ? formatDisplayTime(value, hourCycle) : placeholder }), _jsx(View, { className: "ml-2 shrink-0 h-2 w-2 rotate-45 border-b-2 border-r-2 border-muted-foreground" })] }));
|
|
186
|
+
}
|
|
187
|
+
export { formatDisplayTime, parseTime };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { type TextInputInstance, type TextInputProps } from 'react-native';
|
|
3
|
+
import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
3
4
|
export interface SmartInputProps extends TextInputProps {
|
|
4
5
|
/** Left slot: emoji, attach, etc. */
|
|
5
6
|
leftSlot?: React.ReactNode;
|
|
@@ -24,4 +25,4 @@ export interface SmartInputProps extends TextInputProps {
|
|
|
24
25
|
placeholderPauseDuration?: number;
|
|
25
26
|
placeholderClearInterval?: number;
|
|
26
27
|
}
|
|
27
|
-
export declare const SmartInput:
|
|
28
|
+
export declare const SmartInput: ForwardRefComponent<TextInputInstance, SmartInputProps>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { AccessibilityInfo, View, TextInput } from 'react-native';
|
|
3
|
+
import { AccessibilityInfo, View, TextInput, } from 'react-native';
|
|
4
4
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
5
5
|
import { cn } from '../../lib/cn';
|
|
6
6
|
import { focusRing } from '../../lib/focus-ring';
|
|
@@ -79,6 +79,30 @@ function useRotatingPlaceholder({ active, examplesKey, prefix, typingInterval, p
|
|
|
79
79
|
}
|
|
80
80
|
return animatedPlaceholder || prefix;
|
|
81
81
|
}
|
|
82
|
+
function useSmartInputPlaceholder({ value, defaultValue, placeholder, examples, prefix, typingInterval, pauseDuration, clearInterval, onChangeText, }) {
|
|
83
|
+
const [uncontrolledHasText, setUncontrolledHasText] = useState(() => Boolean(defaultValue));
|
|
84
|
+
const hasText = value !== undefined ? Boolean(value) : uncontrolledHasText;
|
|
85
|
+
const examplesKey = examples?.join(SEPARATOR) ?? '';
|
|
86
|
+
const animatedPlaceholder = useRotatingPlaceholder({
|
|
87
|
+
active: placeholder === undefined && !hasText && Boolean(examplesKey),
|
|
88
|
+
examplesKey,
|
|
89
|
+
prefix,
|
|
90
|
+
typingInterval,
|
|
91
|
+
pauseDuration,
|
|
92
|
+
clearInterval,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
placeholder: placeholder ?? (hasText || !examplesKey ? undefined : animatedPlaceholder),
|
|
96
|
+
onChangeText: (text) => {
|
|
97
|
+
if (value === undefined)
|
|
98
|
+
setUncontrolledHasText(Boolean(text));
|
|
99
|
+
onChangeText?.(text);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function SmartInputSlot({ children }) {
|
|
104
|
+
return children ? _jsx(View, { className: "shrink-0", children: children }) : null;
|
|
105
|
+
}
|
|
82
106
|
export const SmartInput = forwardRef(function SmartInput({ leftSlot, centerSlot, rightSlot, variant = 'bar', className, inputClassName, submitBehavior = 'newline', safeArea = true, placeholderExamples, placeholderPrefix = 'e.g. ', placeholderTypingInterval = 65, placeholderPauseDuration = 2200, placeholderClearInterval = 25, placeholderTextColor, placeholder, value, defaultValue, onChangeText, editable = true, style, ...props }, ref) {
|
|
83
107
|
const insets = useSafeAreaInsets();
|
|
84
108
|
const colors = useThemeColors();
|
|
@@ -87,22 +111,17 @@ export const SmartInput = forwardRef(function SmartInput({ leftSlot, centerSlot,
|
|
|
87
111
|
const bottomPadding = safeArea
|
|
88
112
|
? Math.max(insets.bottom, visual.controlPaddingYSm)
|
|
89
113
|
: visual.controlPaddingYSm;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const examplesKey = placeholderExamples?.join(SEPARATOR) ?? '';
|
|
96
|
-
const animatePlaceholder = placeholder === undefined && !hasText && Boolean(examplesKey);
|
|
97
|
-
const animatedPlaceholder = useRotatingPlaceholder({
|
|
98
|
-
active: animatePlaceholder,
|
|
99
|
-
examplesKey,
|
|
114
|
+
const placeholderState = useSmartInputPlaceholder({
|
|
115
|
+
value,
|
|
116
|
+
defaultValue,
|
|
117
|
+
placeholder,
|
|
118
|
+
examples: placeholderExamples,
|
|
100
119
|
prefix: placeholderPrefix,
|
|
101
120
|
typingInterval: placeholderTypingInterval,
|
|
102
121
|
pauseDuration: placeholderPauseDuration,
|
|
103
122
|
clearInterval: placeholderClearInterval,
|
|
123
|
+
onChangeText,
|
|
104
124
|
});
|
|
105
|
-
const resolvedPlaceholder = placeholder ?? (hasText || !examplesKey ? undefined : animatedPlaceholder);
|
|
106
125
|
return (_jsxs(View, { className: cn('w-full flex-row items-center gap-2 bg-background', variant === 'bar' && 'border-border', variant === 'card' && 'border-border', className), style: [
|
|
107
126
|
{
|
|
108
127
|
paddingHorizontal: visual.controlPaddingXSm,
|
|
@@ -117,16 +136,12 @@ export const SmartInput = forwardRef(function SmartInput({ leftSlot, centerSlot,
|
|
|
117
136
|
...shadowStyle(visual, colors, 'md'),
|
|
118
137
|
}
|
|
119
138
|
: null,
|
|
120
|
-
], children: [
|
|
139
|
+
], children: [_jsx(SmartInputSlot, { children: leftSlot }), _jsx(SmartInputSlot, { children: centerSlot }), _jsx(TextInput, { ref: ref, style: [
|
|
121
140
|
controlRecipe(visual, 'default'),
|
|
122
141
|
typographyStyle(visual, 'label'),
|
|
123
142
|
variant === 'bar' ? { borderWidth: visual.borderWidthControl } : null,
|
|
124
143
|
{ maxHeight: visual.smartInputMaxHeight },
|
|
125
144
|
!editable ? { opacity: interaction.disabledOpacity } : null,
|
|
126
145
|
style,
|
|
127
|
-
], className: cn('flex-1 text-foreground placeholder:text-muted-foreground', focusRing, variant === 'bar' && 'border-border bg-muted', variant === 'card' && 'bg-transparent', inputClassName), multiline: true, submitBehavior: submitBehavior, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, placeholder:
|
|
128
|
-
if (value === undefined)
|
|
129
|
-
setUncontrolledHasText(Boolean(text));
|
|
130
|
-
onChangeText?.(text);
|
|
131
|
-
}, editable: editable, ...props }), rightSlot && _jsx(View, { className: "shrink-0", children: rightSlot })] }));
|
|
146
|
+
], className: cn('flex-1 text-foreground placeholder:text-muted-foreground', focusRing, variant === 'bar' && 'border-border bg-muted', variant === 'card' && 'bg-transparent', inputClassName), multiline: true, submitBehavior: submitBehavior, textAlignVertical: "top", placeholderTextColor: placeholderTextColor ?? colors.placeholder, placeholder: placeholderState.placeholder, value: value, defaultValue: defaultValue, onChangeText: placeholderState.onChangeText, editable: editable, ...props }), _jsx(SmartInputSlot, { children: rightSlot })] }));
|
|
132
147
|
});
|
|
@@ -1,122 +1,63 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { type ViewInstance, type ViewProps } from 'react-native';
|
|
2
|
+
import { type ImageProps } from 'expo-image';
|
|
3
3
|
import { type VariantProps } from '../../lib/tv';
|
|
4
|
+
import type { ForwardRefComponent } from '../../lib/ref-types';
|
|
4
5
|
declare const smartImageVariants: import("tailwind-variants").TVReturnType<{
|
|
5
6
|
radius: {
|
|
6
|
-
none:
|
|
7
|
-
sm:
|
|
8
|
-
md:
|
|
9
|
-
lg:
|
|
10
|
-
xl:
|
|
11
|
-
full:
|
|
7
|
+
none: "";
|
|
8
|
+
sm: "";
|
|
9
|
+
md: "";
|
|
10
|
+
lg: "";
|
|
11
|
+
xl: "";
|
|
12
|
+
full: "rounded-full";
|
|
12
13
|
};
|
|
13
14
|
objectFit: {
|
|
14
|
-
cover:
|
|
15
|
-
contain:
|
|
16
|
-
fill:
|
|
17
|
-
'scale-down':
|
|
15
|
+
cover: "object-cover";
|
|
16
|
+
contain: "object-contain";
|
|
17
|
+
fill: "object-fill";
|
|
18
|
+
'scale-down': "object-scale-down";
|
|
18
19
|
};
|
|
19
|
-
}, undefined, "overflow-hidden",
|
|
20
|
+
}, undefined, "overflow-hidden", {
|
|
20
21
|
radius: {
|
|
21
|
-
none:
|
|
22
|
-
sm:
|
|
23
|
-
md:
|
|
24
|
-
lg:
|
|
25
|
-
xl:
|
|
26
|
-
full:
|
|
22
|
+
none: "";
|
|
23
|
+
sm: "";
|
|
24
|
+
md: "";
|
|
25
|
+
lg: "";
|
|
26
|
+
xl: "";
|
|
27
|
+
full: "rounded-full";
|
|
27
28
|
};
|
|
28
29
|
objectFit: {
|
|
29
|
-
cover:
|
|
30
|
-
contain:
|
|
31
|
-
fill:
|
|
32
|
-
'scale-down':
|
|
30
|
+
cover: "object-cover";
|
|
31
|
+
contain: "object-contain";
|
|
32
|
+
fill: "object-fill";
|
|
33
|
+
'scale-down': "object-scale-down";
|
|
33
34
|
};
|
|
34
|
-
}, {
|
|
35
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
35
36
|
radius: {
|
|
36
|
-
none:
|
|
37
|
-
sm:
|
|
38
|
-
md:
|
|
39
|
-
lg:
|
|
40
|
-
xl:
|
|
41
|
-
full:
|
|
37
|
+
none: "";
|
|
38
|
+
sm: "";
|
|
39
|
+
md: "";
|
|
40
|
+
lg: "";
|
|
41
|
+
xl: "";
|
|
42
|
+
full: "rounded-full";
|
|
42
43
|
};
|
|
43
44
|
objectFit: {
|
|
44
|
-
cover:
|
|
45
|
-
contain:
|
|
46
|
-
fill:
|
|
47
|
-
'scale-down':
|
|
45
|
+
cover: "object-cover";
|
|
46
|
+
contain: "object-contain";
|
|
47
|
+
fill: "object-fill";
|
|
48
|
+
'scale-down': "object-scale-down";
|
|
48
49
|
};
|
|
49
|
-
}
|
|
50
|
-
radius: {
|
|
51
|
-
none: string;
|
|
52
|
-
sm: string;
|
|
53
|
-
md: string;
|
|
54
|
-
lg: string;
|
|
55
|
-
xl: string;
|
|
56
|
-
full: string;
|
|
57
|
-
};
|
|
58
|
-
objectFit: {
|
|
59
|
-
cover: string;
|
|
60
|
-
contain: string;
|
|
61
|
-
fill: string;
|
|
62
|
-
'scale-down': string;
|
|
63
|
-
};
|
|
64
|
-
}, undefined, import("tailwind-variants").TVReturnType<{
|
|
65
|
-
radius: {
|
|
66
|
-
none: string;
|
|
67
|
-
sm: string;
|
|
68
|
-
md: string;
|
|
69
|
-
lg: string;
|
|
70
|
-
xl: string;
|
|
71
|
-
full: string;
|
|
72
|
-
};
|
|
73
|
-
objectFit: {
|
|
74
|
-
cover: string;
|
|
75
|
-
contain: string;
|
|
76
|
-
fill: string;
|
|
77
|
-
'scale-down': string;
|
|
78
|
-
};
|
|
79
|
-
}, undefined, "overflow-hidden", import("tailwind-variants/dist/config.js").TVConfig<{
|
|
80
|
-
radius: {
|
|
81
|
-
none: string;
|
|
82
|
-
sm: string;
|
|
83
|
-
md: string;
|
|
84
|
-
lg: string;
|
|
85
|
-
xl: string;
|
|
86
|
-
full: string;
|
|
87
|
-
};
|
|
88
|
-
objectFit: {
|
|
89
|
-
cover: string;
|
|
90
|
-
contain: string;
|
|
91
|
-
fill: string;
|
|
92
|
-
'scale-down': string;
|
|
93
|
-
};
|
|
94
|
-
}, {
|
|
95
|
-
radius: {
|
|
96
|
-
none: string;
|
|
97
|
-
sm: string;
|
|
98
|
-
md: string;
|
|
99
|
-
lg: string;
|
|
100
|
-
xl: string;
|
|
101
|
-
full: string;
|
|
102
|
-
};
|
|
103
|
-
objectFit: {
|
|
104
|
-
cover: string;
|
|
105
|
-
contain: string;
|
|
106
|
-
fill: string;
|
|
107
|
-
'scale-down': string;
|
|
108
|
-
};
|
|
109
|
-
}>, unknown, unknown, undefined>>;
|
|
50
|
+
}, undefined>>;
|
|
110
51
|
export interface SmartImageProps extends Omit<ViewProps, 'className'>, VariantProps<typeof smartImageVariants> {
|
|
111
52
|
className?: string;
|
|
112
|
-
source:
|
|
53
|
+
source: ImageProps['source'];
|
|
113
54
|
alt: string;
|
|
114
55
|
skeleton?: boolean;
|
|
115
56
|
skeletonClassName?: string;
|
|
116
57
|
transition?: number;
|
|
117
58
|
onLoad?: () => void;
|
|
118
59
|
onError?: () => void;
|
|
119
|
-
fallbackSource?:
|
|
60
|
+
fallbackSource?: ImageProps['source'];
|
|
120
61
|
}
|
|
121
|
-
declare const SmartImage:
|
|
62
|
+
declare const SmartImage: ForwardRefComponent<ViewInstance, SmartImageProps>;
|
|
122
63
|
export { SmartImage, smartImageVariants };
|