@webority-technologies/mobile 0.0.20 → 0.0.21
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/components/FieldBase/FieldBase.js +212 -24
- package/lib/commonjs/components/Input/Input.js +1 -1
- package/lib/commonjs/components/SearchBar/SearchBar.js +8 -2
- package/lib/commonjs/components/Select/Select.js +1 -1
- package/lib/module/components/FieldBase/FieldBase.js +211 -23
- package/lib/module/components/Input/Input.js +1 -1
- package/lib/module/components/SearchBar/SearchBar.js +8 -2
- package/lib/module/components/Select/Select.js +1 -1
- package/lib/typescript/commonjs/components/FieldBase/FieldBase.d.ts +43 -12
- package/lib/typescript/commonjs/components/Input/Input.d.ts +1 -1
- package/lib/typescript/commonjs/theme/types.d.ts +31 -7
- package/lib/typescript/module/components/FieldBase/FieldBase.d.ts +43 -12
- package/lib/typescript/module/components/Input/Input.d.ts +1 -1
- package/lib/typescript/module/theme/types.d.ts +31 -7
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.resolveVariantColors = exports.resolveFieldTextStyle = exports.resolveFieldSize = exports.default = exports.FieldBase = void 0;
|
|
6
|
+
exports.resolveVariantColors = exports.resolveFieldTextStyle = exports.resolveFieldSize = exports.pickRepresentativeBorderColor = exports.default = exports.FieldBase = void 0;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _index = require("../../theme/index.js");
|
|
@@ -33,6 +33,66 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
33
33
|
* - Shake-on-error or any cross-field animation.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
+
const SIDES = ['top', 'right', 'bottom', 'left'];
|
|
37
|
+
|
|
38
|
+
/** Map a single colour string to all four sides (shorthand expansion). */
|
|
39
|
+
const expandColorSides = value => {
|
|
40
|
+
if (typeof value === 'string') {
|
|
41
|
+
return {
|
|
42
|
+
top: value,
|
|
43
|
+
right: value,
|
|
44
|
+
bottom: value,
|
|
45
|
+
left: value
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Object form: any side the consumer omitted falls through to 'transparent'.
|
|
49
|
+
// Rationale: if a consumer says `{ bottom: '#000' }`, they want only the
|
|
50
|
+
// bottom drawn — the other sides should be invisible, not inherit some
|
|
51
|
+
// unrelated colour.
|
|
52
|
+
return {
|
|
53
|
+
top: value.top ?? 'transparent',
|
|
54
|
+
right: value.right ?? 'transparent',
|
|
55
|
+
bottom: value.bottom ?? 'transparent',
|
|
56
|
+
left: value.left ?? 'transparent'
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Map a single width number to all four sides (shorthand expansion). */
|
|
61
|
+
const expandWidthSides = value => {
|
|
62
|
+
if (typeof value === 'number') {
|
|
63
|
+
return {
|
|
64
|
+
top: value,
|
|
65
|
+
right: value,
|
|
66
|
+
bottom: value,
|
|
67
|
+
left: value
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
top: value.top ?? 0,
|
|
72
|
+
right: value.right ?? 0,
|
|
73
|
+
bottom: value.bottom ?? 0,
|
|
74
|
+
left: value.left ?? 0
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** Are all four sides of a per-side record equal? Used to collapse to shorthand. */
|
|
79
|
+
const allSidesEqual = sides => sides.top === sides.right && sides.right === sides.bottom && sides.bottom === sides.left;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pick the most "representative" side colour from a 4-side record. Used by
|
|
83
|
+
* consumers (Input's selectionColor, focus rings, etc.) that need a single
|
|
84
|
+
* colour to mirror the visible focus indicator. Priority: bottom → top →
|
|
85
|
+
* left → right, skipping `'transparent'`. The bottom-first order means
|
|
86
|
+
* underline variants pick their underline colour automatically.
|
|
87
|
+
*/
|
|
88
|
+
const pickRepresentativeBorderColor = sides => {
|
|
89
|
+
for (const side of ['bottom', 'top', 'left', 'right']) {
|
|
90
|
+
const c = sides[side];
|
|
91
|
+
if (c && c !== 'transparent') return c;
|
|
92
|
+
}
|
|
93
|
+
return sides.bottom;
|
|
94
|
+
};
|
|
95
|
+
|
|
36
96
|
/**
|
|
37
97
|
* Resolved text styling for the editable / displayed content inside a field.
|
|
38
98
|
* Single source of truth so every field component (Input, NumberInput,
|
|
@@ -41,7 +101,7 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
41
101
|
* same place. OTPInput intentionally overrides this with its own semibold
|
|
42
102
|
* display weight.
|
|
43
103
|
*/
|
|
44
|
-
|
|
104
|
+
exports.pickRepresentativeBorderColor = pickRepresentativeBorderColor;
|
|
45
105
|
/**
|
|
46
106
|
* Resolve the canonical text style chunk for a field's value. Reads from
|
|
47
107
|
* `theme.components.field.{textColor,disabledTextColor,placeholderColor,fontWeight}`
|
|
@@ -122,31 +182,70 @@ const DEFAULT_SIZES = {
|
|
|
122
182
|
|
|
123
183
|
/**
|
|
124
184
|
* Strict, fully-resolved colour set used internally. Mirrors `FieldVariantTokens`
|
|
125
|
-
* but every
|
|
126
|
-
*
|
|
185
|
+
* but every border colour / width is expanded to a 4-side record by the time
|
|
186
|
+
* the box renders, so the animation pipeline can treat per-side and shorthand
|
|
187
|
+
* the same way. `borderFocusedRepresentative` is a single colour pulled from
|
|
188
|
+
* the focused sides — exposed for consumers (Input's selectionColor) that
|
|
189
|
+
* need to mirror the visible focus indicator without re-resolving per side.
|
|
127
190
|
*/
|
|
128
191
|
|
|
129
192
|
/**
|
|
130
193
|
* Resolve the full set of state-aware fill + border colours for a given
|
|
131
|
-
* variant. Order: explicit override → theme token → library default.
|
|
194
|
+
* variant. Order: explicit override → theme token → library default. Border
|
|
195
|
+
* colour + width are normalized to per-side records here; callers that need
|
|
196
|
+
* a single colour read `borderFocusedRepresentative`.
|
|
132
197
|
*/
|
|
133
198
|
const resolveVariantColors = (theme, variant, override) => {
|
|
134
199
|
const tokenSet = theme.components.field?.[variant];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
200
|
+
|
|
201
|
+
// Variant-specific defaults for resting fill + border treatment. `outlined`
|
|
202
|
+
// draws all four sides; `filled` draws nothing (fill-only); `underline`
|
|
203
|
+
// draws only the bottom edge — Material-style single-line inputs.
|
|
204
|
+
let idleEmpty;
|
|
205
|
+
let borderIdleDefault;
|
|
206
|
+
let borderWidthDefault;
|
|
207
|
+
if (variant === 'outlined') {
|
|
208
|
+
idleEmpty = theme.colors.background.primary;
|
|
209
|
+
borderIdleDefault = theme.colors.border.primary;
|
|
210
|
+
borderWidthDefault = theme.colors.border.width;
|
|
211
|
+
} else if (variant === 'underline') {
|
|
212
|
+
idleEmpty = 'transparent';
|
|
213
|
+
borderIdleDefault = {
|
|
214
|
+
bottom: theme.colors.border.primary
|
|
215
|
+
};
|
|
216
|
+
borderWidthDefault = {
|
|
217
|
+
bottom: theme.colors.border.width
|
|
218
|
+
};
|
|
219
|
+
} else {
|
|
220
|
+
idleEmpty = theme.colors.background.secondary;
|
|
221
|
+
borderIdleDefault = 'transparent';
|
|
222
|
+
borderWidthDefault = 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// For focused/error/disabled we expand using the same per-side shape as
|
|
226
|
+
// idle. If `borderIdle` ended up bottom-only (underline), an unset
|
|
227
|
+
// `borderFocused` defaults to `{ bottom: colors.border.focus }` so the
|
|
228
|
+
// focus indicator stays on the same edge. Same logic for error.
|
|
229
|
+
const wrapToIdleShape = colour => typeof borderIdleDefault === 'string' ? colour : {
|
|
230
|
+
bottom: colour
|
|
231
|
+
};
|
|
232
|
+
const borderIdle = expandColorSides(override?.borderIdle ?? tokenSet?.borderIdle ?? borderIdleDefault);
|
|
233
|
+
const borderFocused = expandColorSides(override?.borderFocused ?? tokenSet?.borderFocused ?? wrapToIdleShape(theme.colors.border.focus));
|
|
234
|
+
const borderError = expandColorSides(override?.borderError ?? tokenSet?.borderError ?? wrapToIdleShape(theme.colors.border.error));
|
|
235
|
+
const borderDisabled = expandColorSides(override?.borderDisabled ?? tokenSet?.borderDisabled ?? borderIdleDefault);
|
|
236
|
+
const borderWidth = expandWidthSides(override?.borderWidth ?? tokenSet?.borderWidth ?? borderWidthDefault);
|
|
139
237
|
return {
|
|
140
238
|
backgroundIdleEmpty: override?.backgroundIdleEmpty ?? tokenSet?.backgroundIdleEmpty ?? idleEmpty,
|
|
141
239
|
backgroundIdleFilled: override?.backgroundIdleFilled ?? tokenSet?.backgroundIdleFilled ?? override?.backgroundIdleEmpty ?? tokenSet?.backgroundIdleEmpty ?? idleEmpty,
|
|
142
240
|
backgroundFocused: override?.backgroundFocused ?? tokenSet?.backgroundFocused ?? undefined,
|
|
143
241
|
backgroundError: override?.backgroundError ?? tokenSet?.backgroundError ?? undefined,
|
|
144
242
|
backgroundDisabled: override?.backgroundDisabled ?? tokenSet?.backgroundDisabled ?? theme.colors.surface.disabled,
|
|
145
|
-
borderIdle
|
|
146
|
-
borderFocused
|
|
147
|
-
borderError
|
|
148
|
-
borderDisabled
|
|
149
|
-
borderWidth
|
|
243
|
+
borderIdle,
|
|
244
|
+
borderFocused,
|
|
245
|
+
borderError,
|
|
246
|
+
borderDisabled,
|
|
247
|
+
borderWidth,
|
|
248
|
+
borderFocusedRepresentative: pickRepresentativeBorderColor(borderFocused)
|
|
150
249
|
};
|
|
151
250
|
};
|
|
152
251
|
exports.resolveVariantColors = resolveVariantColors;
|
|
@@ -169,7 +268,15 @@ const FieldBase = props => {
|
|
|
169
268
|
paddingHorizontal: paddingHorizontalProp,
|
|
170
269
|
paddingVertical: paddingVerticalProp,
|
|
171
270
|
borderRadius: borderRadiusProp,
|
|
271
|
+
borderTopLeftRadius: borderTopLeftRadiusProp,
|
|
272
|
+
borderTopRightRadius: borderTopRightRadiusProp,
|
|
273
|
+
borderBottomLeftRadius: borderBottomLeftRadiusProp,
|
|
274
|
+
borderBottomRightRadius: borderBottomRightRadiusProp,
|
|
172
275
|
borderWidth: borderWidthProp,
|
|
276
|
+
borderTopWidth: borderTopWidthProp,
|
|
277
|
+
borderRightWidth: borderRightWidthProp,
|
|
278
|
+
borderBottomWidth: borderBottomWidthProp,
|
|
279
|
+
borderLeftWidth: borderLeftWidthProp,
|
|
173
280
|
gap: gapProp,
|
|
174
281
|
fillOverrides,
|
|
175
282
|
style,
|
|
@@ -208,13 +315,6 @@ const FieldBase = props => {
|
|
|
208
315
|
const idleFill = filled ? colors.backgroundIdleFilled : colors.backgroundIdleEmpty;
|
|
209
316
|
const focusedFill = colors.backgroundFocused ?? idleFill;
|
|
210
317
|
const errorFill = colors.backgroundError ?? idleFill;
|
|
211
|
-
const animatedBorderColor = disabled ? colors.borderDisabled : error ? errorAnim.interpolate({
|
|
212
|
-
inputRange: [0, 1],
|
|
213
|
-
outputRange: [colors.borderIdle, colors.borderError]
|
|
214
|
-
}) : focusAnim.interpolate({
|
|
215
|
-
inputRange: [0, 1],
|
|
216
|
-
outputRange: [colors.borderIdle, colors.borderFocused]
|
|
217
|
-
});
|
|
218
318
|
const animatedBackgroundColor = disabled ? colors.backgroundDisabled : error ? errorAnim.interpolate({
|
|
219
319
|
inputRange: [0, 1],
|
|
220
320
|
outputRange: [idleFill, errorFill]
|
|
@@ -222,17 +322,105 @@ const FieldBase = props => {
|
|
|
222
322
|
inputRange: [0, 1],
|
|
223
323
|
outputRange: [idleFill, focusedFill]
|
|
224
324
|
});
|
|
325
|
+
|
|
326
|
+
// Per-side border colour. For each side we resolve the "from" (idle) and
|
|
327
|
+
// "to" (focused or error, depending on state) colour, then drive a single
|
|
328
|
+
// animated interpolation. When all four sides share the same from+to pair
|
|
329
|
+
// we collapse to one `borderColor` key — the common case stays cheap.
|
|
330
|
+
const activeColors = error ? colors.borderError : colors.borderFocused;
|
|
331
|
+
const activeAnim = error ? errorAnim : focusAnim;
|
|
332
|
+
const sideFrom = disabled ? colors.borderDisabled : colors.borderIdle;
|
|
333
|
+
const sideTo = disabled ? colors.borderDisabled : activeColors;
|
|
334
|
+
|
|
335
|
+
// Per-side widths: longhand prop wins over shorthand prop wins over
|
|
336
|
+
// resolved variant token. Each side independently.
|
|
337
|
+
const propWidth = {
|
|
338
|
+
top: borderTopWidthProp,
|
|
339
|
+
right: borderRightWidthProp,
|
|
340
|
+
bottom: borderBottomWidthProp,
|
|
341
|
+
left: borderLeftWidthProp
|
|
342
|
+
};
|
|
343
|
+
const resolvedWidths = {
|
|
344
|
+
top: propWidth.top ?? borderWidthProp ?? colors.borderWidth.top,
|
|
345
|
+
right: propWidth.right ?? borderWidthProp ?? colors.borderWidth.right,
|
|
346
|
+
bottom: propWidth.bottom ?? borderWidthProp ?? colors.borderWidth.bottom,
|
|
347
|
+
left: propWidth.left ?? borderWidthProp ?? colors.borderWidth.left
|
|
348
|
+
};
|
|
225
349
|
const boxStyle = {
|
|
226
350
|
minHeight: height ?? minHeightProp ?? sizeTokens.minHeight,
|
|
227
351
|
maxHeight,
|
|
228
352
|
paddingHorizontal: paddingHorizontalProp ?? sizeTokens.paddingHorizontal,
|
|
229
353
|
paddingVertical: paddingVerticalProp ?? sizeTokens.paddingVertical,
|
|
230
|
-
borderRadius: borderRadiusProp ?? sizeTokens.borderRadius,
|
|
231
|
-
borderWidth: borderWidthProp ?? colors.borderWidth,
|
|
232
|
-
borderColor: animatedBorderColor,
|
|
233
354
|
backgroundColor: animatedBackgroundColor,
|
|
234
355
|
columnGap: gapProp ?? theme.spacing.sm
|
|
235
356
|
};
|
|
357
|
+
|
|
358
|
+
// Radius: per-corner prop wins over shorthand prop wins over size token.
|
|
359
|
+
// Collapse to single `borderRadius` when all four corners match.
|
|
360
|
+
const radiusShorthand = borderRadiusProp ?? sizeTokens.borderRadius;
|
|
361
|
+
const corners = {
|
|
362
|
+
tl: borderTopLeftRadiusProp ?? radiusShorthand,
|
|
363
|
+
tr: borderTopRightRadiusProp ?? radiusShorthand,
|
|
364
|
+
bl: borderBottomLeftRadiusProp ?? radiusShorthand,
|
|
365
|
+
br: borderBottomRightRadiusProp ?? radiusShorthand
|
|
366
|
+
};
|
|
367
|
+
if (corners.tl === corners.tr && corners.tr === corners.bl && corners.bl === corners.br) {
|
|
368
|
+
boxStyle.borderRadius = corners.tl;
|
|
369
|
+
} else {
|
|
370
|
+
boxStyle.borderTopLeftRadius = corners.tl;
|
|
371
|
+
boxStyle.borderTopRightRadius = corners.tr;
|
|
372
|
+
boxStyle.borderBottomLeftRadius = corners.bl;
|
|
373
|
+
boxStyle.borderBottomRightRadius = corners.br;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Width: collapse to single `borderWidth` when all four sides match.
|
|
377
|
+
if (resolvedWidths.top === resolvedWidths.right && resolvedWidths.right === resolvedWidths.bottom && resolvedWidths.bottom === resolvedWidths.left) {
|
|
378
|
+
boxStyle.borderWidth = resolvedWidths.top;
|
|
379
|
+
} else {
|
|
380
|
+
boxStyle.borderTopWidth = resolvedWidths.top;
|
|
381
|
+
boxStyle.borderRightWidth = resolvedWidths.right;
|
|
382
|
+
boxStyle.borderBottomWidth = resolvedWidths.bottom;
|
|
383
|
+
boxStyle.borderLeftWidth = resolvedWidths.left;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Colour: collapse to single `borderColor` only when both endpoints are
|
|
387
|
+
// identical across all sides. Otherwise emit four `borderXColor`
|
|
388
|
+
// interpolations. Disabled state is static (no animation needed).
|
|
389
|
+
const fromAllEqual = allSidesEqual(sideFrom);
|
|
390
|
+
const toAllEqual = allSidesEqual(sideTo);
|
|
391
|
+
if (disabled) {
|
|
392
|
+
if (fromAllEqual) {
|
|
393
|
+
boxStyle.borderColor = sideFrom.top;
|
|
394
|
+
} else {
|
|
395
|
+
boxStyle.borderTopColor = sideFrom.top;
|
|
396
|
+
boxStyle.borderRightColor = sideFrom.right;
|
|
397
|
+
boxStyle.borderBottomColor = sideFrom.bottom;
|
|
398
|
+
boxStyle.borderLeftColor = sideFrom.left;
|
|
399
|
+
}
|
|
400
|
+
} else if (fromAllEqual && toAllEqual && sideFrom.top === sideFrom.right && sideTo.top === sideTo.right) {
|
|
401
|
+
// Common case: shorthand on both ends → single interpolation.
|
|
402
|
+
boxStyle.borderColor = activeAnim.interpolate({
|
|
403
|
+
inputRange: [0, 1],
|
|
404
|
+
outputRange: [sideFrom.top, sideTo.top]
|
|
405
|
+
});
|
|
406
|
+
} else {
|
|
407
|
+
boxStyle.borderTopColor = activeAnim.interpolate({
|
|
408
|
+
inputRange: [0, 1],
|
|
409
|
+
outputRange: [sideFrom.top, sideTo.top]
|
|
410
|
+
});
|
|
411
|
+
boxStyle.borderRightColor = activeAnim.interpolate({
|
|
412
|
+
inputRange: [0, 1],
|
|
413
|
+
outputRange: [sideFrom.right, sideTo.right]
|
|
414
|
+
});
|
|
415
|
+
boxStyle.borderBottomColor = activeAnim.interpolate({
|
|
416
|
+
inputRange: [0, 1],
|
|
417
|
+
outputRange: [sideFrom.bottom, sideTo.bottom]
|
|
418
|
+
});
|
|
419
|
+
boxStyle.borderLeftColor = activeAnim.interpolate({
|
|
420
|
+
inputRange: [0, 1],
|
|
421
|
+
outputRange: [sideFrom.left, sideTo.left]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
236
424
|
if (width !== undefined) boxStyle.width = width;
|
|
237
425
|
if (height !== undefined) boxStyle.height = height;
|
|
238
426
|
const a11yState = {
|
|
@@ -168,7 +168,7 @@ const Input = exports.Input = /*#__PURE__*/(0, _react.forwardRef)((props, ref) =
|
|
|
168
168
|
// Selection / caret / handle colour walks the same resolution chain as the
|
|
169
169
|
// focused border so brands that override `components.field.<variant>.borderFocused`
|
|
170
170
|
// get a matching selection tint without also having to update root `border.focus`.
|
|
171
|
-
const selectionColor = (0, _react.useMemo)(() => (0, _FieldBase.resolveVariantColors)(theme, variant, fillOverrides).
|
|
171
|
+
const selectionColor = (0, _react.useMemo)(() => (0, _FieldBase.resolveVariantColors)(theme, variant, fillOverrides).borderFocusedRepresentative, [theme, variant, fillOverrides]);
|
|
172
172
|
const borderRadiusOverride = borderRadiusProp ?? theme.components.input?.borderRadius;
|
|
173
173
|
|
|
174
174
|
// Floating label styles
|
|
@@ -40,7 +40,13 @@ const SearchBar = exports.SearchBar = /*#__PURE__*/(0, _react.forwardRef)((props
|
|
|
40
40
|
// field-family token (`components.field.defaultVariant`) → library default.
|
|
41
41
|
// Library default stays `'filled'` so SearchBar reads as the traditional
|
|
42
42
|
// pill-shaped, borderless search box when no theme opinion is expressed.
|
|
43
|
-
|
|
43
|
+
// SearchBar deliberately only supports 'filled' and 'outlined' — an
|
|
44
|
+
// underline-only pill makes no visual sense. If the shared field default is
|
|
45
|
+
// 'underline', fall through to 'filled' rather than carry an unsupported
|
|
46
|
+
// value down to the variant token lookup.
|
|
47
|
+
const sharedDefault = theme.components.field?.defaultVariant;
|
|
48
|
+
const sharedFallback = sharedDefault === 'outlined' || sharedDefault === 'filled' ? sharedDefault : 'filled';
|
|
49
|
+
const variant = variantProp ?? theme.components.searchBar?.defaultVariant ?? sharedFallback;
|
|
44
50
|
const fieldTokens = (0, _FieldBase.resolveFieldSize)(theme, size);
|
|
45
51
|
const sizeStyles = {
|
|
46
52
|
...fieldTokens,
|
|
@@ -184,7 +190,7 @@ const SearchBar = exports.SearchBar = /*#__PURE__*/(0, _react.forwardRef)((props
|
|
|
184
190
|
autoFocus: autoFocus,
|
|
185
191
|
editable: !disabled,
|
|
186
192
|
returnKeyType: "search",
|
|
187
|
-
selectionColor: (0, _FieldBase.resolveVariantColors)(theme, variant).
|
|
193
|
+
selectionColor: (0, _FieldBase.resolveVariantColors)(theme, variant).borderFocusedRepresentative,
|
|
188
194
|
accessibilityLabel: accessibilityLabel ?? placeholder,
|
|
189
195
|
accessibilityState: {
|
|
190
196
|
disabled
|
|
@@ -326,7 +326,7 @@ const Select = exports.Select = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
|
|
|
326
326
|
onChangeText: setQuery,
|
|
327
327
|
placeholder: "Search\u2026",
|
|
328
328
|
placeholderTextColor: theme.colors.text.tertiary,
|
|
329
|
-
selectionColor: (0, _FieldBase.resolveVariantColors)(theme, theme.components.field?.defaultVariant ?? 'outlined').
|
|
329
|
+
selectionColor: (0, _FieldBase.resolveVariantColors)(theme, theme.components.field?.defaultVariant ?? 'outlined').borderFocusedRepresentative,
|
|
330
330
|
accessibilityLabel: "Search options",
|
|
331
331
|
style: [styles.searchInput, {
|
|
332
332
|
color: theme.colors.text.primary,
|
|
@@ -27,6 +27,66 @@
|
|
|
27
27
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
28
28
|
import { Animated, Easing, Pressable, StyleSheet, View } from 'react-native';
|
|
29
29
|
import { useTheme, createAnimatedValue, fontFor } from "../../theme/index.js";
|
|
30
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
31
|
+
const SIDES = ['top', 'right', 'bottom', 'left'];
|
|
32
|
+
|
|
33
|
+
/** Map a single colour string to all four sides (shorthand expansion). */
|
|
34
|
+
const expandColorSides = value => {
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
return {
|
|
37
|
+
top: value,
|
|
38
|
+
right: value,
|
|
39
|
+
bottom: value,
|
|
40
|
+
left: value
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Object form: any side the consumer omitted falls through to 'transparent'.
|
|
44
|
+
// Rationale: if a consumer says `{ bottom: '#000' }`, they want only the
|
|
45
|
+
// bottom drawn — the other sides should be invisible, not inherit some
|
|
46
|
+
// unrelated colour.
|
|
47
|
+
return {
|
|
48
|
+
top: value.top ?? 'transparent',
|
|
49
|
+
right: value.right ?? 'transparent',
|
|
50
|
+
bottom: value.bottom ?? 'transparent',
|
|
51
|
+
left: value.left ?? 'transparent'
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Map a single width number to all four sides (shorthand expansion). */
|
|
56
|
+
const expandWidthSides = value => {
|
|
57
|
+
if (typeof value === 'number') {
|
|
58
|
+
return {
|
|
59
|
+
top: value,
|
|
60
|
+
right: value,
|
|
61
|
+
bottom: value,
|
|
62
|
+
left: value
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
top: value.top ?? 0,
|
|
67
|
+
right: value.right ?? 0,
|
|
68
|
+
bottom: value.bottom ?? 0,
|
|
69
|
+
left: value.left ?? 0
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** Are all four sides of a per-side record equal? Used to collapse to shorthand. */
|
|
74
|
+
const allSidesEqual = sides => sides.top === sides.right && sides.right === sides.bottom && sides.bottom === sides.left;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Pick the most "representative" side colour from a 4-side record. Used by
|
|
78
|
+
* consumers (Input's selectionColor, focus rings, etc.) that need a single
|
|
79
|
+
* colour to mirror the visible focus indicator. Priority: bottom → top →
|
|
80
|
+
* left → right, skipping `'transparent'`. The bottom-first order means
|
|
81
|
+
* underline variants pick their underline colour automatically.
|
|
82
|
+
*/
|
|
83
|
+
export const pickRepresentativeBorderColor = sides => {
|
|
84
|
+
for (const side of ['bottom', 'top', 'left', 'right']) {
|
|
85
|
+
const c = sides[side];
|
|
86
|
+
if (c && c !== 'transparent') return c;
|
|
87
|
+
}
|
|
88
|
+
return sides.bottom;
|
|
89
|
+
};
|
|
30
90
|
|
|
31
91
|
/**
|
|
32
92
|
* Resolved text styling for the editable / displayed content inside a field.
|
|
@@ -36,7 +96,7 @@ import { useTheme, createAnimatedValue, fontFor } from "../../theme/index.js";
|
|
|
36
96
|
* same place. OTPInput intentionally overrides this with its own semibold
|
|
37
97
|
* display weight.
|
|
38
98
|
*/
|
|
39
|
-
|
|
99
|
+
|
|
40
100
|
/**
|
|
41
101
|
* Resolve the canonical text style chunk for a field's value. Reads from
|
|
42
102
|
* `theme.components.field.{textColor,disabledTextColor,placeholderColor,fontWeight}`
|
|
@@ -115,31 +175,70 @@ const DEFAULT_SIZES = {
|
|
|
115
175
|
|
|
116
176
|
/**
|
|
117
177
|
* Strict, fully-resolved colour set used internally. Mirrors `FieldVariantTokens`
|
|
118
|
-
* but every
|
|
119
|
-
*
|
|
178
|
+
* but every border colour / width is expanded to a 4-side record by the time
|
|
179
|
+
* the box renders, so the animation pipeline can treat per-side and shorthand
|
|
180
|
+
* the same way. `borderFocusedRepresentative` is a single colour pulled from
|
|
181
|
+
* the focused sides — exposed for consumers (Input's selectionColor) that
|
|
182
|
+
* need to mirror the visible focus indicator without re-resolving per side.
|
|
120
183
|
*/
|
|
121
184
|
|
|
122
185
|
/**
|
|
123
186
|
* Resolve the full set of state-aware fill + border colours for a given
|
|
124
|
-
* variant. Order: explicit override → theme token → library default.
|
|
187
|
+
* variant. Order: explicit override → theme token → library default. Border
|
|
188
|
+
* colour + width are normalized to per-side records here; callers that need
|
|
189
|
+
* a single colour read `borderFocusedRepresentative`.
|
|
125
190
|
*/
|
|
126
191
|
export const resolveVariantColors = (theme, variant, override) => {
|
|
127
192
|
const tokenSet = theme.components.field?.[variant];
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
193
|
+
|
|
194
|
+
// Variant-specific defaults for resting fill + border treatment. `outlined`
|
|
195
|
+
// draws all four sides; `filled` draws nothing (fill-only); `underline`
|
|
196
|
+
// draws only the bottom edge — Material-style single-line inputs.
|
|
197
|
+
let idleEmpty;
|
|
198
|
+
let borderIdleDefault;
|
|
199
|
+
let borderWidthDefault;
|
|
200
|
+
if (variant === 'outlined') {
|
|
201
|
+
idleEmpty = theme.colors.background.primary;
|
|
202
|
+
borderIdleDefault = theme.colors.border.primary;
|
|
203
|
+
borderWidthDefault = theme.colors.border.width;
|
|
204
|
+
} else if (variant === 'underline') {
|
|
205
|
+
idleEmpty = 'transparent';
|
|
206
|
+
borderIdleDefault = {
|
|
207
|
+
bottom: theme.colors.border.primary
|
|
208
|
+
};
|
|
209
|
+
borderWidthDefault = {
|
|
210
|
+
bottom: theme.colors.border.width
|
|
211
|
+
};
|
|
212
|
+
} else {
|
|
213
|
+
idleEmpty = theme.colors.background.secondary;
|
|
214
|
+
borderIdleDefault = 'transparent';
|
|
215
|
+
borderWidthDefault = 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// For focused/error/disabled we expand using the same per-side shape as
|
|
219
|
+
// idle. If `borderIdle` ended up bottom-only (underline), an unset
|
|
220
|
+
// `borderFocused` defaults to `{ bottom: colors.border.focus }` so the
|
|
221
|
+
// focus indicator stays on the same edge. Same logic for error.
|
|
222
|
+
const wrapToIdleShape = colour => typeof borderIdleDefault === 'string' ? colour : {
|
|
223
|
+
bottom: colour
|
|
224
|
+
};
|
|
225
|
+
const borderIdle = expandColorSides(override?.borderIdle ?? tokenSet?.borderIdle ?? borderIdleDefault);
|
|
226
|
+
const borderFocused = expandColorSides(override?.borderFocused ?? tokenSet?.borderFocused ?? wrapToIdleShape(theme.colors.border.focus));
|
|
227
|
+
const borderError = expandColorSides(override?.borderError ?? tokenSet?.borderError ?? wrapToIdleShape(theme.colors.border.error));
|
|
228
|
+
const borderDisabled = expandColorSides(override?.borderDisabled ?? tokenSet?.borderDisabled ?? borderIdleDefault);
|
|
229
|
+
const borderWidth = expandWidthSides(override?.borderWidth ?? tokenSet?.borderWidth ?? borderWidthDefault);
|
|
132
230
|
return {
|
|
133
231
|
backgroundIdleEmpty: override?.backgroundIdleEmpty ?? tokenSet?.backgroundIdleEmpty ?? idleEmpty,
|
|
134
232
|
backgroundIdleFilled: override?.backgroundIdleFilled ?? tokenSet?.backgroundIdleFilled ?? override?.backgroundIdleEmpty ?? tokenSet?.backgroundIdleEmpty ?? idleEmpty,
|
|
135
233
|
backgroundFocused: override?.backgroundFocused ?? tokenSet?.backgroundFocused ?? undefined,
|
|
136
234
|
backgroundError: override?.backgroundError ?? tokenSet?.backgroundError ?? undefined,
|
|
137
235
|
backgroundDisabled: override?.backgroundDisabled ?? tokenSet?.backgroundDisabled ?? theme.colors.surface.disabled,
|
|
138
|
-
borderIdle
|
|
139
|
-
borderFocused
|
|
140
|
-
borderError
|
|
141
|
-
borderDisabled
|
|
142
|
-
borderWidth
|
|
236
|
+
borderIdle,
|
|
237
|
+
borderFocused,
|
|
238
|
+
borderError,
|
|
239
|
+
borderDisabled,
|
|
240
|
+
borderWidth,
|
|
241
|
+
borderFocusedRepresentative: pickRepresentativeBorderColor(borderFocused)
|
|
143
242
|
};
|
|
144
243
|
};
|
|
145
244
|
export const FieldBase = props => {
|
|
@@ -161,7 +260,15 @@ export const FieldBase = props => {
|
|
|
161
260
|
paddingHorizontal: paddingHorizontalProp,
|
|
162
261
|
paddingVertical: paddingVerticalProp,
|
|
163
262
|
borderRadius: borderRadiusProp,
|
|
263
|
+
borderTopLeftRadius: borderTopLeftRadiusProp,
|
|
264
|
+
borderTopRightRadius: borderTopRightRadiusProp,
|
|
265
|
+
borderBottomLeftRadius: borderBottomLeftRadiusProp,
|
|
266
|
+
borderBottomRightRadius: borderBottomRightRadiusProp,
|
|
164
267
|
borderWidth: borderWidthProp,
|
|
268
|
+
borderTopWidth: borderTopWidthProp,
|
|
269
|
+
borderRightWidth: borderRightWidthProp,
|
|
270
|
+
borderBottomWidth: borderBottomWidthProp,
|
|
271
|
+
borderLeftWidth: borderLeftWidthProp,
|
|
165
272
|
gap: gapProp,
|
|
166
273
|
fillOverrides,
|
|
167
274
|
style,
|
|
@@ -200,13 +307,6 @@ export const FieldBase = props => {
|
|
|
200
307
|
const idleFill = filled ? colors.backgroundIdleFilled : colors.backgroundIdleEmpty;
|
|
201
308
|
const focusedFill = colors.backgroundFocused ?? idleFill;
|
|
202
309
|
const errorFill = colors.backgroundError ?? idleFill;
|
|
203
|
-
const animatedBorderColor = disabled ? colors.borderDisabled : error ? errorAnim.interpolate({
|
|
204
|
-
inputRange: [0, 1],
|
|
205
|
-
outputRange: [colors.borderIdle, colors.borderError]
|
|
206
|
-
}) : focusAnim.interpolate({
|
|
207
|
-
inputRange: [0, 1],
|
|
208
|
-
outputRange: [colors.borderIdle, colors.borderFocused]
|
|
209
|
-
});
|
|
210
310
|
const animatedBackgroundColor = disabled ? colors.backgroundDisabled : error ? errorAnim.interpolate({
|
|
211
311
|
inputRange: [0, 1],
|
|
212
312
|
outputRange: [idleFill, errorFill]
|
|
@@ -214,17 +314,105 @@ export const FieldBase = props => {
|
|
|
214
314
|
inputRange: [0, 1],
|
|
215
315
|
outputRange: [idleFill, focusedFill]
|
|
216
316
|
});
|
|
317
|
+
|
|
318
|
+
// Per-side border colour. For each side we resolve the "from" (idle) and
|
|
319
|
+
// "to" (focused or error, depending on state) colour, then drive a single
|
|
320
|
+
// animated interpolation. When all four sides share the same from+to pair
|
|
321
|
+
// we collapse to one `borderColor` key — the common case stays cheap.
|
|
322
|
+
const activeColors = error ? colors.borderError : colors.borderFocused;
|
|
323
|
+
const activeAnim = error ? errorAnim : focusAnim;
|
|
324
|
+
const sideFrom = disabled ? colors.borderDisabled : colors.borderIdle;
|
|
325
|
+
const sideTo = disabled ? colors.borderDisabled : activeColors;
|
|
326
|
+
|
|
327
|
+
// Per-side widths: longhand prop wins over shorthand prop wins over
|
|
328
|
+
// resolved variant token. Each side independently.
|
|
329
|
+
const propWidth = {
|
|
330
|
+
top: borderTopWidthProp,
|
|
331
|
+
right: borderRightWidthProp,
|
|
332
|
+
bottom: borderBottomWidthProp,
|
|
333
|
+
left: borderLeftWidthProp
|
|
334
|
+
};
|
|
335
|
+
const resolvedWidths = {
|
|
336
|
+
top: propWidth.top ?? borderWidthProp ?? colors.borderWidth.top,
|
|
337
|
+
right: propWidth.right ?? borderWidthProp ?? colors.borderWidth.right,
|
|
338
|
+
bottom: propWidth.bottom ?? borderWidthProp ?? colors.borderWidth.bottom,
|
|
339
|
+
left: propWidth.left ?? borderWidthProp ?? colors.borderWidth.left
|
|
340
|
+
};
|
|
217
341
|
const boxStyle = {
|
|
218
342
|
minHeight: height ?? minHeightProp ?? sizeTokens.minHeight,
|
|
219
343
|
maxHeight,
|
|
220
344
|
paddingHorizontal: paddingHorizontalProp ?? sizeTokens.paddingHorizontal,
|
|
221
345
|
paddingVertical: paddingVerticalProp ?? sizeTokens.paddingVertical,
|
|
222
|
-
borderRadius: borderRadiusProp ?? sizeTokens.borderRadius,
|
|
223
|
-
borderWidth: borderWidthProp ?? colors.borderWidth,
|
|
224
|
-
borderColor: animatedBorderColor,
|
|
225
346
|
backgroundColor: animatedBackgroundColor,
|
|
226
347
|
columnGap: gapProp ?? theme.spacing.sm
|
|
227
348
|
};
|
|
349
|
+
|
|
350
|
+
// Radius: per-corner prop wins over shorthand prop wins over size token.
|
|
351
|
+
// Collapse to single `borderRadius` when all four corners match.
|
|
352
|
+
const radiusShorthand = borderRadiusProp ?? sizeTokens.borderRadius;
|
|
353
|
+
const corners = {
|
|
354
|
+
tl: borderTopLeftRadiusProp ?? radiusShorthand,
|
|
355
|
+
tr: borderTopRightRadiusProp ?? radiusShorthand,
|
|
356
|
+
bl: borderBottomLeftRadiusProp ?? radiusShorthand,
|
|
357
|
+
br: borderBottomRightRadiusProp ?? radiusShorthand
|
|
358
|
+
};
|
|
359
|
+
if (corners.tl === corners.tr && corners.tr === corners.bl && corners.bl === corners.br) {
|
|
360
|
+
boxStyle.borderRadius = corners.tl;
|
|
361
|
+
} else {
|
|
362
|
+
boxStyle.borderTopLeftRadius = corners.tl;
|
|
363
|
+
boxStyle.borderTopRightRadius = corners.tr;
|
|
364
|
+
boxStyle.borderBottomLeftRadius = corners.bl;
|
|
365
|
+
boxStyle.borderBottomRightRadius = corners.br;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Width: collapse to single `borderWidth` when all four sides match.
|
|
369
|
+
if (resolvedWidths.top === resolvedWidths.right && resolvedWidths.right === resolvedWidths.bottom && resolvedWidths.bottom === resolvedWidths.left) {
|
|
370
|
+
boxStyle.borderWidth = resolvedWidths.top;
|
|
371
|
+
} else {
|
|
372
|
+
boxStyle.borderTopWidth = resolvedWidths.top;
|
|
373
|
+
boxStyle.borderRightWidth = resolvedWidths.right;
|
|
374
|
+
boxStyle.borderBottomWidth = resolvedWidths.bottom;
|
|
375
|
+
boxStyle.borderLeftWidth = resolvedWidths.left;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Colour: collapse to single `borderColor` only when both endpoints are
|
|
379
|
+
// identical across all sides. Otherwise emit four `borderXColor`
|
|
380
|
+
// interpolations. Disabled state is static (no animation needed).
|
|
381
|
+
const fromAllEqual = allSidesEqual(sideFrom);
|
|
382
|
+
const toAllEqual = allSidesEqual(sideTo);
|
|
383
|
+
if (disabled) {
|
|
384
|
+
if (fromAllEqual) {
|
|
385
|
+
boxStyle.borderColor = sideFrom.top;
|
|
386
|
+
} else {
|
|
387
|
+
boxStyle.borderTopColor = sideFrom.top;
|
|
388
|
+
boxStyle.borderRightColor = sideFrom.right;
|
|
389
|
+
boxStyle.borderBottomColor = sideFrom.bottom;
|
|
390
|
+
boxStyle.borderLeftColor = sideFrom.left;
|
|
391
|
+
}
|
|
392
|
+
} else if (fromAllEqual && toAllEqual && sideFrom.top === sideFrom.right && sideTo.top === sideTo.right) {
|
|
393
|
+
// Common case: shorthand on both ends → single interpolation.
|
|
394
|
+
boxStyle.borderColor = activeAnim.interpolate({
|
|
395
|
+
inputRange: [0, 1],
|
|
396
|
+
outputRange: [sideFrom.top, sideTo.top]
|
|
397
|
+
});
|
|
398
|
+
} else {
|
|
399
|
+
boxStyle.borderTopColor = activeAnim.interpolate({
|
|
400
|
+
inputRange: [0, 1],
|
|
401
|
+
outputRange: [sideFrom.top, sideTo.top]
|
|
402
|
+
});
|
|
403
|
+
boxStyle.borderRightColor = activeAnim.interpolate({
|
|
404
|
+
inputRange: [0, 1],
|
|
405
|
+
outputRange: [sideFrom.right, sideTo.right]
|
|
406
|
+
});
|
|
407
|
+
boxStyle.borderBottomColor = activeAnim.interpolate({
|
|
408
|
+
inputRange: [0, 1],
|
|
409
|
+
outputRange: [sideFrom.bottom, sideTo.bottom]
|
|
410
|
+
});
|
|
411
|
+
boxStyle.borderLeftColor = activeAnim.interpolate({
|
|
412
|
+
inputRange: [0, 1],
|
|
413
|
+
outputRange: [sideFrom.left, sideTo.left]
|
|
414
|
+
});
|
|
415
|
+
}
|
|
228
416
|
if (width !== undefined) boxStyle.width = width;
|
|
229
417
|
if (height !== undefined) boxStyle.height = height;
|
|
230
418
|
const a11yState = {
|
|
@@ -163,7 +163,7 @@ const Input = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
163
163
|
// Selection / caret / handle colour walks the same resolution chain as the
|
|
164
164
|
// focused border so brands that override `components.field.<variant>.borderFocused`
|
|
165
165
|
// get a matching selection tint without also having to update root `border.focus`.
|
|
166
|
-
const selectionColor = useMemo(() => resolveVariantColors(theme, variant, fillOverrides).
|
|
166
|
+
const selectionColor = useMemo(() => resolveVariantColors(theme, variant, fillOverrides).borderFocusedRepresentative, [theme, variant, fillOverrides]);
|
|
167
167
|
const borderRadiusOverride = borderRadiusProp ?? theme.components.input?.borderRadius;
|
|
168
168
|
|
|
169
169
|
// Floating label styles
|
|
@@ -35,7 +35,13 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
35
35
|
// field-family token (`components.field.defaultVariant`) → library default.
|
|
36
36
|
// Library default stays `'filled'` so SearchBar reads as the traditional
|
|
37
37
|
// pill-shaped, borderless search box when no theme opinion is expressed.
|
|
38
|
-
|
|
38
|
+
// SearchBar deliberately only supports 'filled' and 'outlined' — an
|
|
39
|
+
// underline-only pill makes no visual sense. If the shared field default is
|
|
40
|
+
// 'underline', fall through to 'filled' rather than carry an unsupported
|
|
41
|
+
// value down to the variant token lookup.
|
|
42
|
+
const sharedDefault = theme.components.field?.defaultVariant;
|
|
43
|
+
const sharedFallback = sharedDefault === 'outlined' || sharedDefault === 'filled' ? sharedDefault : 'filled';
|
|
44
|
+
const variant = variantProp ?? theme.components.searchBar?.defaultVariant ?? sharedFallback;
|
|
39
45
|
const fieldTokens = resolveFieldSize(theme, size);
|
|
40
46
|
const sizeStyles = {
|
|
41
47
|
...fieldTokens,
|
|
@@ -179,7 +185,7 @@ const SearchBar = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
179
185
|
autoFocus: autoFocus,
|
|
180
186
|
editable: !disabled,
|
|
181
187
|
returnKeyType: "search",
|
|
182
|
-
selectionColor: resolveVariantColors(theme, variant).
|
|
188
|
+
selectionColor: resolveVariantColors(theme, variant).borderFocusedRepresentative,
|
|
183
189
|
accessibilityLabel: accessibilityLabel ?? placeholder,
|
|
184
190
|
accessibilityState: {
|
|
185
191
|
disabled
|
|
@@ -321,7 +321,7 @@ const Select = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
321
321
|
onChangeText: setQuery,
|
|
322
322
|
placeholder: "Search\u2026",
|
|
323
323
|
placeholderTextColor: theme.colors.text.tertiary,
|
|
324
|
-
selectionColor: resolveVariantColors(theme, theme.components.field?.defaultVariant ?? 'outlined').
|
|
324
|
+
selectionColor: resolveVariantColors(theme, theme.components.field?.defaultVariant ?? 'outlined').borderFocusedRepresentative,
|
|
325
325
|
accessibilityLabel: "Search options",
|
|
326
326
|
style: [styles.searchInput, {
|
|
327
327
|
color: theme.colors.text.primary,
|
|
@@ -23,9 +23,17 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import React from 'react';
|
|
25
25
|
import type { AccessibilityRole, AccessibilityState, StyleProp, TextStyle, ViewStyle } from 'react-native';
|
|
26
|
-
import type { FieldSizeTokens, FieldVariantTokens, Theme } from '../../theme/types';
|
|
26
|
+
import type { FieldSide, FieldSizeTokens, FieldVariantTokens, Theme } from '../../theme/types';
|
|
27
27
|
export type FieldBaseSize = 'sm' | 'md' | 'lg';
|
|
28
|
-
export type FieldBaseVariant = 'outlined' | 'filled';
|
|
28
|
+
export type FieldBaseVariant = 'outlined' | 'filled' | 'underline';
|
|
29
|
+
/**
|
|
30
|
+
* Pick the most "representative" side colour from a 4-side record. Used by
|
|
31
|
+
* consumers (Input's selectionColor, focus rings, etc.) that need a single
|
|
32
|
+
* colour to mirror the visible focus indicator. Priority: bottom → top →
|
|
33
|
+
* left → right, skipping `'transparent'`. The bottom-first order means
|
|
34
|
+
* underline variants pick their underline colour automatically.
|
|
35
|
+
*/
|
|
36
|
+
export declare const pickRepresentativeBorderColor: (sides: Record<FieldSide, string>) => string;
|
|
29
37
|
export interface FieldBaseProps {
|
|
30
38
|
/** Resolves to dimension tokens at `theme.components.field[size]`. Default `'md'`. */
|
|
31
39
|
size?: FieldBaseSize;
|
|
@@ -65,10 +73,26 @@ export interface FieldBaseProps {
|
|
|
65
73
|
paddingHorizontal?: number;
|
|
66
74
|
/** Override vertical padding (single-line). Multiline parents usually leave this default. */
|
|
67
75
|
paddingVertical?: number;
|
|
68
|
-
/** Override corner radius. SearchBar pill uses this to apply `radius.full`. */
|
|
76
|
+
/** Override corner radius (shorthand — all four corners). SearchBar pill uses this to apply `radius.full`. */
|
|
69
77
|
borderRadius?: number;
|
|
70
|
-
/**
|
|
78
|
+
/** Top-left corner radius. Wins over `borderRadius`. */
|
|
79
|
+
borderTopLeftRadius?: number;
|
|
80
|
+
/** Top-right corner radius. Wins over `borderRadius`. */
|
|
81
|
+
borderTopRightRadius?: number;
|
|
82
|
+
/** Bottom-left corner radius. Wins over `borderRadius`. */
|
|
83
|
+
borderBottomLeftRadius?: number;
|
|
84
|
+
/** Bottom-right corner radius. Wins over `borderRadius`. */
|
|
85
|
+
borderBottomRightRadius?: number;
|
|
86
|
+
/** Override border width (shorthand — all four sides). OTP uses 1.5; everything else inherits `colors.border.width`. */
|
|
71
87
|
borderWidth?: number;
|
|
88
|
+
/** Top border width. Wins over `borderWidth`. */
|
|
89
|
+
borderTopWidth?: number;
|
|
90
|
+
/** Right border width. Wins over `borderWidth`. */
|
|
91
|
+
borderRightWidth?: number;
|
|
92
|
+
/** Bottom border width. Wins over `borderWidth`. */
|
|
93
|
+
borderBottomWidth?: number;
|
|
94
|
+
/** Left border width. Wins over `borderWidth`. */
|
|
95
|
+
borderLeftWidth?: number;
|
|
72
96
|
/** Gap between leading / children / trailing. Defaults to `theme.spacing.sm`. */
|
|
73
97
|
gap?: number;
|
|
74
98
|
fillOverrides?: Partial<FieldVariantTokens>;
|
|
@@ -114,8 +138,11 @@ export declare const resolveFieldTextStyle: (theme: Theme, options?: FieldTextSt
|
|
|
114
138
|
export declare const resolveFieldSize: (theme: Theme, size: FieldBaseSize) => FieldSizeTokens;
|
|
115
139
|
/**
|
|
116
140
|
* Strict, fully-resolved colour set used internally. Mirrors `FieldVariantTokens`
|
|
117
|
-
* but every
|
|
118
|
-
*
|
|
141
|
+
* but every border colour / width is expanded to a 4-side record by the time
|
|
142
|
+
* the box renders, so the animation pipeline can treat per-side and shorthand
|
|
143
|
+
* the same way. `borderFocusedRepresentative` is a single colour pulled from
|
|
144
|
+
* the focused sides — exposed for consumers (Input's selectionColor) that
|
|
145
|
+
* need to mirror the visible focus indicator without re-resolving per side.
|
|
119
146
|
*/
|
|
120
147
|
interface ResolvedFieldColors {
|
|
121
148
|
backgroundIdleEmpty: string;
|
|
@@ -125,15 +152,19 @@ interface ResolvedFieldColors {
|
|
|
125
152
|
/** Optional override fill while in error — when undefined, error animates from idle to idle. */
|
|
126
153
|
backgroundError: string | undefined;
|
|
127
154
|
backgroundDisabled: string;
|
|
128
|
-
borderIdle: string
|
|
129
|
-
borderFocused: string
|
|
130
|
-
borderError: string
|
|
131
|
-
borderDisabled: string
|
|
132
|
-
borderWidth: number
|
|
155
|
+
borderIdle: Record<FieldSide, string>;
|
|
156
|
+
borderFocused: Record<FieldSide, string>;
|
|
157
|
+
borderError: Record<FieldSide, string>;
|
|
158
|
+
borderDisabled: Record<FieldSide, string>;
|
|
159
|
+
borderWidth: Record<FieldSide, number>;
|
|
160
|
+
/** Single representative colour from `borderFocused` — first non-transparent side, bottom-priority. */
|
|
161
|
+
borderFocusedRepresentative: string;
|
|
133
162
|
}
|
|
134
163
|
/**
|
|
135
164
|
* Resolve the full set of state-aware fill + border colours for a given
|
|
136
|
-
* variant. Order: explicit override → theme token → library default.
|
|
165
|
+
* variant. Order: explicit override → theme token → library default. Border
|
|
166
|
+
* colour + width are normalized to per-side records here; callers that need
|
|
167
|
+
* a single colour read `borderFocusedRepresentative`.
|
|
137
168
|
*/
|
|
138
169
|
export declare const resolveVariantColors: (theme: Theme, variant: FieldBaseVariant, override?: Partial<FieldVariantTokens>) => ResolvedFieldColors;
|
|
139
170
|
export declare const FieldBase: React.FC<FieldBaseProps>;
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { TextInput } from 'react-native';
|
|
3
3
|
import type { BlurEvent, FocusEvent, StyleProp, TextInputProps, TextStyle, ViewStyle } from 'react-native';
|
|
4
4
|
export type InputSize = 'sm' | 'md' | 'lg';
|
|
5
|
-
export type InputVariant = 'outlined' | 'filled';
|
|
5
|
+
export type InputVariant = 'outlined' | 'filled' | 'underline';
|
|
6
6
|
export type InputLabelMode = 'float' | 'top';
|
|
7
7
|
export interface InputProps extends Omit<TextInputProps, 'style'> {
|
|
8
8
|
label?: string;
|
|
@@ -391,12 +391,33 @@ export interface FieldSizeTokens {
|
|
|
391
391
|
borderRadius: number;
|
|
392
392
|
iconSize: number;
|
|
393
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* One of the four field-box sides. Used everywhere the library accepts
|
|
396
|
+
* per-side overrides for border width / colour.
|
|
397
|
+
*/
|
|
398
|
+
export type FieldSide = 'top' | 'right' | 'bottom' | 'left';
|
|
399
|
+
/**
|
|
400
|
+
* Shorthand-or-longhand value. A bare `T` means "apply to all four sides";
|
|
401
|
+
* an object form lets a consumer set specific sides while the others fall
|
|
402
|
+
* through to whatever the resolver decided. Mirrors how React Native's
|
|
403
|
+
* `borderWidth` + `borderXWidth` (and CSS `border` + `border-X`) compose.
|
|
404
|
+
*/
|
|
405
|
+
export type FieldPerSide<T> = T | Partial<Record<FieldSide, T>>;
|
|
406
|
+
/** Border colour value: a single colour string or a per-side colour map. */
|
|
407
|
+
export type FieldBorderColor = FieldPerSide<string>;
|
|
408
|
+
/** Border width value: a single width or a per-side width map. */
|
|
409
|
+
export type FieldBorderWidth = FieldPerSide<number>;
|
|
394
410
|
/**
|
|
395
411
|
* State-aware fill + border colours for a field variant. Resolution order
|
|
396
412
|
* inside FieldBase: variant token → library default. Idle distinguishes
|
|
397
413
|
* empty vs filled so consumers can tint the resting field differently once
|
|
398
414
|
* the user has typed. When focused/error/disabled fills are omitted, the
|
|
399
415
|
* box animates from the current idle fill to itself (no fill change).
|
|
416
|
+
*
|
|
417
|
+
* Border colour + width are `FieldPerSide` values — pass a bare string /
|
|
418
|
+
* number to set all four sides (the common case), or an object like
|
|
419
|
+
* `{ bottom: '#1A73E8' }` to draw only one side. Sides not specified in the
|
|
420
|
+
* object fall through to the shorthand default.
|
|
400
421
|
*/
|
|
401
422
|
export interface FieldVariantTokens {
|
|
402
423
|
backgroundIdleEmpty: string;
|
|
@@ -404,18 +425,20 @@ export interface FieldVariantTokens {
|
|
|
404
425
|
backgroundFocused?: string;
|
|
405
426
|
backgroundError?: string;
|
|
406
427
|
backgroundDisabled: string;
|
|
407
|
-
borderIdle:
|
|
408
|
-
borderFocused:
|
|
409
|
-
borderError:
|
|
410
|
-
borderDisabled:
|
|
411
|
-
borderWidth:
|
|
428
|
+
borderIdle: FieldBorderColor;
|
|
429
|
+
borderFocused: FieldBorderColor;
|
|
430
|
+
borderError: FieldBorderColor;
|
|
431
|
+
borderDisabled: FieldBorderColor;
|
|
432
|
+
borderWidth: FieldBorderWidth;
|
|
412
433
|
}
|
|
413
434
|
export interface FieldTokens extends Partial<Record<'sm' | 'md' | 'lg', FieldSizeTokens>> {
|
|
414
435
|
/** Default `variant` when caller doesn't pass one. Library default: 'outlined'. */
|
|
415
|
-
defaultVariant?: 'outlined' | 'filled';
|
|
436
|
+
defaultVariant?: 'outlined' | 'filled' | 'underline';
|
|
416
437
|
/** State-aware fill + border colours per variant. */
|
|
417
438
|
outlined?: Partial<FieldVariantTokens>;
|
|
418
439
|
filled?: Partial<FieldVariantTokens>;
|
|
440
|
+
/** Bottom-border-only variant — Material-style underline inputs. */
|
|
441
|
+
underline?: Partial<FieldVariantTokens>;
|
|
419
442
|
/** Text colour for the editable / displayed value. Defaults to `colors.text.primary`. */
|
|
420
443
|
textColor?: string;
|
|
421
444
|
/** Text colour when the field is disabled. Defaults to `colors.text.disabled`. */
|
|
@@ -439,7 +462,7 @@ export interface InputFillTokens {
|
|
|
439
462
|
}
|
|
440
463
|
export interface InputComponentTokens extends Partial<Record<ComponentSizeKey, InputSizeTokens>> {
|
|
441
464
|
/** Default `variant` when caller doesn't pass one. Library default: 'outlined'. */
|
|
442
|
-
defaultVariant?: 'outlined' | 'filled';
|
|
465
|
+
defaultVariant?: 'outlined' | 'filled' | 'underline';
|
|
443
466
|
/** Default `labelMode` when caller doesn't pass one. Library default: 'float'. */
|
|
444
467
|
defaultLabelMode?: 'float' | 'top';
|
|
445
468
|
/** Corner radius in pixels for the field wrapper. Library default: size-driven (sm=10, md=12, lg=14). */
|
|
@@ -468,6 +491,7 @@ export interface InputComponentTokens extends Partial<Record<ComponentSizeKey, I
|
|
|
468
491
|
fillColors?: {
|
|
469
492
|
outlined?: InputFillTokens;
|
|
470
493
|
filled?: InputFillTokens;
|
|
494
|
+
underline?: InputFillTokens;
|
|
471
495
|
};
|
|
472
496
|
}
|
|
473
497
|
export interface BottomSheetTokens {
|
|
@@ -23,9 +23,17 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import React from 'react';
|
|
25
25
|
import type { AccessibilityRole, AccessibilityState, StyleProp, TextStyle, ViewStyle } from 'react-native';
|
|
26
|
-
import type { FieldSizeTokens, FieldVariantTokens, Theme } from '../../theme/types';
|
|
26
|
+
import type { FieldSide, FieldSizeTokens, FieldVariantTokens, Theme } from '../../theme/types';
|
|
27
27
|
export type FieldBaseSize = 'sm' | 'md' | 'lg';
|
|
28
|
-
export type FieldBaseVariant = 'outlined' | 'filled';
|
|
28
|
+
export type FieldBaseVariant = 'outlined' | 'filled' | 'underline';
|
|
29
|
+
/**
|
|
30
|
+
* Pick the most "representative" side colour from a 4-side record. Used by
|
|
31
|
+
* consumers (Input's selectionColor, focus rings, etc.) that need a single
|
|
32
|
+
* colour to mirror the visible focus indicator. Priority: bottom → top →
|
|
33
|
+
* left → right, skipping `'transparent'`. The bottom-first order means
|
|
34
|
+
* underline variants pick their underline colour automatically.
|
|
35
|
+
*/
|
|
36
|
+
export declare const pickRepresentativeBorderColor: (sides: Record<FieldSide, string>) => string;
|
|
29
37
|
export interface FieldBaseProps {
|
|
30
38
|
/** Resolves to dimension tokens at `theme.components.field[size]`. Default `'md'`. */
|
|
31
39
|
size?: FieldBaseSize;
|
|
@@ -65,10 +73,26 @@ export interface FieldBaseProps {
|
|
|
65
73
|
paddingHorizontal?: number;
|
|
66
74
|
/** Override vertical padding (single-line). Multiline parents usually leave this default. */
|
|
67
75
|
paddingVertical?: number;
|
|
68
|
-
/** Override corner radius. SearchBar pill uses this to apply `radius.full`. */
|
|
76
|
+
/** Override corner radius (shorthand — all four corners). SearchBar pill uses this to apply `radius.full`. */
|
|
69
77
|
borderRadius?: number;
|
|
70
|
-
/**
|
|
78
|
+
/** Top-left corner radius. Wins over `borderRadius`. */
|
|
79
|
+
borderTopLeftRadius?: number;
|
|
80
|
+
/** Top-right corner radius. Wins over `borderRadius`. */
|
|
81
|
+
borderTopRightRadius?: number;
|
|
82
|
+
/** Bottom-left corner radius. Wins over `borderRadius`. */
|
|
83
|
+
borderBottomLeftRadius?: number;
|
|
84
|
+
/** Bottom-right corner radius. Wins over `borderRadius`. */
|
|
85
|
+
borderBottomRightRadius?: number;
|
|
86
|
+
/** Override border width (shorthand — all four sides). OTP uses 1.5; everything else inherits `colors.border.width`. */
|
|
71
87
|
borderWidth?: number;
|
|
88
|
+
/** Top border width. Wins over `borderWidth`. */
|
|
89
|
+
borderTopWidth?: number;
|
|
90
|
+
/** Right border width. Wins over `borderWidth`. */
|
|
91
|
+
borderRightWidth?: number;
|
|
92
|
+
/** Bottom border width. Wins over `borderWidth`. */
|
|
93
|
+
borderBottomWidth?: number;
|
|
94
|
+
/** Left border width. Wins over `borderWidth`. */
|
|
95
|
+
borderLeftWidth?: number;
|
|
72
96
|
/** Gap between leading / children / trailing. Defaults to `theme.spacing.sm`. */
|
|
73
97
|
gap?: number;
|
|
74
98
|
fillOverrides?: Partial<FieldVariantTokens>;
|
|
@@ -114,8 +138,11 @@ export declare const resolveFieldTextStyle: (theme: Theme, options?: FieldTextSt
|
|
|
114
138
|
export declare const resolveFieldSize: (theme: Theme, size: FieldBaseSize) => FieldSizeTokens;
|
|
115
139
|
/**
|
|
116
140
|
* Strict, fully-resolved colour set used internally. Mirrors `FieldVariantTokens`
|
|
117
|
-
* but every
|
|
118
|
-
*
|
|
141
|
+
* but every border colour / width is expanded to a 4-side record by the time
|
|
142
|
+
* the box renders, so the animation pipeline can treat per-side and shorthand
|
|
143
|
+
* the same way. `borderFocusedRepresentative` is a single colour pulled from
|
|
144
|
+
* the focused sides — exposed for consumers (Input's selectionColor) that
|
|
145
|
+
* need to mirror the visible focus indicator without re-resolving per side.
|
|
119
146
|
*/
|
|
120
147
|
interface ResolvedFieldColors {
|
|
121
148
|
backgroundIdleEmpty: string;
|
|
@@ -125,15 +152,19 @@ interface ResolvedFieldColors {
|
|
|
125
152
|
/** Optional override fill while in error — when undefined, error animates from idle to idle. */
|
|
126
153
|
backgroundError: string | undefined;
|
|
127
154
|
backgroundDisabled: string;
|
|
128
|
-
borderIdle: string
|
|
129
|
-
borderFocused: string
|
|
130
|
-
borderError: string
|
|
131
|
-
borderDisabled: string
|
|
132
|
-
borderWidth: number
|
|
155
|
+
borderIdle: Record<FieldSide, string>;
|
|
156
|
+
borderFocused: Record<FieldSide, string>;
|
|
157
|
+
borderError: Record<FieldSide, string>;
|
|
158
|
+
borderDisabled: Record<FieldSide, string>;
|
|
159
|
+
borderWidth: Record<FieldSide, number>;
|
|
160
|
+
/** Single representative colour from `borderFocused` — first non-transparent side, bottom-priority. */
|
|
161
|
+
borderFocusedRepresentative: string;
|
|
133
162
|
}
|
|
134
163
|
/**
|
|
135
164
|
* Resolve the full set of state-aware fill + border colours for a given
|
|
136
|
-
* variant. Order: explicit override → theme token → library default.
|
|
165
|
+
* variant. Order: explicit override → theme token → library default. Border
|
|
166
|
+
* colour + width are normalized to per-side records here; callers that need
|
|
167
|
+
* a single colour read `borderFocusedRepresentative`.
|
|
137
168
|
*/
|
|
138
169
|
export declare const resolveVariantColors: (theme: Theme, variant: FieldBaseVariant, override?: Partial<FieldVariantTokens>) => ResolvedFieldColors;
|
|
139
170
|
export declare const FieldBase: React.FC<FieldBaseProps>;
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { TextInput } from 'react-native';
|
|
3
3
|
import type { BlurEvent, FocusEvent, StyleProp, TextInputProps, TextStyle, ViewStyle } from 'react-native';
|
|
4
4
|
export type InputSize = 'sm' | 'md' | 'lg';
|
|
5
|
-
export type InputVariant = 'outlined' | 'filled';
|
|
5
|
+
export type InputVariant = 'outlined' | 'filled' | 'underline';
|
|
6
6
|
export type InputLabelMode = 'float' | 'top';
|
|
7
7
|
export interface InputProps extends Omit<TextInputProps, 'style'> {
|
|
8
8
|
label?: string;
|
|
@@ -391,12 +391,33 @@ export interface FieldSizeTokens {
|
|
|
391
391
|
borderRadius: number;
|
|
392
392
|
iconSize: number;
|
|
393
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* One of the four field-box sides. Used everywhere the library accepts
|
|
396
|
+
* per-side overrides for border width / colour.
|
|
397
|
+
*/
|
|
398
|
+
export type FieldSide = 'top' | 'right' | 'bottom' | 'left';
|
|
399
|
+
/**
|
|
400
|
+
* Shorthand-or-longhand value. A bare `T` means "apply to all four sides";
|
|
401
|
+
* an object form lets a consumer set specific sides while the others fall
|
|
402
|
+
* through to whatever the resolver decided. Mirrors how React Native's
|
|
403
|
+
* `borderWidth` + `borderXWidth` (and CSS `border` + `border-X`) compose.
|
|
404
|
+
*/
|
|
405
|
+
export type FieldPerSide<T> = T | Partial<Record<FieldSide, T>>;
|
|
406
|
+
/** Border colour value: a single colour string or a per-side colour map. */
|
|
407
|
+
export type FieldBorderColor = FieldPerSide<string>;
|
|
408
|
+
/** Border width value: a single width or a per-side width map. */
|
|
409
|
+
export type FieldBorderWidth = FieldPerSide<number>;
|
|
394
410
|
/**
|
|
395
411
|
* State-aware fill + border colours for a field variant. Resolution order
|
|
396
412
|
* inside FieldBase: variant token → library default. Idle distinguishes
|
|
397
413
|
* empty vs filled so consumers can tint the resting field differently once
|
|
398
414
|
* the user has typed. When focused/error/disabled fills are omitted, the
|
|
399
415
|
* box animates from the current idle fill to itself (no fill change).
|
|
416
|
+
*
|
|
417
|
+
* Border colour + width are `FieldPerSide` values — pass a bare string /
|
|
418
|
+
* number to set all four sides (the common case), or an object like
|
|
419
|
+
* `{ bottom: '#1A73E8' }` to draw only one side. Sides not specified in the
|
|
420
|
+
* object fall through to the shorthand default.
|
|
400
421
|
*/
|
|
401
422
|
export interface FieldVariantTokens {
|
|
402
423
|
backgroundIdleEmpty: string;
|
|
@@ -404,18 +425,20 @@ export interface FieldVariantTokens {
|
|
|
404
425
|
backgroundFocused?: string;
|
|
405
426
|
backgroundError?: string;
|
|
406
427
|
backgroundDisabled: string;
|
|
407
|
-
borderIdle:
|
|
408
|
-
borderFocused:
|
|
409
|
-
borderError:
|
|
410
|
-
borderDisabled:
|
|
411
|
-
borderWidth:
|
|
428
|
+
borderIdle: FieldBorderColor;
|
|
429
|
+
borderFocused: FieldBorderColor;
|
|
430
|
+
borderError: FieldBorderColor;
|
|
431
|
+
borderDisabled: FieldBorderColor;
|
|
432
|
+
borderWidth: FieldBorderWidth;
|
|
412
433
|
}
|
|
413
434
|
export interface FieldTokens extends Partial<Record<'sm' | 'md' | 'lg', FieldSizeTokens>> {
|
|
414
435
|
/** Default `variant` when caller doesn't pass one. Library default: 'outlined'. */
|
|
415
|
-
defaultVariant?: 'outlined' | 'filled';
|
|
436
|
+
defaultVariant?: 'outlined' | 'filled' | 'underline';
|
|
416
437
|
/** State-aware fill + border colours per variant. */
|
|
417
438
|
outlined?: Partial<FieldVariantTokens>;
|
|
418
439
|
filled?: Partial<FieldVariantTokens>;
|
|
440
|
+
/** Bottom-border-only variant — Material-style underline inputs. */
|
|
441
|
+
underline?: Partial<FieldVariantTokens>;
|
|
419
442
|
/** Text colour for the editable / displayed value. Defaults to `colors.text.primary`. */
|
|
420
443
|
textColor?: string;
|
|
421
444
|
/** Text colour when the field is disabled. Defaults to `colors.text.disabled`. */
|
|
@@ -439,7 +462,7 @@ export interface InputFillTokens {
|
|
|
439
462
|
}
|
|
440
463
|
export interface InputComponentTokens extends Partial<Record<ComponentSizeKey, InputSizeTokens>> {
|
|
441
464
|
/** Default `variant` when caller doesn't pass one. Library default: 'outlined'. */
|
|
442
|
-
defaultVariant?: 'outlined' | 'filled';
|
|
465
|
+
defaultVariant?: 'outlined' | 'filled' | 'underline';
|
|
443
466
|
/** Default `labelMode` when caller doesn't pass one. Library default: 'float'. */
|
|
444
467
|
defaultLabelMode?: 'float' | 'top';
|
|
445
468
|
/** Corner radius in pixels for the field wrapper. Library default: size-driven (sm=10, md=12, lg=14). */
|
|
@@ -468,6 +491,7 @@ export interface InputComponentTokens extends Partial<Record<ComponentSizeKey, I
|
|
|
468
491
|
fillColors?: {
|
|
469
492
|
outlined?: InputFillTokens;
|
|
470
493
|
filled?: InputFillTokens;
|
|
494
|
+
underline?: InputFillTokens;
|
|
471
495
|
};
|
|
472
496
|
}
|
|
473
497
|
export interface BottomSheetTokens {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority-technologies/mobile",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Beautiful, animated, accessible React Native components plus API/auth/logging/network/storage utilities for Webority projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|