@oxyhq/bloom 0.19.1 → 0.20.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/lib/commonjs/benefit-list/index.js +119 -0
- package/lib/commonjs/benefit-list/index.js.map +1 -0
- package/lib/commonjs/benefit-list/types.js +6 -0
- package/lib/commonjs/benefit-list/types.js.map +1 -0
- package/lib/commonjs/connection-dots/ConnectionDots.js +112 -0
- package/lib/commonjs/connection-dots/ConnectionDots.js.map +1 -0
- package/lib/commonjs/connection-dots/ConnectionDots.web.js +116 -0
- package/lib/commonjs/connection-dots/ConnectionDots.web.js.map +1 -0
- package/lib/commonjs/connection-dots/ConnectionDotsBase.js +55 -0
- package/lib/commonjs/connection-dots/ConnectionDotsBase.js.map +1 -0
- package/lib/commonjs/connection-dots/index.js +13 -0
- package/lib/commonjs/connection-dots/index.js.map +1 -0
- package/lib/commonjs/connection-dots/index.web.js +13 -0
- package/lib/commonjs/connection-dots/index.web.js.map +1 -0
- package/lib/commonjs/connection-dots/types.js +6 -0
- package/lib/commonjs/connection-dots/types.js.map +1 -0
- package/lib/commonjs/design-tokens/color-roles.js +112 -0
- package/lib/commonjs/design-tokens/color-roles.js.map +1 -0
- package/lib/commonjs/design-tokens/index.js +89 -0
- package/lib/commonjs/design-tokens/index.js.map +1 -0
- package/lib/commonjs/design-tokens/scales.js +168 -0
- package/lib/commonjs/design-tokens/scales.js.map +1 -0
- package/lib/commonjs/design-tokens/shadows.js +48 -0
- package/lib/commonjs/design-tokens/shadows.js.map +1 -0
- package/lib/commonjs/design-tokens/shadows.native.js +53 -0
- package/lib/commonjs/design-tokens/shadows.native.js.map +1 -0
- package/lib/commonjs/design-tokens/tailwind-preset.js +139 -0
- package/lib/commonjs/design-tokens/tailwind-preset.js.map +1 -0
- package/lib/commonjs/design-tokens/theme-css.js +98 -0
- package/lib/commonjs/design-tokens/theme-css.js.map +1 -0
- package/lib/commonjs/index.js +208 -93
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +223 -108
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/text-field/index.js +156 -0
- package/lib/commonjs/text-field/index.js.map +1 -1
- package/lib/module/benefit-list/index.js +114 -0
- package/lib/module/benefit-list/index.js.map +1 -0
- package/lib/module/benefit-list/types.js +4 -0
- package/lib/module/benefit-list/types.js.map +1 -0
- package/lib/module/connection-dots/ConnectionDots.js +107 -0
- package/lib/module/connection-dots/ConnectionDots.js.map +1 -0
- package/lib/module/connection-dots/ConnectionDots.web.js +111 -0
- package/lib/module/connection-dots/ConnectionDots.web.js.map +1 -0
- package/lib/module/connection-dots/ConnectionDotsBase.js +50 -0
- package/lib/module/connection-dots/ConnectionDotsBase.js.map +1 -0
- package/lib/module/connection-dots/index.js +4 -0
- package/lib/module/connection-dots/index.js.map +1 -0
- package/lib/module/connection-dots/index.web.js +4 -0
- package/lib/module/connection-dots/index.web.js.map +1 -0
- package/lib/module/connection-dots/types.js +4 -0
- package/lib/module/connection-dots/types.js.map +1 -0
- package/lib/module/design-tokens/color-roles.js +108 -0
- package/lib/module/design-tokens/color-roles.js.map +1 -0
- package/lib/module/design-tokens/index.js +27 -0
- package/lib/module/design-tokens/index.js.map +1 -0
- package/lib/module/design-tokens/scales.js +164 -0
- package/lib/module/design-tokens/scales.js.map +1 -0
- package/lib/module/design-tokens/shadows.js +43 -0
- package/lib/module/design-tokens/shadows.js.map +1 -0
- package/lib/module/design-tokens/shadows.native.js +48 -0
- package/lib/module/design-tokens/shadows.native.js.map +1 -0
- package/lib/module/design-tokens/tailwind-preset.js +136 -0
- package/lib/module/design-tokens/tailwind-preset.js.map +1 -0
- package/lib/module/design-tokens/theme-css.js +94 -0
- package/lib/module/design-tokens/theme-css.js.map +1 -0
- package/lib/module/index.js +6 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +6 -1
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/text-field/index.js +158 -2
- package/lib/module/text-field/index.js.map +1 -1
- package/lib/typescript/commonjs/benefit-list/index.d.ts +6 -0
- package/lib/typescript/commonjs/benefit-list/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/benefit-list/types.d.ts +35 -0
- package/lib/typescript/commonjs/benefit-list/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.d.ts +4 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.web.d.ts +14 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDots.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDotsBase.d.ts +18 -0
- package/lib/typescript/commonjs/connection-dots/ConnectionDotsBase.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/index.d.ts +3 -0
- package/lib/typescript/commonjs/connection-dots/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/index.web.d.ts +3 -0
- package/lib/typescript/commonjs/connection-dots/index.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/connection-dots/types.d.ts +41 -0
- package/lib/typescript/commonjs/connection-dots/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/color-roles.d.ts +102 -0
- package/lib/typescript/commonjs/design-tokens/color-roles.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/index.d.ts +28 -0
- package/lib/typescript/commonjs/design-tokens/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/scales.d.ts +152 -0
- package/lib/typescript/commonjs/design-tokens/scales.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/shadows.d.ts +34 -0
- package/lib/typescript/commonjs/design-tokens/shadows.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/shadows.native.d.ts +19 -0
- package/lib/typescript/commonjs/design-tokens/shadows.native.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/tailwind-preset.d.ts +59 -0
- package/lib/typescript/commonjs/design-tokens/tailwind-preset.d.ts.map +1 -0
- package/lib/typescript/commonjs/design-tokens/theme-css.d.ts +39 -0
- package/lib/typescript/commonjs/design-tokens/theme-css.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +6 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.web.d.ts +6 -0
- package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/text-field/index.d.ts +14 -1
- package/lib/typescript/commonjs/text-field/index.d.ts.map +1 -1
- package/lib/typescript/module/benefit-list/index.d.ts +6 -0
- package/lib/typescript/module/benefit-list/index.d.ts.map +1 -0
- package/lib/typescript/module/benefit-list/types.d.ts +35 -0
- package/lib/typescript/module/benefit-list/types.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/ConnectionDots.d.ts +4 -0
- package/lib/typescript/module/connection-dots/ConnectionDots.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/ConnectionDots.web.d.ts +14 -0
- package/lib/typescript/module/connection-dots/ConnectionDots.web.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/ConnectionDotsBase.d.ts +18 -0
- package/lib/typescript/module/connection-dots/ConnectionDotsBase.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/index.d.ts +3 -0
- package/lib/typescript/module/connection-dots/index.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/index.web.d.ts +3 -0
- package/lib/typescript/module/connection-dots/index.web.d.ts.map +1 -0
- package/lib/typescript/module/connection-dots/types.d.ts +41 -0
- package/lib/typescript/module/connection-dots/types.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/color-roles.d.ts +102 -0
- package/lib/typescript/module/design-tokens/color-roles.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/index.d.ts +28 -0
- package/lib/typescript/module/design-tokens/index.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/scales.d.ts +152 -0
- package/lib/typescript/module/design-tokens/scales.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/shadows.d.ts +34 -0
- package/lib/typescript/module/design-tokens/shadows.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/shadows.native.d.ts +19 -0
- package/lib/typescript/module/design-tokens/shadows.native.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/tailwind-preset.d.ts +59 -0
- package/lib/typescript/module/design-tokens/tailwind-preset.d.ts.map +1 -0
- package/lib/typescript/module/design-tokens/theme-css.d.ts +39 -0
- package/lib/typescript/module/design-tokens/theme-css.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +6 -0
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/index.web.d.ts +6 -0
- package/lib/typescript/module/index.web.d.ts.map +1 -1
- package/lib/typescript/module/text-field/index.d.ts +14 -1
- package/lib/typescript/module/text-field/index.d.ts.map +1 -1
- package/package.json +50 -1
- package/src/__tests__/BenefitList.test.tsx +108 -0
- package/src/__tests__/ConnectionDots.test.tsx +86 -0
- package/src/__tests__/TextFieldFloatingLabel.test.tsx +93 -0
- package/src/__tests__/design-tokens.test.ts +169 -0
- package/src/benefit-list/BenefitList.stories.tsx +50 -0
- package/src/benefit-list/index.tsx +133 -0
- package/src/benefit-list/types.ts +36 -0
- package/src/connection-dots/ConnectionDots.stories.tsx +54 -0
- package/src/connection-dots/ConnectionDots.tsx +139 -0
- package/src/connection-dots/ConnectionDots.web.tsx +122 -0
- package/src/connection-dots/ConnectionDotsBase.tsx +62 -0
- package/src/connection-dots/index.ts +2 -0
- package/src/connection-dots/index.web.ts +2 -0
- package/src/connection-dots/types.ts +41 -0
- package/src/design-tokens/color-roles.ts +110 -0
- package/src/design-tokens/index.ts +45 -0
- package/src/design-tokens/scales.ts +134 -0
- package/src/design-tokens/shadows.native.ts +44 -0
- package/src/design-tokens/shadows.ts +42 -0
- package/src/design-tokens/tailwind-preset.ts +139 -0
- package/src/design-tokens/theme-css.ts +99 -0
- package/src/index.ts +33 -0
- package/src/index.web.ts +33 -0
- package/src/text-field/TextField.stories.tsx +46 -0
- package/src/text-field/index.tsx +212 -1
package/src/index.web.ts
CHANGED
|
@@ -12,6 +12,35 @@ export type { ViewStyleProp, TextStyleProp } from './styles';
|
|
|
12
12
|
export * as tokens from './styles/tokens';
|
|
13
13
|
export { web, native, ios, android, platform, select } from './styles/platform';
|
|
14
14
|
|
|
15
|
+
// Design tokens (Oxy Unified Design Language) — semantic Tailwind/NativeWind
|
|
16
|
+
// vocabulary. The Tailwind preset is consumed from `@oxyhq/bloom/tailwind-preset`
|
|
17
|
+
// in a config file; these named exports cover programmatic/runtime use.
|
|
18
|
+
export {
|
|
19
|
+
bloomTailwindPreset,
|
|
20
|
+
bloomThemeCss,
|
|
21
|
+
bloomThemeBlock,
|
|
22
|
+
FILL_ROLES,
|
|
23
|
+
TEXT_ROLES,
|
|
24
|
+
BORDER_ROLES,
|
|
25
|
+
SPACING,
|
|
26
|
+
RADIUS,
|
|
27
|
+
BORDER_WIDTH,
|
|
28
|
+
TYPOGRAPHY,
|
|
29
|
+
FONT_FAMILY_VARS,
|
|
30
|
+
SHADOW_BOX,
|
|
31
|
+
bloomShadowStyle,
|
|
32
|
+
} from './design-tokens';
|
|
33
|
+
export type {
|
|
34
|
+
TailwindPreset,
|
|
35
|
+
TailwindPresetThemeExtend,
|
|
36
|
+
FillRole,
|
|
37
|
+
TextRole,
|
|
38
|
+
BorderRole,
|
|
39
|
+
TypeRole,
|
|
40
|
+
TypeRoleName,
|
|
41
|
+
ShadowRole,
|
|
42
|
+
} from './design-tokens';
|
|
43
|
+
|
|
15
44
|
// Hooks
|
|
16
45
|
export { useInteractionState } from './hooks/useInteractionState';
|
|
17
46
|
export { useDelayedLoading } from './hooks/useDelayedLoading';
|
|
@@ -74,6 +103,10 @@ export * as Skeleton from './skeleton';
|
|
|
74
103
|
export * as Grid from './grid';
|
|
75
104
|
export { Fill } from './fill';
|
|
76
105
|
export { IconCircle } from './icon-circle';
|
|
106
|
+
export { ConnectionDots } from './connection-dots/index.web';
|
|
107
|
+
export type { ConnectionDotsProps } from './connection-dots/index.web';
|
|
108
|
+
export { BenefitRow, BenefitList } from './benefit-list';
|
|
109
|
+
export type { BenefitRowProps, BenefitListProps } from './benefit-list';
|
|
77
110
|
|
|
78
111
|
// Form components
|
|
79
112
|
export * from './text-field';
|
|
@@ -43,6 +43,34 @@ function ControlledField({
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function FloatingField({
|
|
47
|
+
label,
|
|
48
|
+
initial = '',
|
|
49
|
+
isInvalid,
|
|
50
|
+
editable = true,
|
|
51
|
+
}: {
|
|
52
|
+
label: string;
|
|
53
|
+
initial?: string;
|
|
54
|
+
isInvalid?: boolean;
|
|
55
|
+
editable?: boolean;
|
|
56
|
+
}) {
|
|
57
|
+
const [value, setValue] = useState(initial);
|
|
58
|
+
return (
|
|
59
|
+
<View style={{ width: 320 }}>
|
|
60
|
+
<TextField isInvalid={isInvalid}>
|
|
61
|
+
<TextFieldInput
|
|
62
|
+
floatingLabel
|
|
63
|
+
label={label}
|
|
64
|
+
value={value}
|
|
65
|
+
onChangeText={setValue}
|
|
66
|
+
editable={editable}
|
|
67
|
+
isInvalid={isInvalid}
|
|
68
|
+
/>
|
|
69
|
+
</TextField>
|
|
70
|
+
</View>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
46
74
|
export const Basic: Story = {
|
|
47
75
|
render: () => (
|
|
48
76
|
<ControlledField label="Username" placeholder="oxylander" />
|
|
@@ -88,3 +116,21 @@ export const Composition: Story = {
|
|
|
88
116
|
</View>
|
|
89
117
|
),
|
|
90
118
|
};
|
|
119
|
+
|
|
120
|
+
export const FloatingLabel: Story = {
|
|
121
|
+
render: () => <FloatingField label="Email" />,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const FloatingLabelWithValue: Story = {
|
|
125
|
+
render: () => <FloatingField label="Email" initial="nate@oxy.so" />,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const FloatingLabelComposition: Story = {
|
|
129
|
+
render: () => (
|
|
130
|
+
<View style={{ gap: 16 }}>
|
|
131
|
+
<FloatingField label="Full name" />
|
|
132
|
+
<FloatingField label="Email" initial="ada@oxy.so" />
|
|
133
|
+
<FloatingField label="Email" initial="invalid-email" isInvalid />
|
|
134
|
+
</View>
|
|
135
|
+
),
|
|
136
|
+
};
|
package/src/text-field/index.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, useRef } from 'react';
|
|
1
|
+
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type AccessibilityProps,
|
|
4
|
+
Animated,
|
|
4
5
|
Platform,
|
|
5
6
|
StyleSheet,
|
|
6
7
|
TextInput,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
View,
|
|
10
11
|
type ViewStyle,
|
|
11
12
|
} from 'react-native';
|
|
13
|
+
import { useReducedMotion } from 'react-native-reanimated';
|
|
12
14
|
|
|
13
15
|
import { useTheme } from '../theme/use-theme';
|
|
14
16
|
import { useInteractionState } from '../hooks/useInteractionState';
|
|
@@ -132,6 +134,19 @@ export type TextFieldInputProps = Omit<
|
|
|
132
134
|
isInvalid?: boolean;
|
|
133
135
|
inputRef?: React.RefObject<TextInput | null> | React.ForwardedRef<TextInput>;
|
|
134
136
|
placeholder?: string | null | undefined;
|
|
137
|
+
/**
|
|
138
|
+
* Render the field with a Material-style floating label. When `true`, the
|
|
139
|
+
* {@link label} sits inside the field as the placeholder while the input is
|
|
140
|
+
* empty AND unfocused; on focus OR when a value is present it animates up to a
|
|
141
|
+
* small caption pinned to the top of the field and the typed value shows
|
|
142
|
+
* below it. Opt-in — the default (`false`) keeps the existing chrome where the
|
|
143
|
+
* label lives above the field (`TextFieldLabel`) and the placeholder is plain.
|
|
144
|
+
*
|
|
145
|
+
* Cross-platform: the animation is driven by focus + value-presence state (no
|
|
146
|
+
* CSS `:placeholder-shown` / `peer-focus`), so web and native behave
|
|
147
|
+
* identically. Respects reduced-motion (snaps instead of animating).
|
|
148
|
+
*/
|
|
149
|
+
floatingLabel?: boolean;
|
|
135
150
|
};
|
|
136
151
|
|
|
137
152
|
export function TextFieldInput({
|
|
@@ -144,6 +159,7 @@ export function TextFieldInput({
|
|
|
144
159
|
isInvalid,
|
|
145
160
|
inputRef,
|
|
146
161
|
style,
|
|
162
|
+
floatingLabel = false,
|
|
147
163
|
...rest
|
|
148
164
|
}: TextFieldInputProps) {
|
|
149
165
|
const theme = useTheme();
|
|
@@ -162,6 +178,11 @@ export function TextFieldInput({
|
|
|
162
178
|
value={value}
|
|
163
179
|
onChangeText={onChangeText}
|
|
164
180
|
isInvalid={isInvalid}
|
|
181
|
+
floatingLabel={floatingLabel}
|
|
182
|
+
style={style}
|
|
183
|
+
inputRef={inputRef}
|
|
184
|
+
onFocus={onFocus}
|
|
185
|
+
onBlur={onBlur}
|
|
165
186
|
{...rest}
|
|
166
187
|
/>
|
|
167
188
|
</TextField>
|
|
@@ -174,6 +195,26 @@ export function TextFieldInput({
|
|
|
174
195
|
),
|
|
175
196
|
);
|
|
176
197
|
|
|
198
|
+
if (floatingLabel) {
|
|
199
|
+
return (
|
|
200
|
+
<FloatingLabelInput
|
|
201
|
+
label={label}
|
|
202
|
+
value={value}
|
|
203
|
+
onChangeText={onChangeText}
|
|
204
|
+
onFocus={onFocus}
|
|
205
|
+
onBlur={onBlur}
|
|
206
|
+
isInvalid={isInvalid}
|
|
207
|
+
refs={refs}
|
|
208
|
+
style={style}
|
|
209
|
+
chromeHover={chromeHover}
|
|
210
|
+
chromeFocus={chromeFocus}
|
|
211
|
+
chromeError={chromeError}
|
|
212
|
+
chromeErrorHover={chromeErrorHover}
|
|
213
|
+
{...rest}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
177
218
|
const flattened: TextStyle = StyleSheet.flatten([
|
|
178
219
|
a.relative,
|
|
179
220
|
a.z_20,
|
|
@@ -246,6 +287,176 @@ export function TextFieldInput({
|
|
|
246
287
|
);
|
|
247
288
|
}
|
|
248
289
|
|
|
290
|
+
/** Vertical band the floating label travels between rest (centered) and floated (top). */
|
|
291
|
+
const FLOAT_TRAVEL = 11;
|
|
292
|
+
/** Resting label size (matches the input text) vs. floated caption size. */
|
|
293
|
+
const FLOAT_LABEL_REST_SIZE = a.text_md.fontSize;
|
|
294
|
+
const FLOAT_LABEL_FLOAT_SIZE = a.text_xs.fontSize;
|
|
295
|
+
|
|
296
|
+
type FloatingLabelInputProps = Omit<
|
|
297
|
+
TextInputProps,
|
|
298
|
+
'value' | 'onChangeText' | 'placeholder'
|
|
299
|
+
> & {
|
|
300
|
+
label: string;
|
|
301
|
+
value?: string;
|
|
302
|
+
onChangeText?: (value: string) => void;
|
|
303
|
+
isInvalid?: boolean;
|
|
304
|
+
refs: (instance: TextInput | null) => void;
|
|
305
|
+
chromeHover: ViewStyle;
|
|
306
|
+
chromeFocus: ViewStyle;
|
|
307
|
+
chromeError: ViewStyle;
|
|
308
|
+
chromeErrorHover: ViewStyle;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The floating-label rendering of {@link TextFieldInput}.
|
|
313
|
+
*
|
|
314
|
+
* State, not CSS, drives the motion so web and native behave identically:
|
|
315
|
+
* `floated = focused || hasValue`. A single `Animated.Value` (0 → rest, 1 →
|
|
316
|
+
* floated) interpolates the label's `top`, `fontSize`, and `color`; the same
|
|
317
|
+
* `Animated` API maps to CSS transforms on web via react-native-web, so no
|
|
318
|
+
* `:placeholder-shown` / `peer-focus` is needed. Reduced-motion snaps the value
|
|
319
|
+
* (duration 0) instead of animating.
|
|
320
|
+
*
|
|
321
|
+
* The native placeholder is intentionally suppressed — the floating label IS the
|
|
322
|
+
* placeholder at rest. The label still drives `accessibilityLabel`, and the
|
|
323
|
+
* chrome / focus / error / disabled infra is shared with the default variant.
|
|
324
|
+
*/
|
|
325
|
+
function FloatingLabelInput({
|
|
326
|
+
label,
|
|
327
|
+
value,
|
|
328
|
+
onChangeText,
|
|
329
|
+
onFocus,
|
|
330
|
+
onBlur,
|
|
331
|
+
isInvalid,
|
|
332
|
+
refs,
|
|
333
|
+
style,
|
|
334
|
+
chromeHover,
|
|
335
|
+
chromeFocus,
|
|
336
|
+
chromeError,
|
|
337
|
+
chromeErrorHover,
|
|
338
|
+
...rest
|
|
339
|
+
}: FloatingLabelInputProps) {
|
|
340
|
+
const theme = useTheme();
|
|
341
|
+
const ctx = useContext(Context);
|
|
342
|
+
const reduceMotion = useReducedMotion();
|
|
343
|
+
|
|
344
|
+
const hasValue = (value?.length ?? 0) > 0;
|
|
345
|
+
const floated = ctx.focused || hasValue;
|
|
346
|
+
const invalid = ctx.isInvalid || isInvalid;
|
|
347
|
+
|
|
348
|
+
const progress = useRef(new Animated.Value(floated ? 1 : 0)).current;
|
|
349
|
+
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
const target = floated ? 1 : 0;
|
|
352
|
+
if (reduceMotion) {
|
|
353
|
+
progress.setValue(target);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const timing = Animated.timing(progress, {
|
|
357
|
+
toValue: target,
|
|
358
|
+
duration: tokens.animation.duration.fast,
|
|
359
|
+
useNativeDriver: false,
|
|
360
|
+
});
|
|
361
|
+
timing.start();
|
|
362
|
+
return () => timing.stop();
|
|
363
|
+
}, [floated, reduceMotion, progress]);
|
|
364
|
+
|
|
365
|
+
const labelTop = progress.interpolate({
|
|
366
|
+
inputRange: [0, 1],
|
|
367
|
+
outputRange: [16, 16 - FLOAT_TRAVEL],
|
|
368
|
+
});
|
|
369
|
+
const labelSize = progress.interpolate({
|
|
370
|
+
inputRange: [0, 1],
|
|
371
|
+
outputRange: [FLOAT_LABEL_REST_SIZE, FLOAT_LABEL_FLOAT_SIZE],
|
|
372
|
+
});
|
|
373
|
+
const labelColor = progress.interpolate({
|
|
374
|
+
inputRange: [0, 1],
|
|
375
|
+
outputRange: [
|
|
376
|
+
theme.colors.textSecondary,
|
|
377
|
+
invalid ? theme.colors.negative : theme.colors.primary,
|
|
378
|
+
],
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const inputStyle: TextStyle = StyleSheet.flatten([
|
|
382
|
+
a.relative,
|
|
383
|
+
a.z_20,
|
|
384
|
+
a.flex_1,
|
|
385
|
+
a.text_md,
|
|
386
|
+
{ color: theme.colors.text },
|
|
387
|
+
a.px_xs,
|
|
388
|
+
{
|
|
389
|
+
lineHeight: a.text_md.fontSize * 1.2,
|
|
390
|
+
minWidth: 0,
|
|
391
|
+
paddingTop: 26,
|
|
392
|
+
paddingBottom: 8,
|
|
393
|
+
},
|
|
394
|
+
android({ paddingTop: 24, paddingBottom: 6 }),
|
|
395
|
+
web({ paddingTop: 25, paddingBottom: 7 }),
|
|
396
|
+
style,
|
|
397
|
+
]) as TextStyle;
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<>
|
|
401
|
+
<Animated.Text
|
|
402
|
+
accessibilityElementsHidden
|
|
403
|
+
importantForAccessibility="no-hide-descendants"
|
|
404
|
+
aria-hidden
|
|
405
|
+
numberOfLines={1}
|
|
406
|
+
style={[
|
|
407
|
+
a.absolute,
|
|
408
|
+
a.z_30,
|
|
409
|
+
a.pointer_events_none,
|
|
410
|
+
{
|
|
411
|
+
left: tokens.space.xs + tokens.space._2xs,
|
|
412
|
+
top: labelTop,
|
|
413
|
+
fontSize: labelSize,
|
|
414
|
+
color: labelColor,
|
|
415
|
+
},
|
|
416
|
+
]}>
|
|
417
|
+
{label}
|
|
418
|
+
</Animated.Text>
|
|
419
|
+
|
|
420
|
+
<TextInput
|
|
421
|
+
accessibilityHint={undefined}
|
|
422
|
+
hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
|
|
423
|
+
{...rest}
|
|
424
|
+
accessibilityLabel={label}
|
|
425
|
+
ref={refs}
|
|
426
|
+
value={value}
|
|
427
|
+
onChangeText={onChangeText}
|
|
428
|
+
onFocus={(e) => {
|
|
429
|
+
ctx.onFocus();
|
|
430
|
+
onFocus?.(e);
|
|
431
|
+
}}
|
|
432
|
+
onBlur={(e) => {
|
|
433
|
+
ctx.onBlur();
|
|
434
|
+
onBlur?.(e);
|
|
435
|
+
}}
|
|
436
|
+
placeholder={undefined}
|
|
437
|
+
placeholderTextColor={theme.colors.textSecondary}
|
|
438
|
+
keyboardAppearance={theme.mode === 'light' ? 'light' : 'dark'}
|
|
439
|
+
style={inputStyle}
|
|
440
|
+
/>
|
|
441
|
+
|
|
442
|
+
<View
|
|
443
|
+
style={[
|
|
444
|
+
a.z_10,
|
|
445
|
+
a.absolute,
|
|
446
|
+
a.inset_0,
|
|
447
|
+
{ borderRadius: 10 },
|
|
448
|
+
{ backgroundColor: theme.colors.contrast50 },
|
|
449
|
+
{ borderColor: 'transparent', borderWidth: 2 },
|
|
450
|
+
ctx.hovered ? chromeHover : {},
|
|
451
|
+
ctx.focused ? chromeFocus : {},
|
|
452
|
+
invalid ? chromeError : {},
|
|
453
|
+
invalid && (ctx.hovered || ctx.focused) ? chromeErrorHover : {},
|
|
454
|
+
]}
|
|
455
|
+
/>
|
|
456
|
+
</>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
249
460
|
export function TextFieldLabel({
|
|
250
461
|
nativeID,
|
|
251
462
|
children,
|