@korsolutions/ui 0.0.89 → 0.0.91

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 (89) hide show
  1. package/README.md +21 -0
  2. package/dist/module/components/avatar/components/avatar-image.js.map +1 -1
  3. package/dist/module/components/badge/badge.js +1 -6
  4. package/dist/module/components/badge/badge.js.map +1 -1
  5. package/dist/module/components/badge/variants/default.js +3 -2
  6. package/dist/module/components/badge/variants/default.js.map +1 -1
  7. package/dist/module/components/badge/variants/secondary.js +2 -1
  8. package/dist/module/components/badge/variants/secondary.js.map +1 -1
  9. package/dist/module/components/card/card-title.js +2 -3
  10. package/dist/module/components/card/card-title.js.map +1 -1
  11. package/dist/module/components/combobox/components/combobox-list.js +24 -0
  12. package/dist/module/components/combobox/components/combobox-list.js.map +1 -0
  13. package/dist/module/components/combobox/components/combobox-option.js +19 -23
  14. package/dist/module/components/combobox/components/combobox-option.js.map +1 -1
  15. package/dist/module/components/combobox/components/combobox-overlay.js +0 -1
  16. package/dist/module/components/combobox/components/combobox-overlay.js.map +1 -1
  17. package/dist/module/components/combobox/components/combobox-root.js +62 -11
  18. package/dist/module/components/combobox/components/combobox-root.js.map +1 -1
  19. package/dist/module/components/combobox/components/combobox-trigger.js +3 -2
  20. package/dist/module/components/combobox/components/combobox-trigger.js.map +1 -1
  21. package/dist/module/components/combobox/context.js.map +1 -1
  22. package/dist/module/components/combobox/index.js +2 -0
  23. package/dist/module/components/combobox/index.js.map +1 -1
  24. package/dist/module/components/menu/components/menu-selection-indicator.js +4 -3
  25. package/dist/module/components/menu/components/menu-selection-indicator.js.map +1 -1
  26. package/dist/module/components/portal/portal.js.map +1 -1
  27. package/dist/module/components/select/components/select-option.js +11 -12
  28. package/dist/module/components/select/components/select-option.js.map +1 -1
  29. package/dist/module/components/select/components/select-trigger.js +3 -1
  30. package/dist/module/components/select/components/select-trigger.js.map +1 -1
  31. package/dist/module/hooks/use-relative-position.js.map +1 -1
  32. package/dist/module/utils/normalize-layout.js.map +1 -1
  33. package/dist/typescript/src/components/avatar/components/avatar-image.d.ts +2 -2
  34. package/dist/typescript/src/components/avatar/components/avatar-image.d.ts.map +1 -1
  35. package/dist/typescript/src/components/badge/badge.d.ts.map +1 -1
  36. package/dist/typescript/src/components/badge/variants/default.d.ts.map +1 -1
  37. package/dist/typescript/src/components/badge/variants/secondary.d.ts.map +1 -1
  38. package/dist/typescript/src/components/card/card-title.d.ts +2 -2
  39. package/dist/typescript/src/components/card/card-title.d.ts.map +1 -1
  40. package/dist/typescript/src/components/combobox/components/combobox-list.d.ts +12 -0
  41. package/dist/typescript/src/components/combobox/components/combobox-list.d.ts.map +1 -0
  42. package/dist/typescript/src/components/combobox/components/combobox-option.d.ts +3 -4
  43. package/dist/typescript/src/components/combobox/components/combobox-option.d.ts.map +1 -1
  44. package/dist/typescript/src/components/combobox/components/combobox-overlay.d.ts.map +1 -1
  45. package/dist/typescript/src/components/combobox/components/combobox-root.d.ts +26 -6
  46. package/dist/typescript/src/components/combobox/components/combobox-root.d.ts.map +1 -1
  47. package/dist/typescript/src/components/combobox/components/combobox-trigger.d.ts.map +1 -1
  48. package/dist/typescript/src/components/combobox/context.d.ts +6 -2
  49. package/dist/typescript/src/components/combobox/context.d.ts.map +1 -1
  50. package/dist/typescript/src/components/combobox/index.d.ts +3 -0
  51. package/dist/typescript/src/components/combobox/index.d.ts.map +1 -1
  52. package/dist/typescript/src/components/combobox/types.d.ts +2 -3
  53. package/dist/typescript/src/components/combobox/types.d.ts.map +1 -1
  54. package/dist/typescript/src/components/input/input.d.ts +1 -1
  55. package/dist/typescript/src/components/input/input.d.ts.map +1 -1
  56. package/dist/typescript/src/components/menu/components/menu-selection-indicator.d.ts.map +1 -1
  57. package/dist/typescript/src/components/select/components/select-option.d.ts.map +1 -1
  58. package/dist/typescript/src/components/select/components/select-trigger.d.ts.map +1 -1
  59. package/dist/typescript/src/hooks/use-relative-position.d.ts +2 -2
  60. package/dist/typescript/src/hooks/use-relative-position.d.ts.map +1 -1
  61. package/dist/typescript/src/types/element.types.d.ts +3 -10
  62. package/dist/typescript/src/types/element.types.d.ts.map +1 -1
  63. package/dist/typescript/src/utils/normalize-layout.d.ts +3 -2
  64. package/dist/typescript/src/utils/normalize-layout.d.ts.map +1 -1
  65. package/package.json +8 -1
  66. package/src/components/avatar/components/avatar-image.tsx +2 -2
  67. package/src/components/badge/badge.tsx +1 -3
  68. package/src/components/badge/variants/default.tsx +2 -1
  69. package/src/components/badge/variants/secondary.tsx +1 -0
  70. package/src/components/card/card-title.tsx +7 -4
  71. package/src/components/combobox/components/combobox-list.tsx +32 -0
  72. package/src/components/combobox/components/combobox-option.tsx +27 -33
  73. package/src/components/combobox/components/combobox-overlay.tsx +0 -1
  74. package/src/components/combobox/components/combobox-root.tsx +110 -23
  75. package/src/components/combobox/components/combobox-trigger.tsx +3 -2
  76. package/src/components/combobox/context.ts +7 -2
  77. package/src/components/combobox/index.ts +3 -0
  78. package/src/components/combobox/types.ts +2 -3
  79. package/src/components/menu/components/menu-selection-indicator.tsx +5 -4
  80. package/src/components/portal/portal.tsx +2 -2
  81. package/src/components/select/components/select-option.tsx +14 -14
  82. package/src/components/select/components/select-trigger.tsx +11 -6
  83. package/src/hooks/use-relative-position.ts +4 -4
  84. package/src/types/element.types.ts +3 -10
  85. package/src/utils/normalize-layout.ts +3 -2
  86. package/AGENTS.md +0 -678
  87. package/babel.config.js +0 -8
  88. package/bob.config.js +0 -14
  89. package/tsconfig.json +0 -29
@@ -1,11 +1,10 @@
1
1
  import React, { useState } from "react";
2
- import { Pressable, StyleSheet, Text } from "react-native";
2
+ import { Pressable, StyleSheet, Text, type StyleProp, type ViewStyle } from "react-native";
3
3
  import { useCombobox } from "../context";
4
4
  import type { ComboboxOptionState, ComboboxState } from "../types";
5
5
 
6
- export type ComboboxOptionProps = {
7
- value: string;
8
- label?: string;
6
+ export type ComboboxOptionProps<T> = {
7
+ item: T;
9
8
  children?: React.ReactNode;
10
9
  };
11
10
 
@@ -14,48 +13,43 @@ const calculateState = (
14
13
  hovered: boolean,
15
14
  selected: boolean,
16
15
  ): ComboboxOptionState => {
17
- if (comboboxState === "disabled") {
18
- return "disabled";
19
- }
20
- if (selected) {
21
- return "selected";
22
- }
23
- if (hovered) {
24
- return "hovered";
25
- }
16
+ if (comboboxState === "disabled") return "disabled";
17
+ if (selected) return "selected";
18
+ if (hovered) return "hovered";
26
19
  return "default";
27
20
  };
28
21
 
29
- export function ComboboxOption(props: ComboboxOptionProps) {
22
+ export function ComboboxOption<T>(props: ComboboxOptionProps<T>) {
30
23
  const [isHovered, setIsHovered] = useState(false);
31
24
  const combobox = useCombobox();
32
- const isSelected = combobox.value === props.value;
25
+
26
+ const itemValue = combobox.getItemValue(props.item);
27
+ const selectedValue = combobox.value != null ? combobox.getItemValue(combobox.value) : undefined;
28
+ const isSelected = itemValue === selectedValue;
33
29
 
34
30
  const optionState = calculateState(combobox.state, isHovered, isSelected);
35
- const composedStyles = StyleSheet.flatten([
36
- combobox.styles?.option?.default,
37
- combobox.styles?.option?.[optionState],
38
- ]);
31
+ const optionStyles = combobox.styles?.option;
32
+ const composedStyles = StyleSheet.flatten([optionStyles?.default, optionStyles?.[optionState]]);
33
+
34
+ const handlePress = () => {
35
+ combobox.onChange?.(props.item);
36
+ combobox.setIsOpen(false);
37
+ };
38
+ const handlePointerEnter = () => setIsHovered(true);
39
+ const handlePointerLeave = () => setIsHovered(false);
40
+
41
+ const displayContent = props.children ?? combobox.getItemLabel(props.item);
39
42
 
40
43
  const Component = typeof props.children === "string" ? Text : Pressable;
41
44
 
42
45
  return (
43
46
  <Component
44
- onPress={() => {
45
- const label =
46
- props.label ?? (typeof props.children === "string" ? props.children : props.value);
47
- combobox.onChange?.(label);
48
- combobox.setIsOpen(false);
49
- }}
50
- onPointerEnter={() => {
51
- setIsHovered(true);
52
- }}
53
- onPointerLeave={() => {
54
- setIsHovered(false);
55
- }}
56
- style={composedStyles}
47
+ onPress={handlePress}
48
+ onPointerEnter={handlePointerEnter}
49
+ onPointerLeave={handlePointerLeave}
50
+ style={composedStyles as StyleProp<ViewStyle>}
57
51
  >
58
- {props.children}
52
+ {displayContent}
59
53
  </Component>
60
54
  );
61
55
  }
@@ -19,7 +19,6 @@ export function ComboboxOverlay(props: ComboboxOverlayProps) {
19
19
  return (
20
20
  <Pressable
21
21
  onPress={() => {
22
- combobox.onChange?.(combobox.inputValue);
23
22
  combobox.setIsOpen(false);
24
23
  }}
25
24
  pointerEvents="auto"
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useMemo, useRef, useState } from "react";
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import {
3
3
  type LayoutRectangle,
4
4
  type StyleProp,
@@ -14,31 +14,81 @@ import { ComboboxContext } from "../context";
14
14
  import type { ComboboxState } from "../types";
15
15
  import { ComboboxVariants } from "../variants";
16
16
 
17
- export interface ComboboxRootProps {
18
- variant?: keyof typeof ComboboxVariants;
19
- size?: Size;
17
+ const defaultGetItemValue = (item: unknown): string => {
18
+ if (typeof item === "string") return item;
19
+ if (typeof item === "object" && item !== null && "value" in item) {
20
+ return String((item as Record<string, unknown>).value);
21
+ }
22
+ return String(item);
23
+ };
24
+
25
+ const defaultGetItemLabel = (item: unknown): string => {
26
+ if (typeof item === "string") return item;
27
+ if (typeof item === "object" && item !== null && "label" in item) {
28
+ return String((item as Record<string, unknown>).label);
29
+ }
30
+ return defaultGetItemValue(item);
31
+ };
20
32
 
21
- value?: string;
22
- onChange?: (value: string) => void;
33
+ export interface ComboboxRootProps<T> {
34
+ /** The full list of selectable items. */
35
+ items: readonly T[];
23
36
 
24
- /** Called when the text input value changes. Use this to filter options on the consumer side. */
37
+ /** The currently selected item. */
38
+ value?: T;
39
+
40
+ /** Called when the user selects an item. */
41
+ onChange?: (item: T) => void;
42
+
43
+ /**
44
+ * Extracts a unique string identifier from an item.
45
+ * Defaults to reading `item.value` or `String(item)`.
46
+ */
47
+ getItemValue?: (item: T) => string;
48
+
49
+ /**
50
+ * Extracts a display label from an item (shown in the trigger when selected).
51
+ * Defaults to reading `item.label` or falling back to `getItemValue`.
52
+ */
53
+ getItemLabel?: (item: T) => string;
54
+
55
+ /**
56
+ * Custom filter function. Return `true` to include the item.
57
+ * Defaults to case-insensitive label includes query.
58
+ * Pass `null` to disable built-in filtering (useful for async search).
59
+ */
60
+ filter?: ((item: T, query: string) => boolean) | null;
61
+
62
+ /** Called when the text input value changes. Useful for async search. */
25
63
  onInputChange?: (text: string) => void;
26
64
 
65
+ variant?: keyof typeof ComboboxVariants;
66
+ size?: Size;
27
67
  isDisabled?: boolean;
28
-
29
68
  children?: React.ReactNode;
30
69
  style?: StyleProp<ViewStyle>;
31
70
  }
32
71
 
33
- const calculateState = (props: ComboboxRootProps): ComboboxState => {
34
- if (props.isDisabled) {
35
- return "disabled";
36
- }
72
+ const calculateState = <T,>(props: ComboboxRootProps<T>): ComboboxState => {
73
+ if (props.isDisabled) return "disabled";
37
74
  return "default";
38
75
  };
39
76
 
40
- export function ComboboxRoot(props: ComboboxRootProps) {
41
- const variantStyles = ComboboxVariants[props.variant ?? "default"](props.size ?? "md");
77
+ export function ComboboxRoot<T>(props: ComboboxRootProps<T>) {
78
+ const {
79
+ items,
80
+ value,
81
+ onChange,
82
+ getItemValue = defaultGetItemValue as (item: T) => string,
83
+ getItemLabel = defaultGetItemLabel as (item: T) => string,
84
+ filter,
85
+ onInputChange,
86
+ variant = "default",
87
+ size = "md",
88
+ isDisabled = false,
89
+ } = props;
90
+
91
+ const variantStyles = ComboboxVariants[variant](size);
42
92
  const globalStyles = useComponentConfig("combobox");
43
93
  const mergedStyles = mergeStyles(variantStyles, globalStyles?.styles);
44
94
 
@@ -47,9 +97,18 @@ export function ComboboxRoot(props: ComboboxRootProps) {
47
97
  const [triggerPosition, setTriggerPosition] = useState<LayoutPosition>(DEFAULT_POSITION);
48
98
  const [inputValue, setInputValue] = useState("");
49
99
 
50
- const onInputChangeRef = useRef(props.onInputChange);
51
- onInputChangeRef.current = props.onInputChange;
100
+ // Reset input value when closing
101
+ const prevOpen = useRef(isOpen);
102
+ useEffect(() => {
103
+ if (prevOpen.current && !isOpen) {
104
+ setInputValue("");
105
+ }
106
+ prevOpen.current = isOpen;
107
+ }, [isOpen]);
52
108
 
109
+ // Notify consumer when input value changes
110
+ const onInputChangeRef = useRef(onInputChange);
111
+ onInputChangeRef.current = onInputChange;
53
112
  const isFirstRender = useRef(true);
54
113
  useEffect(() => {
55
114
  if (isFirstRender.current) {
@@ -59,6 +118,20 @@ export function ComboboxRoot(props: ComboboxRootProps) {
59
118
  onInputChangeRef.current?.(inputValue);
60
119
  }, [inputValue]);
61
120
 
121
+ const filteredItems = useMemo(() => {
122
+ if (filter === null) return items;
123
+ if (!inputValue) return items;
124
+
125
+ const filterFn =
126
+ filter ??
127
+ ((item: T, query: string) => {
128
+ const label = getItemLabel(item);
129
+ return label.toLowerCase().includes(query.toLowerCase());
130
+ });
131
+
132
+ return items.filter((item) => filterFn(item, inputValue));
133
+ }, [items, inputValue, filter, getItemLabel]);
134
+
62
135
  const state = calculateState(props);
63
136
  const composedStyles = StyleSheet.flatten([
64
137
  mergedStyles?.root?.default,
@@ -66,10 +139,21 @@ export function ComboboxRoot(props: ComboboxRootProps) {
66
139
  props.style,
67
140
  ]);
68
141
 
142
+ const handleChange = useCallback(
143
+ (item: unknown) => {
144
+ onChange?.(item as T);
145
+ },
146
+ [onChange],
147
+ );
148
+
69
149
  const contextValue: ComboboxContext = useMemo(
70
150
  () => ({
71
- value: props.value,
72
- onChange: props.onChange,
151
+ items,
152
+ filteredItems,
153
+ getItemValue: getItemValue as (item: unknown) => string,
154
+ getItemLabel: getItemLabel as (item: unknown) => string,
155
+ value,
156
+ onChange: handleChange,
73
157
  isOpen,
74
158
  setIsOpen,
75
159
  triggerPosition,
@@ -79,19 +163,22 @@ export function ComboboxRoot(props: ComboboxRootProps) {
79
163
  inputValue,
80
164
  setInputValue,
81
165
  state,
82
- isDisabled: props.isDisabled ?? false,
166
+ isDisabled,
83
167
  styles: mergedStyles,
84
168
  }),
85
169
  [
86
- props.value,
87
- props.onChange,
170
+ items,
171
+ filteredItems,
172
+ getItemValue,
173
+ getItemLabel,
174
+ value,
175
+ handleChange,
88
176
  isOpen,
89
177
  triggerPosition,
90
178
  contentLayout,
91
179
  inputValue,
92
- setInputValue,
93
180
  state,
94
- props.isDisabled,
181
+ isDisabled,
95
182
  mergedStyles,
96
183
  ],
97
184
  );
@@ -22,11 +22,12 @@ export function ComboboxTrigger(props: ComboboxTriggerProps) {
22
22
 
23
23
  const triggerState = calculateState(combobox.isDisabled, combobox.isOpen);
24
24
 
25
- const displayValue = combobox.isOpen ? combobox.inputValue : (combobox.value ?? "");
25
+ const selectedLabel = combobox.value != null ? combobox.getItemLabel(combobox.value) : "";
26
+ const displayValue = combobox.isOpen ? combobox.inputValue : selectedLabel;
26
27
 
27
28
  const open = () => {
28
29
  if (combobox.isDisabled) return;
29
- combobox.setInputValue(combobox.value ?? "");
30
+ combobox.setInputValue(selectedLabel);
30
31
  requestAnimationFrame(() => {
31
32
  measureLayoutPosition(triggerRef.current, (layout) => {
32
33
  combobox.setTriggerPosition(layout);
@@ -4,8 +4,13 @@ import type { LayoutPosition } from "../../hooks";
4
4
  import type { ComboboxState, ComboboxStyles } from "./types";
5
5
 
6
6
  export interface ComboboxContext {
7
- value?: string;
8
- onChange?: (value: string) => void;
7
+ items: readonly unknown[];
8
+ filteredItems: readonly unknown[];
9
+ getItemValue: (item: unknown) => string;
10
+ getItemLabel: (item: unknown) => string;
11
+
12
+ value: unknown | undefined;
13
+ onChange: ((item: unknown) => void) | undefined;
9
14
 
10
15
  isOpen: boolean;
11
16
  setIsOpen: Dispatch<React.SetStateAction<boolean>>;
@@ -1,5 +1,6 @@
1
1
  import { ComboboxContent } from "./components/combobox-content";
2
2
  import { ComboboxEmpty } from "./components/combobox-empty";
3
+ import { ComboboxList } from "./components/combobox-list";
3
4
  import { ComboboxOption } from "./components/combobox-option";
4
5
  import { ComboboxOverlay } from "./components/combobox-overlay";
5
6
  import { ComboboxPortal } from "./components/combobox-portal";
@@ -12,12 +13,14 @@ export const Combobox = {
12
13
  Portal: ComboboxPortal,
13
14
  Overlay: ComboboxOverlay,
14
15
  Content: ComboboxContent,
16
+ List: ComboboxList,
15
17
  Option: ComboboxOption,
16
18
  Empty: ComboboxEmpty,
17
19
  };
18
20
 
19
21
  export type { ComboboxContentProps } from "./components/combobox-content";
20
22
  export type { ComboboxEmptyProps } from "./components/combobox-empty";
23
+ export type { ComboboxListProps } from "./components/combobox-list";
21
24
  export type { ComboboxOptionProps } from "./components/combobox-option";
22
25
  export type { ComboboxOverlayProps } from "./components/combobox-overlay";
23
26
  export type { ComboboxPortalProps } from "./components/combobox-portal";
@@ -1,14 +1,13 @@
1
- import type { TextInputProps, TextStyle } from "react-native";
1
+ import type { StyleProp, TextInputProps, TextStyle, ViewStyle } from "react-native";
2
2
  import type { ComboboxContentProps } from "./components/combobox-content";
3
3
  import type { ComboboxOverlayProps } from "./components/combobox-overlay";
4
- import type { ComboboxRootProps } from "./components/combobox-root";
5
4
 
6
5
  export type ComboboxState = "default" | "disabled";
7
6
  export type ComboboxTriggerState = ComboboxState | "focused";
8
7
  export type ComboboxOptionState = ComboboxState | "hovered" | "selected";
9
8
 
10
9
  export interface ComboboxStyles {
11
- root?: Partial<Record<ComboboxState, ComboboxRootProps["style"]>>;
10
+ root?: Partial<Record<ComboboxState, StyleProp<ViewStyle>>>;
12
11
  trigger?: Partial<Record<ComboboxTriggerState, TextInputProps>>;
13
12
  overlay?: Partial<Record<ComboboxState, ComboboxOverlayProps["style"]>>;
14
13
  content?: Partial<Record<ComboboxState, ComboboxContentProps["style"]>>;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Text, View } from "react-native";
2
+ import { Text, View, type StyleProp, type ViewStyle } from "react-native";
3
3
  import { useComponentConfig } from "../../../themes/provider";
4
4
  import { useMenu } from "../context";
5
5
 
@@ -11,14 +11,15 @@ export function MenuSelectionIndicator({ isSelected }: MenuSelectionIndicatorPro
11
11
  const config = useComponentConfig("menu");
12
12
  const menu = useMenu();
13
13
  const SelectionIcon = config?.selectionIcon;
14
+ const indicatorStyles = menu.styles?.selectionIndicator;
14
15
 
15
16
  if (!isSelected) {
16
- return <View style={menu.styles?.selectionIndicator} />;
17
+ return <View style={indicatorStyles as StyleProp<ViewStyle>} />;
17
18
  }
18
19
 
19
20
  if (SelectionIcon) {
20
- return <SelectionIcon {...menu.styles?.selectionIndicator} />;
21
+ return <SelectionIcon {...indicatorStyles} />;
21
22
  }
22
23
 
23
- return <Text style={menu.styles?.selectionIndicator}>✓</Text>;
24
+ return <Text style={indicatorStyles}>✓</Text>;
24
25
  }
@@ -6,7 +6,7 @@ import React, {
6
6
  useState,
7
7
  useSyncExternalStore,
8
8
  } from "react";
9
- import { Platform, View, type HostInstance } from "react-native";
9
+ import { Platform, View } from "react-native";
10
10
  import { measureLayoutPosition } from "../../utils/normalize-layout";
11
11
  import { PortalOffsetContext, type PortalOffset } from "./portal-offset";
12
12
  import { DEFAULT_PORTAL_HOST, type PortalHostProps, type PortalProps } from "./portal.constants";
@@ -58,7 +58,7 @@ function removePortal(hostName: string, name: string) {
58
58
  }
59
59
 
60
60
  function DefaultContainer(props: React.PropsWithChildren) {
61
- const containerRef = useRef<HostInstance>(null);
61
+ const containerRef = useRef<React.ComponentRef<typeof View>>(null);
62
62
  const [offset, setOffset] = useState<PortalOffset | null>(null);
63
63
 
64
64
  const onLayout = useCallback(() => {
@@ -1,6 +1,5 @@
1
- import { calculateComposedStyles } from "../../../utils/calculate-styles";
2
1
  import React, { useEffect, useState } from "react";
3
- import { Pressable, Text } from "react-native";
2
+ import { Pressable, Text, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
4
3
  import { useSelect } from "../context";
5
4
  import type { SelectOptionState, SelectState } from "../types";
6
5
 
@@ -32,7 +31,8 @@ export function SelectOption(props: SelectOptionProps): React.ReactElement {
32
31
  const isSelected = select.value === props.value;
33
32
 
34
33
  const optionState = calculateState(select.state, isHovered, isSelected);
35
- const composedStyles = calculateComposedStyles(select.styles, optionState, "option");
34
+ const optionStyles = select.styles?.option;
35
+ const composedStyles: StyleProp<TextStyle> = [optionStyles?.default, optionStyles?.[optionState]];
36
36
 
37
37
  useEffect(() => {
38
38
  select.setOptions((prev) => {
@@ -43,21 +43,21 @@ export function SelectOption(props: SelectOptionProps): React.ReactElement {
43
43
  });
44
44
  }, [props.value, props.children]);
45
45
 
46
+ const handlePress = () => {
47
+ select.onChange?.(props.value);
48
+ select.setIsOpen(false);
49
+ };
50
+ const handlePointerEnter = () => setIsHovered(true);
51
+ const handlePointerLeave = () => setIsHovered(false);
52
+
46
53
  const Component = typeof props.children === "string" ? Text : Pressable;
47
54
 
48
55
  return (
49
56
  <Component
50
- onPress={() => {
51
- select.onChange?.(props.value);
52
- select.setIsOpen(false);
53
- }}
54
- onPointerEnter={() => {
55
- setIsHovered(true);
56
- }}
57
- onPointerLeave={() => {
58
- setIsHovered(false);
59
- }}
60
- style={composedStyles}
57
+ onPress={handlePress}
58
+ onPointerEnter={handlePointerEnter}
59
+ onPointerLeave={handlePointerLeave}
60
+ style={composedStyles as StyleProp<ViewStyle>}
61
61
  >
62
62
  {props.children}
63
63
  </Component>
@@ -1,5 +1,11 @@
1
1
  import React, { useRef } from "react";
2
- import { Pressable, Text, type StyleProp, type TextStyle, type ViewStyle } from "react-native";
2
+ import {
3
+ Pressable,
4
+ Text,
5
+ type StyleProp,
6
+ type TextStyle,
7
+ type ViewStyle,
8
+ } from "react-native";
3
9
  import type { ViewRef } from "../../../types/element.types";
4
10
  import { calculateComposedStyles } from "../../../utils/calculate-styles";
5
11
  import { measureLayoutPosition } from "../../../utils/normalize-layout";
@@ -56,11 +62,10 @@ export function SelectValue(props: SelectValueProps) {
56
62
  const selectedOptionLabel = selectedOption?.label;
57
63
  const displayValue = selectedOptionLabel ?? select.value;
58
64
 
59
- const composedStyles = calculateComposedStyles(
60
- select.styles,
61
- select.state,
62
- displayValue ? "value" : "placeholder",
63
- );
65
+ const key = displayValue ? "value" : "placeholder";
66
+ const slotStyles = select.styles?.[key];
67
+ const composedStyles: StyleProp<TextStyle> = [slotStyles?.default, slotStyles?.[select.state]];
68
+
64
69
  if (!!displayValue && typeof displayValue !== "string") {
65
70
  return <>{displayValue}</>;
66
71
  }
@@ -2,8 +2,8 @@ import { useMemo } from "react";
2
2
  import {
3
3
  Platform,
4
4
  useWindowDimensions,
5
- type DisplayMetrics,
6
5
  type LayoutRectangle,
6
+ type ScaledSize,
7
7
  type ViewStyle,
8
8
  } from "react-native";
9
9
  import { usePortalOffset } from "../components/portal";
@@ -118,7 +118,7 @@ export interface LayoutPosition {
118
118
  }
119
119
 
120
120
  interface GetPositionArgs {
121
- dimensions: DisplayMetrics;
121
+ dimensions: ScaledSize;
122
122
  triggerPosition: LayoutPosition;
123
123
  contentLayout: LayoutRectangle;
124
124
  insets: SafeAreaInsets;
@@ -148,7 +148,7 @@ interface GetSideArgs {
148
148
  positionTop: number;
149
149
  positionBottom: number;
150
150
  contentLayout: LayoutRectangle;
151
- dimensions: DisplayMetrics;
151
+ dimensions: ScaledSize;
152
152
  }
153
153
 
154
154
  function getSide({
@@ -272,7 +272,7 @@ function getLeftPosition(
272
272
  contentWidth: number,
273
273
  alignOffset: number,
274
274
  insets: SafeAreaInsets,
275
- dimensions: DisplayMetrics,
275
+ dimensions: ScaledSize,
276
276
  ) {
277
277
  let left = 0;
278
278
  if (align === "start") {
@@ -1,15 +1,8 @@
1
- import type { HostInstance } from "react-native";
1
+ import type { TextInput, View } from "react-native";
2
2
  import type { SvgProps } from "./props.types";
3
3
 
4
- export type ViewRef = HostInstance;
5
- export type TextInputRef = HostInstance & {
6
- focus: () => void;
7
- blur: () => void;
8
- clear: () => void;
9
- isFocused: () => boolean;
10
- getNativeRef: () => HostInstance;
11
- setSelection: (start: number, end?: number) => void;
12
- };
4
+ export type ViewRef = React.ComponentRef<typeof View>;
5
+ export type TextInputRef = React.ComponentRef<typeof TextInput>;
13
6
 
14
7
  export type TextChild = string | number | bigint | boolean | null | undefined;
15
8
  export type TextChildren = TextChild | TextChild[];
@@ -1,5 +1,6 @@
1
- import type { HostInstance, LayoutRectangle } from "react-native";
1
+ import type { LayoutRectangle } from "react-native";
2
2
  import type { LayoutPosition } from "../hooks";
3
+ import type { TextInputRef, ViewRef } from "../types/element.types";
3
4
 
4
5
  export const normalizeLayout = (layout: LayoutRectangle) => {
5
6
  const _layout = { ...layout };
@@ -22,7 +23,7 @@ const isValidNumber = (value: unknown): value is number => {
22
23
  };
23
24
 
24
25
  export const measureLayoutPosition = (
25
- ref: HostInstance | null,
26
+ ref: ViewRef | TextInputRef | null,
26
27
  callback: (layout: LayoutPosition) => void,
27
28
  ) => {
28
29
  if (ref && "getBoundingClientRect" in ref) {