fluent-styles 1.62.14 → 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 (81) 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 +32 -8
  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 +32 -8
  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 +20 -1
  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 +56 -10
  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
@@ -285,7 +285,19 @@ function spinnerColor(props: StyledButtonProps): string {
285
285
 
286
286
  // ─── Button ──────────────────────────────────────────────────────────────────
287
287
 
288
- const Button = ({ children, loading, leftIcon, rightIcon, disabled, ref, ...rest }: StyledButtonProps & { ref?: React.Ref<React.ComponentRef<typeof ButtonBase>> }) => {
288
+ // `ref` is destructured straight from props above the button, which only
289
+ // works unpatched on React 19's "ref as prop" model — on React 18 (still the
290
+ // baseline for RN/Expo consumers of this library) a plain function component
291
+ // can never receive a ref at all; React strips it before the function runs.
292
+ // `forwardRef` is the compatible way to accept a ref on both, so wrap here
293
+ // instead of destructuring `ref` out of the props object directly. Any
294
+ // caller relying on that ref — e.g. StyledDropdown's Trigger, which measures
295
+ // this button's on-screen position to place its option panel — silently got
296
+ // `null` without this, and the panel fell back to {top:0,left:0,width:0}.
297
+ const Button = React.forwardRef<
298
+ React.ComponentRef<typeof ButtonBase>,
299
+ StyledButtonProps
300
+ >(({ children, loading, leftIcon, rightIcon, disabled, ...rest }, ref) => {
289
301
  const isDisabled = disabled || loading
290
302
 
291
303
  return (
@@ -315,7 +327,7 @@ const Button = ({ children, loading, leftIcon, rightIcon, disabled, ref, ...rest
315
327
  {!loading && rightIcon ? rightIcon : null}
316
328
  </ButtonBase>
317
329
  );
318
- };
330
+ });
319
331
 
320
332
  Button.displayName = 'StyledButton'
321
333
 
@@ -799,7 +799,11 @@ const InputTrigger: React.FC<{
799
799
  borderRadius={12}
800
800
  paddingHorizontal={14}
801
801
  paddingVertical={12}
802
- backgroundColor={palettes.white}
802
+ // PATCHED: was hard-coded to palettes.white regardless of theme — the
803
+ // "background" token is already themeable via the `colors` prop (it
804
+ // already drives the calendar sheet below), so reuse it here too
805
+ // instead of introducing a second, disconnected background colour.
806
+ backgroundColor={C.background}
803
807
  onPress={onPress}
804
808
  disabled={disabled}
805
809
  >
@@ -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}
@@ -126,7 +126,29 @@ export interface StyledTextInputProps
126
126
  // ── Pass-through style props (from original) ───────────────────────────
127
127
  fontSize?: number
128
128
  fontWeight?: TextStyle['fontWeight']
129
-
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
+
139
+ }
140
+
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
130
152
  }
131
153
 
132
154
  // ─── Flat style-prop resolution ────────────────────────────────────────────────
@@ -138,7 +160,13 @@ export interface StyledTextInputProps
138
160
  // instead of through the shared HOC.
139
161
  const BASE_INPUT_STYLE: TextStyle = {
140
162
  borderColor: theme.colors.gray[200],
141
- backgroundColor: theme.colors.gray[1],
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',
142
170
  flex: 1,
143
171
  color: theme.colors.gray[800],
144
172
  paddingHorizontal: 16,
@@ -212,7 +240,9 @@ function variantWrapStyle(
212
240
  switch (variant) {
213
241
  case 'filled':
214
242
  return {
215
- 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,
216
246
  borderWidth: focused ? 1.5 : 0,
217
247
  borderColor: bc,
218
248
  }
@@ -229,8 +259,11 @@ function variantWrapStyle(
229
259
  borderWidth: 0,
230
260
  }
231
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.
232
265
  return {
233
- backgroundColor: '#fff',
266
+ backgroundColor: bg,
234
267
  borderWidth: focused ? 1.5 : 1,
235
268
  borderColor: bc,
236
269
  }
@@ -379,6 +412,7 @@ export const StyledTextInput = (
379
412
  borderColor: borderColorProp,
380
413
  focusColor = theme.colors.indigo?.[500] ?? '#6366f1',
381
414
  floatLabel = false,
415
+ colors: colorsProp,
382
416
 
383
417
  // Styles
384
418
  containerStyle,
@@ -443,8 +477,20 @@ export const StyledTextInput = (
443
477
  const hasError = error || !!errorMessage
444
478
  const sz = SIZE[size]
445
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
+
446
492
  const resolvedBorderColor = borderColorProp
447
- ?? (hasError ? theme.colors.red[500] : theme.colors.gray[200])
493
+ ?? (hasError ? theme.colors.red[500] : FC.border)
448
494
 
449
495
  const resolvedFocusColor = hasError
450
496
  ? theme.colors.red[500]
@@ -453,7 +499,7 @@ export const StyledTextInput = (
453
499
  const wrapStyle = variantWrapStyle(
454
500
  variant,
455
501
  resolvedBorderColor,
456
- theme.colors.gray[50] ?? '#fafafa',
502
+ FC.background,
457
503
  focused,
458
504
  resolvedFocusColor,
459
505
  )
@@ -492,8 +538,8 @@ export const StyledTextInput = (
492
538
  <StyledText
493
539
  fontSize={theme.fontSize.small}
494
540
  fontWeight={theme.fontWeight.semiBold}
495
- color={theme.colors.gray[700]}
496
-
541
+ color={FC.label}
542
+
497
543
  marginBottom={6}
498
544
  {...labelProps}
499
545
  >
@@ -559,7 +605,7 @@ export const StyledTextInput = (
559
605
  onFocus={handleFocus}
560
606
  onBlur={handleBlur}
561
607
  placeholder={floatLabel ? undefined : placeholder}
562
- placeholderTextColor={theme.colors.gray[300]}
608
+ placeholderTextColor={FC.placeholder}
563
609
  editable={editable && !loading}
564
610
  multiline={multiline}
565
611
  numberOfLines={numberOfLines}
@@ -574,7 +620,7 @@ export const StyledTextInput = (
574
620
  paddingVertical: sz.paddingV,
575
621
  minHeight: multiline ? sz.minHeight * 2 : sz.minHeight,
576
622
  opacity: editable && !loading ? 1 : 0.55,
577
- color: theme.colors.gray[800],
623
+ color: FC.text,
578
624
  textAlignVertical: multiline ? 'top' : 'center',
579
625
  },
580
626
  inputStyle,
@@ -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"