fluent-styles 1.62.13 → 1.62.15

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.
Files changed (88) hide show
  1. package/lib/commonjs/button/index.js +12 -4
  2. package/lib/commonjs/button/index.js.map +1 -1
  3. package/lib/commonjs/datePicker/index.js +7 -2
  4. package/lib/commonjs/datePicker/index.js.map +1 -1
  5. package/lib/commonjs/dropdown/index.js +49 -16
  6. package/lib/commonjs/dropdown/index.js.map +1 -1
  7. package/lib/commonjs/input/index.js +87 -44
  8. package/lib/commonjs/input/index.js.map +1 -1
  9. package/lib/commonjs/page/index.js +24 -3
  10. package/lib/commonjs/page/index.js.map +1 -1
  11. package/lib/commonjs/pressable/index.js +6 -0
  12. package/lib/commonjs/pressable/index.js.map +1 -1
  13. package/lib/commonjs/safeAreaView/index.js +10 -0
  14. package/lib/commonjs/safeAreaView/index.js.map +1 -1
  15. package/lib/commonjs/utiles/styled.js +12 -2
  16. package/lib/commonjs/utiles/styled.js.map +1 -1
  17. package/lib/module/button/index.js +12 -4
  18. package/lib/module/button/index.js.map +1 -1
  19. package/lib/module/datePicker/index.js +7 -2
  20. package/lib/module/datePicker/index.js.map +1 -1
  21. package/lib/module/dropdown/index.js +49 -16
  22. package/lib/module/dropdown/index.js.map +1 -1
  23. package/lib/module/input/index.js +87 -44
  24. package/lib/module/input/index.js.map +1 -1
  25. package/lib/module/page/index.js +25 -4
  26. package/lib/module/page/index.js.map +1 -1
  27. package/lib/module/pressable/index.js +7 -1
  28. package/lib/module/pressable/index.js.map +1 -1
  29. package/lib/module/safeAreaView/index.js +11 -1
  30. package/lib/module/safeAreaView/index.js.map +1 -1
  31. package/lib/module/utiles/styled.js +12 -2
  32. package/lib/module/utiles/styled.js.map +1 -1
  33. package/lib/typescript/badge/index.d.ts +1 -6
  34. package/lib/typescript/badge/index.d.ts.map +1 -1
  35. package/lib/typescript/button/index.d.ts +3 -8
  36. package/lib/typescript/button/index.d.ts.map +1 -1
  37. package/lib/typescript/card/index.d.ts +1 -6
  38. package/lib/typescript/card/index.d.ts.map +1 -1
  39. package/lib/typescript/checkBox/index.d.ts +1 -6
  40. package/lib/typescript/checkBox/index.d.ts.map +1 -1
  41. package/lib/typescript/datePicker/index.d.ts.map +1 -1
  42. package/lib/typescript/dialog/index.d.ts +1 -6
  43. package/lib/typescript/dialog/index.d.ts.map +1 -1
  44. package/lib/typescript/divider/index.d.ts +1 -6
  45. package/lib/typescript/divider/index.d.ts.map +1 -1
  46. package/lib/typescript/dropdown/index.d.ts +27 -2
  47. package/lib/typescript/dropdown/index.d.ts.map +1 -1
  48. package/lib/typescript/image/index.d.ts +1 -6
  49. package/lib/typescript/image/index.d.ts.map +1 -1
  50. package/lib/typescript/input/index.d.ts +21 -8
  51. package/lib/typescript/input/index.d.ts.map +1 -1
  52. package/lib/typescript/page/index.d.ts.map +1 -1
  53. package/lib/typescript/pressable/index.d.ts +1 -6
  54. package/lib/typescript/pressable/index.d.ts.map +1 -1
  55. package/lib/typescript/safeAreaProvider/index.d.ts +1 -6
  56. package/lib/typescript/safeAreaProvider/index.d.ts.map +1 -1
  57. package/lib/typescript/safeAreaView/index.d.ts +1 -6
  58. package/lib/typescript/safeAreaView/index.d.ts.map +1 -1
  59. package/lib/typescript/scrollView/index.d.ts +1 -6
  60. package/lib/typescript/scrollView/index.d.ts.map +1 -1
  61. package/lib/typescript/shape/cycle.d.ts +1 -6
  62. package/lib/typescript/shape/cycle.d.ts.map +1 -1
  63. package/lib/typescript/shape/index.d.ts +1 -6
  64. package/lib/typescript/shape/index.d.ts.map +1 -1
  65. package/lib/typescript/spacer/index.d.ts +1 -6
  66. package/lib/typescript/spacer/index.d.ts.map +1 -1
  67. package/lib/typescript/stack/index.d.ts +3 -18
  68. package/lib/typescript/stack/index.d.ts.map +1 -1
  69. package/lib/typescript/text/index.d.ts +1 -6
  70. package/lib/typescript/text/index.d.ts.map +1 -1
  71. package/lib/typescript/utiles/styled.d.ts +1 -6
  72. package/lib/typescript/utiles/styled.d.ts.map +1 -1
  73. package/package.json +1 -1
  74. package/src/button/index.tsx +14 -2
  75. package/src/datePicker/index.tsx +5 -1
  76. package/src/dropdown/index.tsx +72 -11
  77. package/src/input/index.tsx +111 -48
  78. package/src/page/index.tsx +36 -3
  79. package/src/pressable/index.tsx +8 -2
  80. package/src/safeAreaView/index.tsx +11 -1
  81. package/src/utiles/styled.tsx +12 -3
  82. package/lib/commonjs/portal/portal.test.js +0 -154
  83. package/lib/commonjs/portal/portal.test.js.map +0 -1
  84. package/lib/module/portal/portal.test.js +0 -155
  85. package/lib/module/portal/portal.test.js.map +0 -1
  86. package/lib/typescript/portal/portal.test.d.ts +0 -11
  87. package/lib/typescript/portal/portal.test.d.ts.map +0 -1
  88. package/src/portal/portal.test.ts +0 -157
@@ -81,6 +81,32 @@ interface DropdownBaseProps<TItem extends DropdownOptionItem>
81
81
  emptyText?: string;
82
82
  focusColor?: string;
83
83
  groupBy?: (item: TItem) => string;
84
+ /**
85
+ * Resting-surface + option-panel colour overrides — lets a consumer theme
86
+ * the dropdown (e.g. for dark mode) without the trigger's own focus/error
87
+ * border logic needing to change. A nested `colors` object (rather than
88
+ * a flat `backgroundColor` prop) avoids colliding with the `ViewStyle`
89
+ * props this interface already extends, which style the outer wrapper,
90
+ * not the trigger box.
91
+ */
92
+ colors?: StyledDropdownColors;
93
+ }
94
+
95
+ export interface StyledDropdownColors {
96
+ /** Trigger box resting background. @default '#fff' */
97
+ background?: string;
98
+ /** Trigger box resting border (overridden by focus/error). @default gray[200] */
99
+ border?: string;
100
+ /** Selected value text colour. @default gray[800] */
101
+ text?: string;
102
+ /** Open option-panel background. @default '#fff' */
103
+ panelBackground?: string;
104
+ /** Open option-panel border. @default gray[200] */
105
+ panelBorder?: string;
106
+ /** Option row label text colour. @default gray[800] */
107
+ panelText?: string;
108
+ /** Dividers / section-header strip background inside the panel. @default gray[100] */
109
+ panelDivider?: string;
84
110
  }
85
111
 
86
112
  export interface StyledDropdownProps<
@@ -221,11 +247,12 @@ const OptionRow: React.FC<{
221
247
  multiSelect: boolean;
222
248
  size: DropdownSize;
223
249
  focusColor: string;
224
- }> = ({ item, selected, onPress, multiSelect, size, focusColor }) => {
250
+ textColor: string;
251
+ }> = ({ item, selected, onPress, multiSelect, size, focusColor, textColor: baseTextColor }) => {
225
252
  const sz = SZ[size];
226
253
  const textColor = item.disabled
227
254
  ? theme.colors.gray[300]
228
- : theme.colors.gray[800];
255
+ : baseTextColor;
229
256
 
230
257
  return (
231
258
  <StyledButton
@@ -309,6 +336,7 @@ interface ListPanelProps {
309
336
  allSelected?: boolean;
310
337
  onSelectAll?: () => void;
311
338
  onPanelLayout?: (height: number) => void;
339
+ colors: Required<StyledDropdownColors>;
312
340
  }
313
341
 
314
342
  type ListEntry = DropdownOptionItem | { _header: string };
@@ -333,6 +361,7 @@ const ListPanel: React.FC<ListPanelProps> = ({
333
361
  allSelected,
334
362
  onSelectAll,
335
363
  onPanelLayout,
364
+ colors,
336
365
  }) => {
337
366
  const [query, setQuery] = useState("");
338
367
  const sz = SZ[size];
@@ -376,7 +405,7 @@ const ListPanel: React.FC<ListPanelProps> = ({
376
405
  paddingHorizontal={sz.px}
377
406
  paddingTop={10}
378
407
  paddingBottom={4}
379
- backgroundColor={theme.colors.gray[50]}
408
+ backgroundColor={colors.panelDivider}
380
409
  >
381
410
  <StyledText
382
411
  fontSize={11}
@@ -398,10 +427,11 @@ const ListPanel: React.FC<ListPanelProps> = ({
398
427
  multiSelect={multiSelect}
399
428
  size={size}
400
429
  focusColor={focusColor}
430
+ textColor={colors.panelText}
401
431
  />
402
432
  );
403
433
  },
404
- [selectedKeys, onSelect, multiSelect, size, focusColor, sz.px],
434
+ [selectedKeys, onSelect, multiSelect, size, focusColor, sz.px, colors.panelDivider, colors.panelText],
405
435
  );
406
436
 
407
437
  const keyExtractor = useCallback(
@@ -427,10 +457,10 @@ const ListPanel: React.FC<ListPanelProps> = ({
427
457
  left={position.left}
428
458
  width={position.width}
429
459
  maxHeight={maxHeight}
430
- backgroundColor={theme.colors.white}
460
+ backgroundColor={colors.panelBackground}
431
461
  borderRadius={10}
432
462
  borderWidth={1}
433
- borderColor={theme.colors.gray[200]}
463
+ borderColor={colors.panelBorder}
434
464
  shadowColor={theme.colors.gray[900]}
435
465
  shadowOffset={{
436
466
  width: 0,
@@ -545,7 +575,7 @@ const ListPanel: React.FC<ListPanelProps> = ({
545
575
  renderItem={renderItem}
546
576
  ItemSeparatorComponent={() => (
547
577
  <StyledDivider
548
- backgroundColor={theme.colors.gray[100]}
578
+ backgroundColor={colors.panelDivider}
549
579
  height={1}
550
580
  />
551
581
  )}
@@ -634,6 +664,7 @@ const Trigger: React.FC<{
634
664
  hasError: boolean;
635
665
  focusColor: string;
636
666
  leftIcon?: React.ReactNode;
667
+ colors: Required<StyledDropdownColors>;
637
668
  }> = ({
638
669
  triggerRef,
639
670
  onPress,
@@ -648,24 +679,25 @@ const Trigger: React.FC<{
648
679
  hasError,
649
680
  focusColor,
650
681
  leftIcon,
682
+ colors,
651
683
  }) => {
652
684
  const sz = SZ[size];
653
685
  const opacity = disabled ? 0.5 : 1;
654
- const textColor = isPlaceholder ? placeholderColor : theme.colors.gray[800];
686
+ const textColor = isPlaceholder ? placeholderColor : colors.text;
655
687
 
656
688
  // Border colour logic
657
689
  const borderColor = hasError
658
690
  ? theme.colors.red[500]
659
691
  : open
660
692
  ? focusColor
661
- : theme.colors.gray[200];
693
+ : colors.border;
662
694
 
663
695
  // Variant-specific styles
664
696
  const variantStyle: ViewStyle = (() => {
665
697
  switch (variant) {
666
698
  case "filled":
667
699
  return {
668
- backgroundColor: open ? theme.colors.white : theme.colors.gray[100],
700
+ backgroundColor: colors.background,
669
701
  borderWidth: 0,
670
702
  borderBottomWidth: open ? 2 : 0,
671
703
  borderBottomColor: borderColor,
@@ -686,7 +718,7 @@ const Trigger: React.FC<{
686
718
  };
687
719
  default: // outline
688
720
  return {
689
- backgroundColor: theme.colors.white,
721
+ backgroundColor: colors.background,
690
722
  borderWidth: open ? 1.5 : 1,
691
723
  borderColor: borderColor,
692
724
  borderRadius: 8,
@@ -810,6 +842,7 @@ export const StyledDropdown = (
810
842
  emptyText = "No options found",
811
843
  focusColor = theme.colors.indigo?.[500] ?? "#6366f1",
812
844
  groupBy,
845
+ colors: colorsProp,
813
846
  ref,
814
847
  ...rest
815
848
  }: StyledDropdownProps & { ref?: React.Ref<any> },
@@ -819,6 +852,18 @@ export const StyledDropdown = (
819
852
  const { triggerRef, pos, measure, updatePanelHeight } =
820
853
  useMeasure(maxHeight);
821
854
 
855
+ // PATCHED: resolved once here so both the closed Trigger and the open
856
+ // ListPanel share one themed palette instead of each hard-coding white.
857
+ const RC: Required<StyledDropdownColors> = {
858
+ background: colorsProp?.background ?? theme.colors.white,
859
+ border: colorsProp?.border ?? theme.colors.gray[200],
860
+ text: colorsProp?.text ?? theme.colors.gray[800],
861
+ panelBackground: colorsProp?.panelBackground ?? theme.colors.white,
862
+ panelBorder: colorsProp?.panelBorder ?? theme.colors.gray[200],
863
+ panelText: colorsProp?.panelText ?? theme.colors.gray[800],
864
+ panelDivider: colorsProp?.panelDivider ?? theme.colors.gray[100],
865
+ };
866
+
822
867
  const selected = value ?? internalValue;
823
868
  const hasError = error || !!errorMessage;
824
869
  const selectedItem = useMemo(
@@ -885,6 +930,7 @@ export const StyledDropdown = (
885
930
  hasError={hasError}
886
931
  focusColor={focusColor}
887
932
  leftIcon={leftIcon}
933
+ colors={RC}
888
934
  />
889
935
  {open && (
890
936
  <ListPanel
@@ -897,6 +943,7 @@ export const StyledDropdown = (
897
943
  multiSelect={false}
898
944
  searchable={searchable}
899
945
  searchPlaceholder={searchPlaceholder}
946
+ colors={RC}
900
947
  clearable={clearable}
901
948
  onClear={handleClear}
902
949
  emptyText={emptyText}
@@ -943,6 +990,7 @@ export const StyledMultiSelectDropdown = (
943
990
  separator = ", ",
944
991
  maxDisplay = 3,
945
992
  selectAll = false,
993
+ colors: colorsProp,
946
994
  ref,
947
995
  ...rest
948
996
  }: StyledMultiSelectDropdownProps & { ref?: React.Ref<any> },
@@ -951,6 +999,17 @@ export const StyledMultiSelectDropdown = (
951
999
  const [internalKeys, setInternalKeys] = useState<string[]>(
952
1000
  defaultValue ?? [],
953
1001
  );
1002
+
1003
+ // PATCHED: see StyledDropdown above — same shared-palette rationale.
1004
+ const RC: Required<StyledDropdownColors> = {
1005
+ background: colorsProp?.background ?? theme.colors.white,
1006
+ border: colorsProp?.border ?? theme.colors.gray[200],
1007
+ text: colorsProp?.text ?? theme.colors.gray[800],
1008
+ panelBackground: colorsProp?.panelBackground ?? theme.colors.white,
1009
+ panelBorder: colorsProp?.panelBorder ?? theme.colors.gray[200],
1010
+ panelText: colorsProp?.panelText ?? theme.colors.gray[800],
1011
+ panelDivider: colorsProp?.panelDivider ?? theme.colors.gray[100],
1012
+ };
954
1013
  const { triggerRef, pos, measure, updatePanelHeight } = useMeasure(maxHeight);
955
1014
 
956
1015
  const selectedKeys = useMemo(
@@ -1042,9 +1101,11 @@ export const StyledMultiSelectDropdown = (
1042
1101
  hasError={hasError}
1043
1102
  focusColor={focusColor}
1044
1103
  leftIcon={leftIcon}
1104
+ colors={RC}
1045
1105
  />
1046
1106
  {open && (
1047
1107
  <ListPanel
1108
+ colors={RC}
1048
1109
  data={data}
1049
1110
  selectedKeys={selectedKeys}
1050
1111
  onSelect={handleSelect}
@@ -15,13 +15,10 @@ import {
15
15
  View,
16
16
  } from 'react-native'
17
17
  import type {
18
- NativeSyntheticEvent,
19
- TextInputFocusEventData,
20
18
  TextStyle,
21
19
  ViewStyle,
22
20
  } from 'react-native'
23
21
 
24
- import { styled } from '../utiles/styled'
25
22
  import { theme } from '../utiles/theme'
26
23
  import { viewStyleVariants, viewStyleStringVariants } from '../utiles/viewStyleVariants'
27
24
  import { StyledText } from '../text'
@@ -129,41 +126,83 @@ export interface StyledTextInputProps
129
126
  // ── Pass-through style props (from original) ───────────────────────────
130
127
  fontSize?: number
131
128
  fontWeight?: TextStyle['fontWeight']
132
-
129
+
130
+ // ── Theme override ──────────────────────────────────────────────────────
131
+ /**
132
+ * Resting-state surface colours. Lets a consumer theme this field (e.g.
133
+ * for dark mode) without fighting the focus/error border logic below —
134
+ * those stay computed internally and only fall back to `colors.border`
135
+ * when neither focused nor in an error state.
136
+ */
137
+ colors?: StyledFieldColors
138
+
133
139
  }
134
140
 
135
- const TextInputBase = styled<CardComponentProps>(TextInput, {
136
- base: {
137
- borderColor: theme.colors.gray[200],
138
- backgroundColor: theme.colors.gray[1],
139
- flex : 1,
140
- color: theme.colors.gray[800],
141
- paddingHorizontal: 16,
142
- paddingVertical: 12,
143
- fontSize: theme.fontSize.normal,
144
- minHeight: 48,
145
- },
146
- variants: {
147
- ...viewStyleVariants,
148
- ...viewStyleStringVariants,
149
-
150
- fontSize: (selected: string) => {
151
- const size = selected || theme.fontSize.normal;
152
- if (isNaN(Number(size))) {
153
- // throw new Error('Invalid fontSize value');
154
- }
155
- return { fontSize: Number(size) };
156
- },
141
+ export interface StyledFieldColors {
142
+ /** Resting background of the input box. @default '#fff' (outline/filled) */
143
+ background?: string
144
+ /** Resting border colour (overridden by focus/error). @default gray[200] */
145
+ border?: string
146
+ /** Typed text colour. @default gray[800] */
147
+ text?: string
148
+ /** Placeholder text colour. @default gray[300] */
149
+ placeholder?: string
150
+ /** Label text colour — `labelProps.color` still wins if also set. @default gray[700] */
151
+ label?: string
152
+ }
157
153
 
158
- fontWeight: (selected: string) => {
159
- const weight = selected || theme.fontWeight.normal;
160
- if (isNaN(Number(weight))) {
161
- // throw new Error('Invalid fontWeight value');
162
- }
163
- return { fontWeight: weight as any };
164
- },
165
- }
166
- });
154
+ // ─── Flat style-prop resolution ────────────────────────────────────────────────
155
+ // Replaces the generic `styled()` wrapper that used to sit around TextInput.
156
+ // Preserves its behaviour exactly: any flat ViewStyle/TextStyle prop (e.g.
157
+ // `opacity`, `padding`, `borderColor`) passed straight to StyledTextInput is
158
+ // resolved into a real style on the inner TextInput and stripped out of the
159
+ // props spread onto it same variant maps `styled()` used, resolved locally
160
+ // instead of through the shared HOC.
161
+ const BASE_INPUT_STYLE: TextStyle = {
162
+ borderColor: theme.colors.gray[200],
163
+ // PATCHED: was theme.colors.gray[1] (white) — this style lands on the
164
+ // *inner* native TextInput, layered on top of the wrap View that
165
+ // `variantWrapStyle`/`FC.background` already colour. Nothing downstream
166
+ // ever overrode it, so every input rendered an opaque white rectangle
167
+ // regardless of the wrap's themed colour. Transparent lets the wrap's
168
+ // colour actually show through.
169
+ backgroundColor: 'transparent',
170
+ flex: 1,
171
+ color: theme.colors.gray[800],
172
+ paddingHorizontal: 16,
173
+ paddingVertical: 12,
174
+ fontSize: theme.fontSize.normal,
175
+ minHeight: 48,
176
+ }
177
+
178
+ const FLAT_STYLE_VARIANTS: Record<string, any> = {
179
+ ...viewStyleVariants,
180
+ ...viewStyleStringVariants,
181
+ }
182
+
183
+ const resolveFlatStyle = (
184
+ props: Record<string, any>,
185
+ ): { style: TextStyle; clean: Record<string, any> } => {
186
+ const style: TextStyle = { ...BASE_INPUT_STYLE }
187
+ const clean: Record<string, any> = { ...props }
188
+
189
+ Object.keys(FLAT_STYLE_VARIANTS).forEach((key) => {
190
+ if (!(key in clean)) return
191
+ const selected = clean[key]
192
+ delete clean[key]
193
+
194
+ const variant = FLAT_STYLE_VARIANTS[key]
195
+ if (typeof variant === 'function') {
196
+ const resolved = variant(selected, props)
197
+ if (resolved) Object.assign(style, resolved)
198
+ } else if (variant?.[selected]) {
199
+ const value = variant[selected]
200
+ Object.assign(style, typeof value === 'function' ? value(selected, props) : value)
201
+ }
202
+ })
203
+
204
+ return { style, clean }
205
+ }
167
206
 
168
207
  export interface StyledTextInputHandle extends StyledTextInputProps {
169
208
  focus: () => void
@@ -201,7 +240,9 @@ function variantWrapStyle(
201
240
  switch (variant) {
202
241
  case 'filled':
203
242
  return {
204
- backgroundColor: focused ? '#fff' : bg,
243
+ // PATCHED: used to hard-jump to '#fff' on focus, which fought any
244
+ // themed `bg` (e.g. a dark surface would flash white on tap).
245
+ backgroundColor: bg,
205
246
  borderWidth: focused ? 1.5 : 0,
206
247
  borderColor: bc,
207
248
  }
@@ -218,8 +259,11 @@ function variantWrapStyle(
218
259
  borderWidth: 0,
219
260
  }
220
261
  default: // outline
262
+ // PATCHED: used to hard-code '#fff', ignoring the `bg` param entirely
263
+ // — the one line responsible for every "white input on a dark
264
+ // screen" bug. `bg` now carries the caller's themed surface colour.
221
265
  return {
222
- backgroundColor: '#fff',
266
+ backgroundColor: bg,
223
267
  borderWidth: focused ? 1.5 : 1,
224
268
  borderColor: bc,
225
269
  }
@@ -368,6 +412,7 @@ export const StyledTextInput = (
368
412
  borderColor: borderColorProp,
369
413
  focusColor = theme.colors.indigo?.[500] ?? '#6366f1',
370
414
  floatLabel = false,
415
+ colors: colorsProp,
371
416
 
372
417
  // Styles
373
418
  containerStyle,
@@ -396,7 +441,7 @@ export const StyledTextInput = (
396
441
  onBlur: onBlurProp,
397
442
  ref,
398
443
  ...rest
399
- }: StyledTextInputProps & { ref?: React.Ref<React.ComponentRef<typeof TextInputBase>> },
444
+ }: StyledTextInputProps & { ref?: React.Ref<StyledTextInputHandle> },
400
445
  ) => {
401
446
  const [focused, setFocused] = useState(false)
402
447
  const [localValue, setLocalValue] = useState(defaultValue ?? '')
@@ -418,12 +463,12 @@ export const StyledTextInput = (
418
463
  onChangeText?.(text)
419
464
  }, [value, onChangeText])
420
465
 
421
- const handleFocus = useCallback((e: NativeSyntheticEvent<TextInputFocusEventData>) => {
466
+ const handleFocus = useCallback((e: Parameters<NonNullable<TextInputProps['onFocus']>>[0]) => {
422
467
  setFocused(true)
423
468
  onFocusProp?.(e)
424
469
  }, [onFocusProp])
425
470
 
426
- const handleBlur = useCallback((e: NativeSyntheticEvent<TextInputFocusEventData>) => {
471
+ const handleBlur = useCallback((e: Parameters<NonNullable<TextInputProps['onBlur']>>[0]) => {
427
472
  setFocused(false)
428
473
  onBlurProp?.(e)
429
474
  }, [onBlurProp])
@@ -432,8 +477,20 @@ export const StyledTextInput = (
432
477
  const hasError = error || !!errorMessage
433
478
  const sz = SIZE[size]
434
479
 
480
+ // PATCHED: `colors` lets a consumer theme the resting surface (e.g. for
481
+ // dark mode) without touching the focus/error logic below — those stay
482
+ // computed here and only fall back to `FC.border` when neither focused
483
+ // nor erroring, so a themed border never masks a validation error.
484
+ const FC = {
485
+ background: colorsProp?.background ?? (theme.colors.gray[50] ?? '#fafafa'),
486
+ border: colorsProp?.border ?? theme.colors.gray[200],
487
+ text: colorsProp?.text ?? theme.colors.gray[800],
488
+ placeholder: colorsProp?.placeholder ?? theme.colors.gray[300],
489
+ label: colorsProp?.label ?? theme.colors.gray[700],
490
+ }
491
+
435
492
  const resolvedBorderColor = borderColorProp
436
- ?? (hasError ? theme.colors.red[500] : theme.colors.gray[200])
493
+ ?? (hasError ? theme.colors.red[500] : FC.border)
437
494
 
438
495
  const resolvedFocusColor = hasError
439
496
  ? theme.colors.red[500]
@@ -442,7 +499,7 @@ export const StyledTextInput = (
442
499
  const wrapStyle = variantWrapStyle(
443
500
  variant,
444
501
  resolvedBorderColor,
445
- theme.colors.gray[50] ?? '#fafafa',
502
+ FC.background,
446
503
  focused,
447
504
  resolvedFocusColor,
448
505
  )
@@ -451,6 +508,11 @@ export const StyledTextInput = (
451
508
  const hasLeftAddon = !!leftAddon
452
509
  const hasRightAddon = !!rightAddon
453
510
 
511
+ // Resolves any flat ViewStyle/TextStyle prop passed straight to
512
+ // StyledTextInput (e.g. `opacity`, `padding`) into a real style, and
513
+ // strips those keys out of what gets spread onto the native TextInput.
514
+ const { style: flatStyle, clean: cleanRest } = resolveFlatStyle(rest)
515
+
454
516
  // ── Right-side icons (priority: loading > clear > rightIcon) ─────────
455
517
  const resolvedRightNode = loading ? (
456
518
  <ActivityIndicator size="small" color={theme.colors.gray[400]} />
@@ -476,8 +538,8 @@ export const StyledTextInput = (
476
538
  <StyledText
477
539
  fontSize={theme.fontSize.small}
478
540
  fontWeight={theme.fontWeight.semiBold}
479
- color={theme.colors.gray[700]}
480
-
541
+ color={FC.label}
542
+
481
543
  marginBottom={6}
482
544
  {...labelProps}
483
545
  >
@@ -535,7 +597,7 @@ export const StyledTextInput = (
535
597
  ) : null}
536
598
 
537
599
  {/* Core TextInput */}
538
- <TextInputBase
600
+ <TextInput
539
601
  ref={inputRef}
540
602
  value={value}
541
603
  defaultValue={defaultValue}
@@ -543,12 +605,13 @@ export const StyledTextInput = (
543
605
  onFocus={handleFocus}
544
606
  onBlur={handleBlur}
545
607
  placeholder={floatLabel ? undefined : placeholder}
546
- placeholderTextColor={theme.colors.gray[300]}
608
+ placeholderTextColor={FC.placeholder}
547
609
  editable={editable && !loading}
548
610
  multiline={multiline}
549
611
  numberOfLines={numberOfLines}
550
612
  maxLength={maxLength}
551
613
  style={[
614
+ flatStyle,
552
615
  S.input,
553
616
  {
554
617
  fontSize: fontSizeProp ?? sz.fontSize,
@@ -557,12 +620,12 @@ export const StyledTextInput = (
557
620
  paddingVertical: sz.paddingV,
558
621
  minHeight: multiline ? sz.minHeight * 2 : sz.minHeight,
559
622
  opacity: editable && !loading ? 1 : 0.55,
560
- color: theme.colors.gray[800],
623
+ color: FC.text,
561
624
  textAlignVertical: multiline ? 'top' : 'center',
562
625
  },
563
626
  inputStyle,
564
627
  ]}
565
- {...rest}
628
+ {...cleanRest}
566
629
  />
567
630
 
568
631
  {/* Right icon / clear / spinner */}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { StyledSafeAreaView, StyledSafeAreaViewProps } from '../safeAreaView';
3
- import { StatusBar, StatusBarProps } from 'react-native';
3
+ import { StatusBar, StatusBarProps, Platform, useColorScheme } from 'react-native';
4
4
  import { StyledHeader } from '../header';
5
5
 
6
6
  interface StyledPageProps extends StyledSafeAreaViewProps {
@@ -19,10 +19,43 @@ interface StyledPageType {
19
19
  displayName?: string;
20
20
  }
21
21
 
22
- const Page = ({ children, statusBarProps, showStatusBar = true, statusBarStyle = 'dark-content', statusBarBackgroundColor, hideStatusBarOnAndroid, hideStatusBarOnIOS, translucentStatusBar, ...props }: React.PropsWithChildren<StyledPageProps>) => {
22
+ // statusBarStyle used to default to a hardcoded 'dark-content' regardless of
23
+ // the active theme — any screen rendered in dark mode that forgot to pass
24
+ // its own statusBarStyle got dark (near-invisible) status bar icons on a
25
+ // dark background. Falls back to the OS color scheme via useColorScheme()
26
+ // instead, the same signal a theme-aware caller would use for its own
27
+ // explicit statusBarStyle.
28
+ //
29
+ // translucentStatusBar now also defaults to true on Android specifically
30
+ // (via Platform.select): with edge-to-edge unavoidable from targetSdk 35+,
31
+ // StatusBar's `backgroundColor` prop is deprecated and silently ignored on
32
+ // Android there, so a non-translucent bar left the *native* window
33
+ // background (white by default) showing behind the status bar instead of
34
+ // the page's own backgroundColor. iOS's status bar is always an overlay, so
35
+ // it's left unaffected (translucentStatusBar stays undefined there unless
36
+ // the caller sets it).
37
+ const Page = ({
38
+ children, statusBarProps, showStatusBar = true,
39
+ statusBarStyle, statusBarBackgroundColor,
40
+ hideStatusBarOnAndroid, hideStatusBarOnIOS,
41
+ translucentStatusBar,
42
+ ...props
43
+ }: React.PropsWithChildren<StyledPageProps>) => {
44
+ const colorScheme = useColorScheme();
45
+ const resolvedStatusBarStyle = statusBarStyle ?? (colorScheme === 'dark' ? 'light-content' : 'dark-content');
46
+ const resolvedTranslucent = translucentStatusBar ?? Platform.select({ android: true, default: undefined });
47
+
23
48
  return (
24
49
  <StyledSafeAreaView {...props}>
25
- {showStatusBar && <StatusBar hidden={hideStatusBarOnAndroid || hideStatusBarOnIOS} barStyle={statusBarStyle} backgroundColor={statusBarBackgroundColor} translucent={translucentStatusBar} {...statusBarProps} />}
50
+ {showStatusBar && (
51
+ <StatusBar
52
+ hidden={hideStatusBarOnAndroid || hideStatusBarOnIOS}
53
+ barStyle={resolvedStatusBarStyle}
54
+ backgroundColor={statusBarBackgroundColor}
55
+ translucent={resolvedTranslucent}
56
+ {...statusBarProps}
57
+ />
58
+ )}
26
59
  {children}
27
60
  </StyledSafeAreaView>
28
61
  )
@@ -2,16 +2,22 @@
2
2
  import { Pressable, PressableProps } from 'react-native';
3
3
  import { styled } from '../utiles/styled';
4
4
  import { ViewStyle } from '../utiles/viewStyleProps';
5
- import { viewStyleStringVariants } from '../utiles/viewStyleVariants';
5
+ import { viewStyleVariants, viewStyleStringVariants } from '../utiles/viewStyleVariants';
6
6
 
7
7
  type StyledPressableProps = PressableProps & ViewStyle;
8
8
 
9
+ // Same missing-viewStyleVariants gap as StyledSafeAreaView (see that file
10
+ // for the full explanation): width/height/borderRadius/backgroundColor
11
+ // passed to StyledPressable were silently dropped instead of styled —
12
+ // e.g. an icon button built as <StyledPressable width={44} height={44}
13
+ // borderRadius={22} backgroundColor={...}> rendered with none of that.
9
14
  const StyledPressable = styled<StyledPressableProps>(Pressable, {
10
15
  base: {
11
16
  position: 'relative',
12
17
  } as ViewStyle,
13
18
  variants: {
14
- ...viewStyleStringVariants
19
+ ...viewStyleVariants,
20
+ ...viewStyleStringVariants,
15
21
  } as any
16
22
  });
17
23
 
@@ -4,15 +4,25 @@ import {
4
4
  } from 'react-native-safe-area-context';
5
5
  import { styled } from '../utiles/styled';
6
6
  import { ViewStyleProps, ViewStyle } from '../utiles/viewStyleProps';
7
- import { viewStyleStringVariants } from '../utiles/viewStyleVariants';
7
+ import { viewStyleVariants, viewStyleStringVariants } from '../utiles/viewStyleVariants';
8
8
 
9
9
  type StyledSafeAreaViewProps = SafeAreaViewProps & ViewStyleProps;
10
10
 
11
+ // This only spread viewStyleStringVariants, so backgroundColor/padding/
12
+ // margin/width/height/borderRadius/etc — everything defined in
13
+ // viewStyleVariants instead — were never registered as variants here. They
14
+ // fell through as raw unhandled props on SafeAreaView instead of landing in
15
+ // `style`, so e.g. <StyledPage backgroundColor="#111"> silently did nothing.
16
+ // Stack already spreads both and works correctly, which is what exposed the
17
+ // gap: on Android specifically, a screen that relied on StyledPage's
18
+ // backgroundColor to paint the safe area showed the native window's default
19
+ // white background through instead.
11
20
  const StyledSafeAreaView = styled<StyledSafeAreaViewProps>(SafeAreaView, {
12
21
  base: {
13
22
  flex: 1,
14
23
  } as ViewStyle,
15
24
  variants: {
25
+ ...viewStyleVariants,
16
26
  ...viewStyleStringVariants,
17
27
  }
18
28
  });
@@ -17,7 +17,16 @@ const styled = <P extends object>(
17
17
  Component: ComponentType<P>,
18
18
  { base, variants }: StyledOptions = {}
19
19
  ) => {
20
- const StyledComponent = (props: P & { ref?: React.Ref<any> }) => {
20
+ // `ref` used to be handled via a plain function reading it off props —
21
+ // that only works under React 19's "ref as prop" model. Consumers still
22
+ // on React 18 (the current baseline for RN/Expo) get a plain function
23
+ // component that can never receive a ref at all (React strips it before
24
+ // the function runs), so any ref attached to a styled() component —
25
+ // including this library's own internals, e.g. StyledDropdown's Trigger
26
+ // measuring its own on-screen position — silently no-ops with a
27
+ // "Function components cannot be given refs" warning. forwardRef is the
28
+ // version that works on both React 18 and 19.
29
+ const StyledComponent = React.forwardRef<any, P>((props, ref) => {
21
30
  const styles: Style = { ...(base || {}) };
22
31
  const options = props as Record<string, any>;
23
32
  const incomingStyle = options.style;
@@ -39,8 +48,8 @@ const styled = <P extends object>(
39
48
  });
40
49
  }
41
50
 
42
- return <Component {...(cleanProps as P)} style={[styles, incomingStyle]} />;
43
- };
51
+ return <Component {...(cleanProps as P)} ref={ref} style={[styles, incomingStyle]} />;
52
+ });
44
53
 
45
54
  StyledComponent.displayName = `Styled(${
46
55
  typeof Component === "string" ? Component : Component.displayName ?? Component.name ?? "Component"