@korsolutions/ui 0.0.18 → 0.0.20

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 (56) hide show
  1. package/dist/components/index.d.mts +70 -4
  2. package/dist/components/index.mjs +224 -8
  3. package/dist/{index-Bfae0NgJ.d.mts → index-CGY0mO6z.d.mts} +216 -8
  4. package/dist/index.d.mts +3 -3
  5. package/dist/index.mjs +2 -2
  6. package/dist/primitives/index.d.mts +2 -2
  7. package/dist/primitives/index.mjs +2 -2
  8. package/dist/{primitives-B4L9y32H.mjs → primitives-P_8clvQr.mjs} +513 -11
  9. package/dist/{toast-manager-Vq38WK76.mjs → toast-manager-DSo9oN8w.mjs} +1 -1
  10. package/package.json +1 -1
  11. package/src/components/badge/badge.tsx +23 -0
  12. package/src/components/badge/variants/default.tsx +26 -0
  13. package/src/components/badge/variants/index.ts +7 -0
  14. package/src/components/badge/variants/secondary.tsx +26 -0
  15. package/src/components/button/button.tsx +7 -4
  16. package/src/components/dropdown-menu/dropdown-menu.tsx +49 -0
  17. package/src/components/dropdown-menu/variants/default.tsx +40 -0
  18. package/src/components/dropdown-menu/variants/index.ts +5 -0
  19. package/src/components/index.ts +4 -0
  20. package/src/components/popover/popover.tsx +51 -0
  21. package/src/components/popover/variants/default.tsx +26 -0
  22. package/src/components/popover/variants/index.ts +5 -0
  23. package/src/components/textarea/textarea.tsx +14 -0
  24. package/src/components/textarea/variants/default.tsx +38 -0
  25. package/src/components/textarea/variants/index.ts +5 -0
  26. package/src/hooks/useRelativePosition.ts +188 -0
  27. package/src/primitives/badge/badge-label.tsx +21 -0
  28. package/src/primitives/badge/badge-root.tsx +30 -0
  29. package/src/primitives/badge/context.ts +17 -0
  30. package/src/primitives/badge/index.ts +11 -0
  31. package/src/primitives/badge/types.ts +9 -0
  32. package/src/primitives/button/button-root.tsx +2 -4
  33. package/src/primitives/dropdown-menu/context.ts +25 -0
  34. package/src/primitives/dropdown-menu/dropdown-menu-button.tsx +47 -0
  35. package/src/primitives/dropdown-menu/dropdown-menu-content.tsx +39 -0
  36. package/src/primitives/dropdown-menu/dropdown-menu-divider.tsx +18 -0
  37. package/src/primitives/dropdown-menu/dropdown-menu-overlay.tsx +29 -0
  38. package/src/primitives/dropdown-menu/dropdown-menu-portal.tsx +21 -0
  39. package/src/primitives/dropdown-menu/dropdown-menu-root.tsx +35 -0
  40. package/src/primitives/dropdown-menu/dropdown-menu-trigger.tsx +47 -0
  41. package/src/primitives/dropdown-menu/index.ts +26 -0
  42. package/src/primitives/dropdown-menu/types.ts +13 -0
  43. package/src/primitives/index.ts +4 -0
  44. package/src/primitives/popover/context.ts +25 -0
  45. package/src/primitives/popover/index.ts +24 -0
  46. package/src/primitives/popover/popover-close.tsx +29 -0
  47. package/src/primitives/popover/popover-content.tsx +39 -0
  48. package/src/primitives/popover/popover-overlay.tsx +37 -0
  49. package/src/primitives/popover/popover-portal.tsx +21 -0
  50. package/src/primitives/popover/popover-root.tsx +35 -0
  51. package/src/primitives/popover/popover-trigger.tsx +47 -0
  52. package/src/primitives/popover/types.ts +7 -0
  53. package/src/primitives/textarea/index.ts +2 -0
  54. package/src/primitives/textarea/textarea.tsx +56 -0
  55. package/src/primitives/textarea/types.ts +5 -0
  56. package/src/utils/get-ref-layout.ts +16 -0
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { StyleProp, View, ViewStyle } from "react-native";
3
+ import { useDropdownMenu } from "./context";
4
+ import { useRelativePosition } from "@/hooks/useRelativePosition";
5
+
6
+ export interface DropdownMenuContentProps {
7
+ children?: React.ReactNode;
8
+
9
+ render?: (props: DropdownMenuContentProps) => React.ReactNode;
10
+
11
+ style?: StyleProp<ViewStyle>;
12
+ }
13
+
14
+ export function DropdownMenuContent(props: DropdownMenuContentProps) {
15
+ const menu = useDropdownMenu();
16
+
17
+ const positionStyle = useRelativePosition({
18
+ align: "start",
19
+ avoidCollisions: true,
20
+ triggerPosition: menu.triggerPosition,
21
+ contentLayout: menu.contentLayout,
22
+ alignOffset: 0,
23
+ side: "bottom",
24
+ sideOffset: 0,
25
+ });
26
+
27
+ const composedStyle = [positionStyle, menu.styles?.content, props.style];
28
+
29
+ const Component = props.render ?? View;
30
+ return (
31
+ <Component
32
+ {...props}
33
+ onLayout={(e) => {
34
+ menu.setContentLayout(e.nativeEvent.layout);
35
+ }}
36
+ style={composedStyle}
37
+ />
38
+ );
39
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import { StyleProp, View, ViewStyle } from "react-native";
3
+ import { useDropdownMenu } from "./context";
4
+
5
+ export interface DropdownMenuDividerProps {
6
+ render?: (props: DropdownMenuDividerProps) => React.ReactNode;
7
+
8
+ style?: StyleProp<ViewStyle>;
9
+ }
10
+
11
+ export function DropdownMenuDivider(props: DropdownMenuDividerProps) {
12
+ const menu = useDropdownMenu();
13
+
14
+ const composedStyle = [menu.styles?.divider, props.style];
15
+
16
+ const Component = props.render ?? View;
17
+ return <Component {...props} style={composedStyle} />;
18
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { useDropdownMenu } from "./context";
3
+ import { Pressable, StyleProp, StyleSheet, ViewStyle } from "react-native";
4
+
5
+ export interface DropdownMenuOverlayProps {
6
+ children?: React.ReactNode;
7
+
8
+ render?: (props: DropdownMenuOverlayProps) => React.ReactElement;
9
+
10
+ style?: StyleProp<ViewStyle>;
11
+ }
12
+
13
+ export function DropdownMenuOverlay(props: DropdownMenuOverlayProps) {
14
+ const menu = useDropdownMenu();
15
+
16
+ const composedStyle = [StyleSheet.absoluteFill, menu.styles?.overlay, props.style];
17
+
18
+ const Component = props.render ?? Pressable;
19
+ return (
20
+ <Component
21
+ onPress={() => {
22
+ menu.setIsOpen(false);
23
+ }}
24
+ style={composedStyle}
25
+ >
26
+ {props.children}
27
+ </Component>
28
+ );
29
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Portal } from "../portal";
3
+ import { useDropdownMenu, DropdownMenuContext } from "./context";
4
+
5
+ export interface DropdownMenuPortalProps {
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ export function DropdownMenuPortal(props: DropdownMenuPortalProps) {
10
+ const menu = useDropdownMenu();
11
+
12
+ if (!menu.isOpen) {
13
+ return null;
14
+ }
15
+
16
+ return (
17
+ <Portal name="dropdown-menu-portal">
18
+ <DropdownMenuContext.Provider value={menu}>{props.children}</DropdownMenuContext.Provider>
19
+ </Portal>
20
+ );
21
+ }
@@ -0,0 +1,35 @@
1
+ import React, { useState } from "react";
2
+ import { LayoutRectangle } from "react-native";
3
+ import { DropdownMenuStyles } from "./types";
4
+ import { DropdownMenuContext } from "./context";
5
+ import { DEFAULT_LAYOUT, DEFAULT_POSITION, LayoutPosition } from "@/hooks/useRelativePosition";
6
+
7
+ export interface DropdownMenuRootProps {
8
+ children?: React.ReactNode;
9
+
10
+ render?: (props: DropdownMenuRootProps) => React.ReactNode;
11
+
12
+ styles?: DropdownMenuStyles;
13
+ }
14
+
15
+ export function DropdownMenuRoot(props: DropdownMenuRootProps) {
16
+ const [isOpen, setIsOpen] = useState(false);
17
+ const [triggerPosition, setTriggerPosition] = useState<LayoutPosition>(DEFAULT_POSITION);
18
+ const [contentLayout, setContentLayout] = useState<LayoutRectangle>(DEFAULT_LAYOUT);
19
+
20
+ return (
21
+ <DropdownMenuContext.Provider
22
+ value={{
23
+ isOpen,
24
+ setIsOpen,
25
+ triggerPosition,
26
+ setTriggerPosition,
27
+ contentLayout,
28
+ setContentLayout,
29
+ styles: props.styles,
30
+ }}
31
+ >
32
+ {props.children}
33
+ </DropdownMenuContext.Provider>
34
+ );
35
+ }
@@ -0,0 +1,47 @@
1
+ import React, { forwardRef, RefAttributes, useImperativeHandle, useRef } from "react";
2
+ import { PressableProps, View } from "react-native";
3
+ import { useDropdownMenu } from "./context";
4
+
5
+ export interface DropdownMenuTriggerProps extends PressableProps {
6
+ children: React.ReactElement<RefAttributes<View> & PressableProps>;
7
+ }
8
+
9
+ export interface DropdownMenuTriggerRef {
10
+ open: () => void;
11
+ close: () => void;
12
+ }
13
+
14
+ export const DropdownMenuTrigger = forwardRef<DropdownMenuTriggerRef, DropdownMenuTriggerProps>((props, ref) => {
15
+ const dropdownMenu = useDropdownMenu();
16
+ const triggerRef = useRef<View>(null);
17
+
18
+ const onTriggerPress = async () => {
19
+ triggerRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
20
+ dropdownMenu.setTriggerPosition({
21
+ height,
22
+ width,
23
+ pageX,
24
+ pageY,
25
+ });
26
+ });
27
+
28
+ dropdownMenu.setIsOpen((prev) => !prev);
29
+ };
30
+
31
+ useImperativeHandle(ref, () => ({
32
+ open: () => dropdownMenu.setIsOpen(true),
33
+ close: () => dropdownMenu.setIsOpen(false),
34
+ }));
35
+
36
+ return React.cloneElement(props.children, {
37
+ ref: triggerRef,
38
+ onPress: onTriggerPress,
39
+ role: "button",
40
+ accessible: true,
41
+ accessibilityRole: "button",
42
+ accessibilityState: { expanded: dropdownMenu.isOpen },
43
+ ...props.children.props,
44
+ });
45
+ });
46
+
47
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
@@ -0,0 +1,26 @@
1
+ import { DropdownMenuRoot } from "./dropdown-menu-root";
2
+ import { DropdownMenuTrigger } from "./dropdown-menu-trigger";
3
+ import { DropdownMenuContent } from "./dropdown-menu-content";
4
+ import { DropdownMenuButton } from "./dropdown-menu-button";
5
+ import { DropdownMenuDivider } from "./dropdown-menu-divider";
6
+ import { DropdownMenuPortal } from "./dropdown-menu-portal";
7
+ import { DropdownMenuOverlay } from "./dropdown-menu-overlay";
8
+
9
+ export const DropdownMenuPrimitive = {
10
+ Root: DropdownMenuRoot,
11
+ Trigger: DropdownMenuTrigger,
12
+ Portal: DropdownMenuPortal,
13
+ Overlay: DropdownMenuOverlay,
14
+ Content: DropdownMenuContent,
15
+ Button: DropdownMenuButton,
16
+ Divider: DropdownMenuDivider,
17
+ };
18
+
19
+ export type { DropdownMenuRootProps } from "./dropdown-menu-root";
20
+ export type { DropdownMenuTriggerProps } from "./dropdown-menu-trigger";
21
+ export type { DropdownMenuPortalProps } from "./dropdown-menu-portal";
22
+ export type { DropdownMenuOverlayProps } from "./dropdown-menu-overlay";
23
+ export type { DropdownMenuContentProps } from "./dropdown-menu-content";
24
+ export type { DropdownMenuButtonProps } from "./dropdown-menu-button";
25
+ export type { DropdownMenuDividerProps } from "./dropdown-menu-divider";
26
+ export type { DropdownMenuStyles } from "./types";
@@ -0,0 +1,13 @@
1
+ import { DropdownMenuContentProps } from "./dropdown-menu-content";
2
+ import { DropdownMenuButtonProps } from "./dropdown-menu-button";
3
+ import { DropdownMenuDividerProps } from "./dropdown-menu-divider";
4
+ import { DropdownMenuOverlayProps } from "./dropdown-menu-overlay";
5
+
6
+ export type DropdownMenuButtonState = "default" | "hovered";
7
+
8
+ export interface DropdownMenuStyles {
9
+ content?: DropdownMenuContentProps["style"];
10
+ button?: Partial<Record<DropdownMenuButtonState, DropdownMenuButtonProps["style"]>>;
11
+ divider?: DropdownMenuDividerProps["style"];
12
+ overlay?: DropdownMenuOverlayProps["style"];
13
+ }
@@ -6,3 +6,7 @@ export * from "./card";
6
6
  export * from "./empty";
7
7
  export * from "./avatar";
8
8
  export * from "./toast";
9
+ export * from "./badge";
10
+ export * from "./textarea";
11
+ export * from "./dropdown-menu";
12
+ export * from "./popover";
@@ -0,0 +1,25 @@
1
+ import { createContext, Dispatch, useContext } from "react";
2
+ import { PopoverStyles } from "./types";
3
+ import { LayoutRectangle } from "react-native";
4
+ import { LayoutPosition } from "@/hooks/useRelativePosition";
5
+
6
+ export interface PopoverContext {
7
+ isOpen: boolean;
8
+ setIsOpen: Dispatch<React.SetStateAction<boolean>>;
9
+ contentLayout: LayoutRectangle;
10
+ setContentLayout: Dispatch<React.SetStateAction<LayoutRectangle>>;
11
+ triggerPosition: LayoutPosition;
12
+ setTriggerPosition: Dispatch<React.SetStateAction<LayoutPosition>>;
13
+
14
+ styles?: PopoverStyles;
15
+ }
16
+
17
+ export const PopoverContext = createContext<PopoverContext | undefined>(undefined);
18
+
19
+ export const usePopover = () => {
20
+ const context = useContext(PopoverContext);
21
+ if (!context) {
22
+ throw new Error("usePopover must be used within a PopoverRoot");
23
+ }
24
+ return context;
25
+ };
@@ -0,0 +1,24 @@
1
+ import { PopoverRoot } from "./popover-root";
2
+ import { PopoverTrigger } from "./popover-trigger";
3
+ import { PopoverPortal } from "./popover-portal";
4
+ import { PopoverOverlay } from "./popover-overlay";
5
+ import { PopoverContent } from "./popover-content";
6
+ import { PopoverClose } from "./popover-close";
7
+
8
+ export const PopoverPrimitive = {
9
+ Root: PopoverRoot,
10
+ Trigger: PopoverTrigger,
11
+ Portal: PopoverPortal,
12
+ Overlay: PopoverOverlay,
13
+ Content: PopoverContent,
14
+ Close: PopoverClose,
15
+ };
16
+
17
+ export type { PopoverRootProps } from "./popover-root";
18
+ export type { PopoverTriggerProps, PopoverTriggerRef } from "./popover-trigger";
19
+ export type { PopoverPortalProps } from "./popover-portal";
20
+ export type { PopoverOverlayProps } from "./popover-overlay";
21
+ export type { PopoverContentProps } from "./popover-content";
22
+ export type { PopoverCloseProps } from "./popover-close";
23
+ export type { PopoverContext } from "./context";
24
+ export { usePopover } from "./context";
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { Pressable, PressableProps, StyleProp, ViewStyle } from "react-native";
3
+ import { usePopover } from "./context";
4
+
5
+ export interface PopoverCloseProps extends Omit<PressableProps, "onPress"> {
6
+ children?: React.ReactNode;
7
+
8
+ onPress?: () => void;
9
+
10
+ render?: (props: PopoverCloseProps) => React.ReactNode;
11
+
12
+ style?: StyleProp<ViewStyle>;
13
+ }
14
+
15
+ export function PopoverClose(props: PopoverCloseProps) {
16
+ const popover = usePopover();
17
+
18
+ const Component = props.render ?? Pressable;
19
+ return (
20
+ <Component
21
+ {...props}
22
+ onPress={() => {
23
+ popover.setIsOpen(false);
24
+ props.onPress?.();
25
+ }}
26
+ style={props.style}
27
+ />
28
+ );
29
+ }
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { StyleProp, View, ViewStyle } from "react-native";
3
+ import { usePopover } from "./context";
4
+ import { useRelativePosition } from "@/hooks/useRelativePosition";
5
+
6
+ export interface PopoverContentProps {
7
+ children?: React.ReactNode;
8
+
9
+ render?: (props: PopoverContentProps) => React.ReactNode;
10
+
11
+ style?: StyleProp<ViewStyle>;
12
+ }
13
+
14
+ export function PopoverContent(props: PopoverContentProps) {
15
+ const popover = usePopover();
16
+
17
+ const positionStyle = useRelativePosition({
18
+ align: "start",
19
+ avoidCollisions: true,
20
+ triggerPosition: popover.triggerPosition,
21
+ contentLayout: popover.contentLayout,
22
+ alignOffset: 0,
23
+ side: "bottom",
24
+ sideOffset: 0,
25
+ });
26
+
27
+ const composedStyle = [positionStyle, popover.styles?.content, props.style];
28
+
29
+ const Component = props.render ?? View;
30
+ return (
31
+ <Component
32
+ {...props}
33
+ onLayout={(e) => {
34
+ popover.setContentLayout(e.nativeEvent.layout);
35
+ }}
36
+ style={composedStyle}
37
+ />
38
+ );
39
+ }
@@ -0,0 +1,37 @@
1
+ import React from "react";
2
+ import { usePopover } from "./context";
3
+ import { Pressable, PressableProps, StyleProp, StyleSheet, ViewStyle } from "react-native";
4
+
5
+ export interface PopoverOverlayProps extends Omit<PressableProps, "onPress"> {
6
+ children?: React.ReactNode;
7
+
8
+ onPress?: () => void;
9
+ closeOnPress?: boolean;
10
+
11
+ render?: (props: PopoverOverlayProps) => React.ReactElement;
12
+
13
+ style?: StyleProp<ViewStyle>;
14
+ }
15
+
16
+ export function PopoverOverlay(props: PopoverOverlayProps) {
17
+ const { closeOnPress = true, ...restProps } = props;
18
+ const popover = usePopover();
19
+
20
+ const composedStyle = [StyleSheet.absoluteFill, popover.styles?.overlay, props.style];
21
+
22
+ const Component = props.render ?? Pressable;
23
+ return (
24
+ <Component
25
+ {...restProps}
26
+ onPress={() => {
27
+ if (closeOnPress) {
28
+ popover.setIsOpen(false);
29
+ }
30
+ props.onPress?.();
31
+ }}
32
+ style={composedStyle}
33
+ >
34
+ {props.children}
35
+ </Component>
36
+ );
37
+ }
@@ -0,0 +1,21 @@
1
+ import React from "react";
2
+ import { Portal } from "../portal";
3
+ import { usePopover, PopoverContext } from "./context";
4
+
5
+ export interface PopoverPortalProps {
6
+ children?: React.ReactNode;
7
+ }
8
+
9
+ export function PopoverPortal(props: PopoverPortalProps) {
10
+ const popover = usePopover();
11
+
12
+ if (!popover.isOpen) {
13
+ return null;
14
+ }
15
+
16
+ return (
17
+ <Portal name="popover-portal">
18
+ <PopoverContext.Provider value={popover}>{props.children}</PopoverContext.Provider>
19
+ </Portal>
20
+ );
21
+ }
@@ -0,0 +1,35 @@
1
+ import React, { useState } from "react";
2
+ import { PopoverContext } from "./context";
3
+ import { PopoverStyles } from "./types";
4
+ import { LayoutRectangle } from "react-native";
5
+ import { DEFAULT_LAYOUT, DEFAULT_POSITION, LayoutPosition } from "@/hooks/useRelativePosition";
6
+
7
+ export interface PopoverRootProps {
8
+ children?: React.ReactNode;
9
+
10
+ render?: (props: PopoverRootProps) => React.ReactNode;
11
+
12
+ styles?: PopoverStyles;
13
+ }
14
+
15
+ export function PopoverRoot(props: PopoverRootProps) {
16
+ const [isOpen, setIsOpen] = useState(false);
17
+ const [contentLayout, setContentLayout] = useState<LayoutRectangle>(DEFAULT_LAYOUT);
18
+ const [triggerPosition, setTriggerPosition] = useState<LayoutPosition>(DEFAULT_POSITION);
19
+
20
+ return (
21
+ <PopoverContext.Provider
22
+ value={{
23
+ isOpen,
24
+ setIsOpen,
25
+ contentLayout,
26
+ setContentLayout,
27
+ triggerPosition,
28
+ setTriggerPosition,
29
+ styles: props.styles,
30
+ }}
31
+ >
32
+ {props.children}
33
+ </PopoverContext.Provider>
34
+ );
35
+ }
@@ -0,0 +1,47 @@
1
+ import React, { forwardRef, RefAttributes, useImperativeHandle, useRef } from "react";
2
+ import { PressableProps, View } from "react-native";
3
+ import { usePopover } from "./context";
4
+
5
+ export interface PopoverTriggerProps extends PressableProps {
6
+ children: React.ReactElement<RefAttributes<View> & PressableProps>;
7
+ }
8
+
9
+ export interface PopoverTriggerRef {
10
+ open: () => void;
11
+ close: () => void;
12
+ }
13
+
14
+ export const PopoverTrigger = forwardRef<PopoverTriggerRef, PopoverTriggerProps>((props, ref) => {
15
+ const popover = usePopover();
16
+ const triggerRef = useRef<View>(null);
17
+
18
+ const onTriggerPress = async () => {
19
+ triggerRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
20
+ popover.setTriggerPosition({
21
+ height,
22
+ width,
23
+ pageX,
24
+ pageY,
25
+ });
26
+ });
27
+
28
+ popover.setIsOpen((prev) => !prev);
29
+ };
30
+
31
+ useImperativeHandle(ref, () => ({
32
+ open: () => popover.setIsOpen(true),
33
+ close: () => popover.setIsOpen(false),
34
+ }));
35
+
36
+ return React.cloneElement(props.children, {
37
+ ref: triggerRef,
38
+ onPress: onTriggerPress,
39
+ role: "button",
40
+ accessible: true,
41
+ accessibilityRole: "button",
42
+ accessibilityState: { expanded: popover.isOpen },
43
+ ...props.children.props,
44
+ });
45
+ });
46
+
47
+ PopoverTrigger.displayName = "PopoverTrigger";
@@ -0,0 +1,7 @@
1
+ import { PopoverContentProps } from "./popover-content";
2
+ import { PopoverOverlayProps } from "./popover-overlay";
3
+
4
+ export interface PopoverStyles {
5
+ overlay?: PopoverOverlayProps["style"];
6
+ content?: PopoverContentProps["style"];
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./textarea";
2
+ export type { TextareaStyles } from "./types";
@@ -0,0 +1,56 @@
1
+ import { TextInput, TextInputProps } from "react-native";
2
+ import { TextareaState, TextareaStyles } from "./types";
3
+ import { useState } from "react";
4
+
5
+ export type TextareaPrimitiveBaseProps = Omit<TextInputProps, "onChange"> & {
6
+ onChange?: TextInputProps["onChangeText"];
7
+
8
+ isDisabled?: boolean;
9
+ };
10
+
11
+ export interface TextareaPrimitiveProps extends TextareaPrimitiveBaseProps {
12
+ render?: (props: TextareaPrimitiveProps) => React.ReactNode;
13
+
14
+ styles?: TextareaStyles;
15
+ }
16
+
17
+ const calculateState = (props: TextareaPrimitiveProps, isFocused: boolean): TextareaState => {
18
+ if (props.isDisabled) {
19
+ return "disabled";
20
+ }
21
+ if (isFocused) {
22
+ return "focused";
23
+ }
24
+ return "default";
25
+ };
26
+
27
+ export function TextareaPrimitive(props: TextareaPrimitiveProps) {
28
+ const [isFocused, setIsFocused] = useState(false);
29
+ const state = calculateState(props, isFocused);
30
+
31
+ const composedStyles = [props.styles?.default?.style, props.styles?.[state]?.style, props.style];
32
+ const composedProps = {
33
+ ...props.styles?.default,
34
+ ...props.styles?.[state],
35
+ ...props,
36
+ };
37
+ const Component = props.render ?? TextInput;
38
+ return (
39
+ <Component
40
+ {...composedProps}
41
+ multiline
42
+ onChange={undefined}
43
+ onChangeText={props.onChange}
44
+ onFocus={(e) => {
45
+ setIsFocused(true);
46
+ props.onFocus?.(e);
47
+ }}
48
+ onBlur={(e) => {
49
+ setIsFocused(false);
50
+ props.onBlur?.(e);
51
+ }}
52
+ readOnly={props.isDisabled || props.readOnly}
53
+ style={composedStyles}
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,5 @@
1
+ import { TextareaPrimitiveBaseProps } from "./textarea";
2
+
3
+ export type TextareaState = "default" | "focused" | "disabled";
4
+
5
+ export type TextareaStyles = Partial<Record<TextareaState, TextareaPrimitiveBaseProps>>;
@@ -0,0 +1,16 @@
1
+ import { LayoutRectangle, View } from "react-native";
2
+
3
+ const DEFAULT_LAYOUT: LayoutRectangle = { x: 0, y: 0, width: 0, height: 0 };
4
+
5
+ export const getRefLayout = async (ref: React.RefObject<View | null>) => {
6
+ return new Promise<LayoutRectangle>((resolve) => {
7
+ if (!ref?.current?.["measureInWindow"]) {
8
+ console.warn("getRefLayout.NoRef");
9
+ resolve(DEFAULT_LAYOUT);
10
+ return;
11
+ }
12
+ ref.current.measureInWindow((x, y, width, height) => {
13
+ resolve({ x, y, width, height });
14
+ });
15
+ });
16
+ };