orn-ui 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +354 -0
- package/README.md +43 -13
- package/lib/commonjs/organisms/DateField.js +1 -1
- package/lib/commonjs/organisms/DateField.js.map +1 -1
- package/lib/module/organisms/DateField.js +1 -1
- package/lib/module/organisms/DateField.js.map +1 -1
- package/lib/typescript/atoms/Badge.d.ts +1 -1
- package/lib/typescript/atoms/Badge.d.ts.map +1 -1
- package/lib/typescript/atoms/Image.d.ts +6 -6
- package/lib/typescript/atoms/Image.d.ts.map +1 -1
- package/lib/typescript/atoms/Input.d.ts +1 -1
- package/lib/typescript/atoms/Input.d.ts.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +5 -5
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
- package/lib/typescript/atoms/Spinner.d.ts +4 -4
- package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
- package/lib/typescript/atoms/Transition.d.ts +10 -10
- package/lib/typescript/atoms/Transition.d.ts.map +1 -1
- package/lib/typescript/molecules/AvatarHeader.d.ts +2 -2
- package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
- package/lib/typescript/molecules/FormActions.d.ts +1 -1
- package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +2 -2
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/Stepper.d.ts +8 -8
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/Steps.d.ts +3 -3
- package/lib/typescript/molecules/Steps.d.ts.map +1 -1
- package/lib/typescript/molecules/SymmetricGrid.d.ts +4 -4
- package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +8 -8
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/DateField.d.ts +10 -10
- package/lib/typescript/organisms/DateField.d.ts.map +1 -1
- package/lib/typescript/organisms/DatePicker.d.ts +10 -10
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +6 -6
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +2 -2
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
- package/lib/typescript/organisms/ReorderableList.d.ts +7 -7
- package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
- package/lib/typescript/organisms/Screen.d.ts +6 -6
- package/lib/typescript/organisms/SearchList.d.ts +8 -8
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +2 -2
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/organisms/Toast.d.ts +2 -2
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +2 -2
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +7 -7
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/theme/UIProvider.d.ts +9 -9
- package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
- package/llms.txt +33 -0
- package/package.json +24 -6
- package/registry/avatar-header.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/bottom-sheet.json +1 -1
- package/registry/core.json +1 -1
- package/registry/date-field.json +1 -1
- package/registry/date-picker.json +1 -1
- package/registry/form-actions.json +1 -1
- package/registry/image.json +1 -1
- package/registry/index.json +26 -26
- package/registry/input.json +1 -1
- package/registry/list.json +1 -1
- package/registry/navigation-bar.json +1 -1
- package/registry/option-card.json +1 -1
- package/registry/pressable-scale.json +1 -1
- package/registry/reorderable-list.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +1 -1
- package/registry/select.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/steps.json +1 -1
- package/registry/symmetric-grid.json +1 -1
- package/registry/toast-provider.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/transition.json +1 -1
- package/registry/wizard.json +1 -1
- package/src/atoms/Badge.tsx +1 -1
- package/src/atoms/Image.tsx +6 -6
- package/src/atoms/Input.tsx +1 -1
- package/src/atoms/PressableScale.tsx +1 -1
- package/src/atoms/Skeleton.tsx +5 -5
- package/src/atoms/Spinner.tsx +4 -4
- package/src/atoms/Transition.tsx +10 -10
- package/src/molecules/AvatarHeader.tsx +2 -2
- package/src/molecules/FormActions.tsx +1 -1
- package/src/molecules/OptionCard.tsx +2 -2
- package/src/molecules/Stepper.tsx +8 -8
- package/src/molecules/Steps.tsx +3 -3
- package/src/molecules/SymmetricGrid.tsx +4 -4
- package/src/organisms/BottomSheet.tsx +8 -8
- package/src/organisms/DateField.tsx +11 -11
- package/src/organisms/DatePicker.tsx +10 -10
- package/src/organisms/List.tsx +6 -6
- package/src/organisms/NavigationBar.tsx +2 -2
- package/src/organisms/ReorderableList.tsx +7 -7
- package/src/organisms/Screen.tsx +6 -6
- package/src/organisms/SearchList.tsx +8 -8
- package/src/organisms/Select.tsx +2 -2
- package/src/organisms/Toast.tsx +2 -2
- package/src/organisms/ToastProvider.tsx +2 -2
- package/src/organisms/Wizard.tsx +7 -7
- package/src/theme/UIProvider.tsx +9 -9
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { type FlatListProps, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import type { IconName } from '../icons/types';
|
|
4
4
|
export interface SearchListProps<T> {
|
|
5
|
-
/**
|
|
5
|
+
/** Optional chrome above the search bar (e.g. back button + title). */
|
|
6
6
|
header?: React.ReactNode;
|
|
7
7
|
searchValue: string;
|
|
8
8
|
onSearchChange: (text: string) => void;
|
|
@@ -14,22 +14,22 @@ export interface SearchListProps<T> {
|
|
|
14
14
|
data: T[];
|
|
15
15
|
keyExtractor: (item: T, index: number) => string;
|
|
16
16
|
renderItem: FlatListProps<T>['renderItem'];
|
|
17
|
-
/**
|
|
17
|
+
/** Initial load: while true and there's no data, placeholders are shown. */
|
|
18
18
|
isLoading?: boolean;
|
|
19
|
-
/** 'replace'
|
|
19
|
+
/** 'replace' swaps the whole list for a spinner/skeleton; 'overlay' shows it on top while keeping the layout. */
|
|
20
20
|
loadingMode?: 'replace' | 'overlay';
|
|
21
|
-
/**
|
|
21
|
+
/** Reload with data already on screen (pull-to-refresh). @default false */
|
|
22
22
|
isRefreshing?: boolean;
|
|
23
23
|
isLoadingMore?: boolean;
|
|
24
|
-
/**
|
|
24
|
+
/** Controlled by the consumer: while false, a fullscreen spinner is shown. @default true */
|
|
25
25
|
isReady?: boolean;
|
|
26
26
|
loadingText?: string;
|
|
27
27
|
loadingMoreText?: string;
|
|
28
28
|
hasMore?: boolean;
|
|
29
29
|
noMoreText?: string;
|
|
30
|
-
/**
|
|
30
|
+
/** Ghost rows for the initial load. @default 6 */
|
|
31
31
|
skeletonCount?: number;
|
|
32
|
-
/**
|
|
32
|
+
/** Custom placeholder, to match the real shape of the item. */
|
|
33
33
|
renderSkeletonItem?: () => React.ReactElement;
|
|
34
34
|
onLoadMore?: () => void;
|
|
35
35
|
onEndReached?: () => void;
|
|
@@ -42,7 +42,7 @@ export interface SearchListProps<T> {
|
|
|
42
42
|
ListFooterComponent?: FlatListProps<T>['ListFooterComponent'];
|
|
43
43
|
contentContainerStyle?: StyleProp<ViewStyle>;
|
|
44
44
|
containerStyle?: ViewStyle;
|
|
45
|
-
/**
|
|
45
|
+
/** List component to use (FlashList, etc). @default FlatList */
|
|
46
46
|
ListComponent?: React.ComponentType<any>;
|
|
47
47
|
listProps?: Record<string, unknown>;
|
|
48
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchList.d.ts","sourceRoot":"","sources":["../../../src/organisms/SearchList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAC3C,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,
|
|
1
|
+
{"version":3,"file":"SearchList.d.ts","sourceRoot":"","sources":["../../../src/organisms/SearchList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsB,MAAM,OAAO,CAAC;AAC3C,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAQtB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,uEAAuE;IACvE,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAEzB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAE/B,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACjD,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IAE3C,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iHAAiH;IACjH,WAAW,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,MAAM,KAAK,CAAC,YAAY,CAAC;IAE9C,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,aAAa,CAAC,EAAE,QAAQ,CAAC;IAEzB,mBAAmB,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;IAC9D,mBAAmB,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;IAE9D,qBAAqB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,SAAS,CAAC;IAE3B,gEAAgE;IAChE,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AA+BD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,EAC5B,MAAM,EACN,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,eAAuB,EACvB,WAAW,EACX,sBAA+B,EAC/B,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,SAAiB,EACjB,WAAuB,EACvB,YAAoB,EACpB,aAAqB,EACrB,OAAc,EACd,WAAW,EACX,eAAe,EACf,OAAe,EACf,UAAU,EACV,aAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,qBAA2B,EAC3B,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,aAAwB,EACxB,SAAS,GACV,EAAE,eAAe,CAAC,CAAC,CAAC,qBAsIpB"}
|
|
@@ -20,10 +20,10 @@ export interface SelectProps<T> {
|
|
|
20
20
|
error?: string;
|
|
21
21
|
isLoading?: boolean;
|
|
22
22
|
disabled?: boolean;
|
|
23
|
-
/**
|
|
23
|
+
/** Adds a search box inside the dropdown. @default false */
|
|
24
24
|
searchable?: boolean;
|
|
25
25
|
searchPlaceholder?: string;
|
|
26
|
-
/**
|
|
26
|
+
/** Maximum height of the dropdown, if the available space allows it. @default 280 */
|
|
27
27
|
maxHeight?: number;
|
|
28
28
|
onOpenChange?: (open: boolean) => void;
|
|
29
29
|
containerStyle?: StyleProp<ViewStyle>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/organisms/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/organisms/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA6ED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EAChD,KAAK,EACL,QAAgB,EAChB,OAAO,EACP,aAAa,EACb,QAAQ,EACR,WAAW,EACX,KAAK,EACL,SAAiB,EACjB,QAAgB,EAChB,UAAkB,EAClB,iBAAiB,EACjB,SAAe,EACf,YAAY,EACZ,cAAc,EACd,MAAM,GACP,EAAE,WAAW,CAAC,CAAC,CAAC,qBAyLhB"}
|
|
@@ -5,10 +5,10 @@ export interface ToastProps {
|
|
|
5
5
|
title: string;
|
|
6
6
|
message?: string;
|
|
7
7
|
variant?: ToastVariant;
|
|
8
|
-
/**
|
|
8
|
+
/** Fires when the toast is tapped (in addition to closing it). */
|
|
9
9
|
onPress?: () => void;
|
|
10
10
|
onDismiss?: () => void;
|
|
11
|
-
/**
|
|
11
|
+
/** Hides the close X. The toast still auto-dismisses by timeout. */
|
|
12
12
|
hideCloseButton?: boolean;
|
|
13
13
|
style?: StyleProp<ViewStyle>;
|
|
14
14
|
testID?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/organisms/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAOrF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/organisms/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAOrF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwCD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,8GAC2E,UAAU,uBAkDtG,CAAC;AAGF,sFAAsF;AACtF,eAAO,MAAM,aAAa,qDACC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,uBAKvF,CAAC"}
|
|
@@ -27,9 +27,9 @@ export interface ToastProviderProps {
|
|
|
27
27
|
children: React.ReactNode;
|
|
28
28
|
/** @default 'top' */
|
|
29
29
|
position?: 'top' | 'bottom';
|
|
30
|
-
/**
|
|
30
|
+
/** Maximum simultaneous toasts; the oldest ones are dropped. @default 3 */
|
|
31
31
|
maxVisible?: number;
|
|
32
|
-
/**
|
|
32
|
+
/** Default duration in ms. @default 4000 */
|
|
33
33
|
defaultDuration?: number;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../src/organisms/ToastProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuF,MAAM,OAAO,CAAC;AAE5G,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAKlE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,UAAU,iBAAiB;IACzB,kFAAkF;IAClF,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,
|
|
1
|
+
{"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../src/organisms/ToastProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuF,MAAM,OAAO,CAAC;AAE5G,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAKlE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,UAAU,iBAAiB;IACzB,kFAAkF;IAClF,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAgB,EAChB,UAAc,EACd,eAAsB,GACvB,EAAE,kBAAkB,qBA4GpB;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
|
|
@@ -8,28 +8,28 @@ export interface WizardStep extends StepItem {
|
|
|
8
8
|
}
|
|
9
9
|
export interface WizardProps {
|
|
10
10
|
steps: WizardStep[];
|
|
11
|
-
/**
|
|
11
|
+
/** Current step (controlled). If omitted, the Wizard manages its own state. */
|
|
12
12
|
current?: number;
|
|
13
13
|
onStepChange?: (index: number) => void;
|
|
14
14
|
onFinish?: () => void;
|
|
15
15
|
/** @default 'horizontal' */
|
|
16
16
|
orientation?: 'horizontal' | 'vertical';
|
|
17
17
|
completedIndicator?: 'check' | 'number';
|
|
18
|
-
/**
|
|
18
|
+
/** Allows returning to an already-completed step by tapping it in the indicator. @default true */
|
|
19
19
|
allowStepNavigation?: boolean;
|
|
20
20
|
backLabel?: string;
|
|
21
21
|
nextLabel?: string;
|
|
22
22
|
finishLabel?: string;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* VirtualizedList
|
|
24
|
+
* The step content scrolls when it doesn't fit. Set to false only if
|
|
25
|
+
* a step renders its own virtualized list (nesting a
|
|
26
|
+
* VirtualizedList inside a ScrollView breaks virtualization).
|
|
27
27
|
* @default true
|
|
28
28
|
*/
|
|
29
29
|
scrollableContent?: boolean;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* The step content slides in following the navigation direction.
|
|
32
|
+
* Only opacity and transform, so the animation runs on the native thread.
|
|
33
33
|
* @default true
|
|
34
34
|
*/
|
|
35
35
|
animated?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Wizard.d.ts","sourceRoot":"","sources":["../../../src/organisms/Wizard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB
|
|
1
|
+
{"version":3,"file":"Wizard.d.ts","sourceRoot":"","sources":["../../../src/organisms/Wizard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,kBAAkB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACxC,kGAAkG;IAClG,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAUD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EACZ,QAAQ,EACR,WAA0B,EAC1B,kBAA4B,EAC5B,mBAA0B,EAC1B,SAAkB,EAClB,SAAkB,EAClB,WAAsB,EACtB,iBAAwB,EACxB,QAAe,EACf,KAAK,GACN,EAAE,WAAW,qBAkGb"}
|
|
@@ -19,24 +19,24 @@ export interface ModalSafeAreaBoundaryProps {
|
|
|
19
19
|
export type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;
|
|
20
20
|
export interface UIProviderProps {
|
|
21
21
|
children: React.ReactNode;
|
|
22
|
-
/**
|
|
22
|
+
/** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */
|
|
23
23
|
theme?: ThemePair;
|
|
24
|
-
/**
|
|
24
|
+
/** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */
|
|
25
25
|
defaultMode?: ThemeMode;
|
|
26
|
-
/**
|
|
26
|
+
/** Controlled mode. If passed together with onModeChange, the provider doesn't manage its own state. */
|
|
27
27
|
mode?: ThemeMode;
|
|
28
28
|
onModeChange?: (mode: ThemeMode) => void;
|
|
29
|
-
/**
|
|
29
|
+
/** Icon renderer. Defaults to the included zero-dep glyphs. */
|
|
30
30
|
icons?: IconRenderer;
|
|
31
|
-
/**
|
|
31
|
+
/** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */
|
|
32
32
|
insets?: EdgeInsets;
|
|
33
33
|
labels?: Partial<Labels>;
|
|
34
|
-
/** @default false,
|
|
34
|
+
/** @default false, for parity with the rest of the library (accessibility first). */
|
|
35
35
|
allowFontScaling?: boolean;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @default
|
|
37
|
+
* Source of insets used by `Modal`/`BottomSheet` for their own native
|
|
38
|
+
* window. Don't pass this by hand — `SafeAreaUIProvider` wires it up itself.
|
|
39
|
+
* @default repeats the ambient insets (behavior without safe-area-context)
|
|
40
40
|
*/
|
|
41
41
|
ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;
|
|
42
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UIProvider.d.ts","sourceRoot":"","sources":["../../../src/theme/UIProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4E,MAAM,OAAO,CAAC;AACjG,OAAO,EAAE,UAAU,EAAkB,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD;AACD,MAAM,MAAM,8BAA8B,GAAG,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;AAoB7F,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B
|
|
1
|
+
{"version":3,"file":"UIProvider.d.ts","sourceRoot":"","sources":["../../../src/theme/UIProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4E,MAAM,OAAO,CAAC;AACjG,OAAO,EAAE,UAAU,EAAkB,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD;AACD,MAAM,MAAM,8BAA8B,GAAG,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;AAoB7F,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,8FAA8F;IAC9F,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wGAAwG;IACxG,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,8BAA8B,CAAC;CACxD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,KAAoB,EACpB,WAAsB,EACtB,IAAI,EAAE,cAAc,EACpB,YAAY,EACZ,KAAyB,EACzB,MAAmB,EACnB,MAAM,EAAE,cAAc,EACtB,gBAAwB,EACxB,qBAAoD,GACrD,EAAE,eAAe,qBAwDjB;AAUD,wBAAgB,QAAQ,IAAI,KAAK,CAEhC;AAED,wBAAgB,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,CAE3C;AAED,wBAAgB,YAAY;;oBA9HV,SAAS,KAAK,IAAI;EAiInC;AAED,wBAAgB,QAAQ,IAAI,YAAY,CAEvC;AAED,wBAAgB,SAAS,IAAI,UAAU,CAEtC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,yGAAyG;AACzG,wBAAgB,wBAAwB,IAAI,8BAA8B,CAEzE;AAED,8FAA8F;AAC9F,eAAO,MAAM,WAAW;;CAAiB,CAAC"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# orn-ui
|
|
2
|
+
|
|
3
|
+
> React Native / Expo component library: 44 typed components (atoms, molecules,
|
|
4
|
+
> organisms), light/dark theming, zero runtime dependencies (only `react` and
|
|
5
|
+
> `react-native` as peers). Verified in CI against Expo SDK 54, 55, 56 and 57.
|
|
6
|
+
> Built to be used by AI coding agents: one component replaces dozens of lines
|
|
7
|
+
> of hand-written `View`/`Text`/`StyleSheet`, so a screen costs a fraction of
|
|
8
|
+
> the tokens and ships production-ready instead of prototype-only.
|
|
9
|
+
|
|
10
|
+
Install: `pnpm add orn-ui` — no native modules, no pods, no babel or metro
|
|
11
|
+
config, works in Expo Go and bare React Native >= 0.81.
|
|
12
|
+
Or copy the source into the project with no dependency: `npx orn-ui add button select`.
|
|
13
|
+
|
|
14
|
+
Import from subpaths (Metro does not tree-shake barrels):
|
|
15
|
+
`import { Button } from 'orn-ui/button'`, `import { UIProvider } from 'orn-ui/theme'`.
|
|
16
|
+
|
|
17
|
+
Wrap the app once: `<SafeAreaUIProvider defaultMode="system">` from `orn-ui/safe-area`
|
|
18
|
+
(or `<UIProvider insets={...}>` from `orn-ui/theme` to skip the optional
|
|
19
|
+
`react-native-safe-area-context` peer).
|
|
20
|
+
|
|
21
|
+
## Docs
|
|
22
|
+
|
|
23
|
+
- [Agent guide](https://github.com/DavidTrujillo123/orn-ui/blob/main/packages/ui/AGENTS.md): full component + props cheat sheet, import rules, gotchas, recipes — shipped in the package as `node_modules/orn-ui/AGENTS.md`
|
|
24
|
+
- [README](https://github.com/DavidTrujillo123/orn-ui#readme): install modes, safe area, theming, compatibility matrix
|
|
25
|
+
- [Live docs](https://orn-ui-docs.vercel.app/): props tables and demo GIFs per component
|
|
26
|
+
- [Repository](https://github.com/DavidTrujillo123/orn-ui): source, tests, CI compatibility matrix
|
|
27
|
+
|
|
28
|
+
## Components
|
|
29
|
+
|
|
30
|
+
- Atoms: Title, Subtitle, Body, Caption, Button, IconButton, Input, Checkbox, Badge, Card, Divider, Avatar, Image, Spinner, Skeleton, Transition, EmptyState, KeyValueRow, Fab, PressableScale
|
|
31
|
+
- Molecules: Stepper, OptionCard, InfoRow, FormActions, AvatarHeader, SegmentedControl, Steps, SymmetricGrid
|
|
32
|
+
- Organisms: Screen, Modal, BottomSheet, Select, List, SearchList, ReorderableList, Alert, AlertProvider, Toast, ToastProvider, DatePicker, DateField, Wizard, ThemeToggle, NavigationBar
|
|
33
|
+
- Theme: UIProvider, SafeAreaUIProvider, createTheme, createStyles, useTheme, useColors, useThemeMode, useIcons, useInsets, useLabels
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orn-ui",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.7",
|
|
4
|
+
"description": "React Native / Expo component library: 44 typed components, light/dark theming, zero runtime dependencies. Verified on Expo SDK 54-57. Agent-friendly: one component instead of hundreds of lines of hand-written UI.",
|
|
5
5
|
"author": "David Trujillo <davot098@gmail.com>",
|
|
6
6
|
"main": "lib/commonjs/index.js",
|
|
7
7
|
"module": "lib/module/index.js",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"lib",
|
|
18
18
|
"bin",
|
|
19
19
|
"registry",
|
|
20
|
+
"AGENTS.md",
|
|
21
|
+
"llms.txt",
|
|
20
22
|
"!**/__tests__",
|
|
21
23
|
"!**/*.test.*"
|
|
22
24
|
],
|
|
@@ -342,12 +344,14 @@
|
|
|
342
344
|
"typecheck": "tsc --noEmit",
|
|
343
345
|
"test": "jest",
|
|
344
346
|
"test:coverage": "jest --coverage",
|
|
347
|
+
"compat": "node scripts/compat-matrix.mjs",
|
|
348
|
+
"compat:typecheck": "node scripts/compat-matrix.mjs --only typecheck",
|
|
345
349
|
"prepare": "node scripts/gen-exports.mjs && node scripts/build-registry.mjs && bob build"
|
|
346
350
|
},
|
|
347
351
|
"peerDependencies": {
|
|
348
|
-
"react": ">=
|
|
349
|
-
"react-native": ">=0.
|
|
350
|
-
"react-native-safe-area-context": ">=4.0
|
|
352
|
+
"react": ">=19.1.0",
|
|
353
|
+
"react-native": ">=0.81.0",
|
|
354
|
+
"react-native-safe-area-context": ">=5.4.0"
|
|
351
355
|
},
|
|
352
356
|
"peerDependenciesMeta": {
|
|
353
357
|
"react-native-safe-area-context": {
|
|
@@ -370,9 +374,23 @@
|
|
|
370
374
|
},
|
|
371
375
|
"keywords": [
|
|
372
376
|
"react-native",
|
|
377
|
+
"expo",
|
|
378
|
+
"expo-sdk-54",
|
|
379
|
+
"expo-sdk-55",
|
|
380
|
+
"expo-sdk-56",
|
|
381
|
+
"expo-sdk-57",
|
|
373
382
|
"ui",
|
|
374
383
|
"components",
|
|
375
|
-
"
|
|
384
|
+
"component-library",
|
|
385
|
+
"design-system",
|
|
386
|
+
"atomic-design",
|
|
387
|
+
"dark-mode",
|
|
388
|
+
"theming",
|
|
389
|
+
"typescript",
|
|
390
|
+
"zero-dependencies",
|
|
391
|
+
"ai",
|
|
392
|
+
"llm",
|
|
393
|
+
"agents"
|
|
376
394
|
],
|
|
377
395
|
"license": "MIT",
|
|
378
396
|
"repository": {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "molecules/AvatarHeader.tsx",
|
|
12
|
-
"content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport interface AvatarHeaderProps {\n /**
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport interface AvatarHeaderProps {\n /** Icon to show inside the circle. If omitted, `initials` is used instead. */\n iconName?: IconName;\n iconColor?: string;\n /** Short text (e.g. 2 letters) shown when `iconName` isn't provided. */\n initials?: string;\n title: string;\n subtitle?: React.ReactNode;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { alignItems: 'center', marginBottom: theme.tokens.spacing.xl },\n avatar: {\n width: 80,\n height: 80,\n borderRadius: 40,\n backgroundColor: theme.colors.primarySoft,\n justifyContent: 'center',\n alignItems: 'center',\n marginBottom: theme.tokens.spacing.md,\n },\n initials: { fontSize: theme.tokens.fontSize.xxl, fontWeight: theme.tokens.fontWeight.bold, color: theme.colors.primaryText },\n title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, textAlign: 'center', color: theme.colors.text, marginBottom: theme.tokens.spacing.xs },\n subtitle: { fontSize: theme.tokens.fontSize.md, color: theme.colors.textLight },\n}));\n\n/**\n * AvatarHeader\n * Círculo 80x80 con ícono o iniciales + título/subtítulo centrado. Header\n * típico de modales de detalle (cliente, producto, etc).\n */\nexport const AvatarHeader = memo(({ iconName, iconColor, initials, title, subtitle, style }: AvatarHeaderProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n\n return (\n <View style={[styles.container, style]}>\n <View style={styles.avatar}>\n {iconName ? (\n <Icon name={iconName} size={40} color={iconColor || colors.primary} />\n ) : (\n <Text allowFontScaling={allowFontScaling} style={styles.initials}>\n {initials}\n </Text>\n )}\n </View>\n <Text allowFontScaling={allowFontScaling} style={styles.title}>\n {title}\n </Text>\n {typeof subtitle === 'string' ? (\n <Text allowFontScaling={allowFontScaling} style={styles.subtitle}>\n {subtitle}\n </Text>\n ) : (\n subtitle\n )}\n </View>\n );\n});\nAvatarHeader.displayName = 'AvatarHeader';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
package/registry/badge.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "atoms/Badge.tsx",
|
|
12
|
-
"content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useTheme } from '../theme/UIProvider';\n\nexport type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral';\n\nexport interface BadgeProps {\n label: string;\n variant?: BadgeVariant;\n /**
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useTheme } from '../theme/UIProvider';\n\nexport type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral';\n\nexport interface BadgeProps {\n label: string;\n variant?: BadgeVariant;\n /** Manual override, takes precedence over `variant`. */\n backgroundColor?: string;\n textColor?: string;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n badge: {\n paddingHorizontal: theme.tokens.spacing.md - 2,\n paddingVertical: theme.tokens.spacing.xs + 1,\n borderRadius: theme.tokens.radius.full,\n alignSelf: 'flex-start',\n },\n text: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.bold, letterSpacing: 0.3 },\n}));\n\n/**\n * Badge\n * Pill de estado. `variant` cubre los casos comunes (éxito/error/advertencia/\n * info/neutral); `backgroundColor`/`textColor` quedan para casos que no\n * encajan en el set semántico.\n */\nexport const Badge = memo(({ label, variant = 'neutral', backgroundColor, textColor, style }: BadgeProps) => {\n const theme = useTheme();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const { colors } = theme;\n\n const palette: Record<BadgeVariant, { bg: string; fg: string }> = {\n // fg es el rol `*Text`, no el acento pleno: el acento sobre su propio\n // tinte da ~2.2:1 en light — el label quedaba ilegible.\n success: { bg: colors.successSoft, fg: colors.successText },\n error: { bg: colors.errorSoft, fg: colors.errorText },\n warning: { bg: colors.warningSoft, fg: colors.warningText },\n info: { bg: colors.secondarySoft, fg: colors.secondaryText },\n neutral: { bg: colors.inputBackground, fg: colors.textLight },\n };\n\n return (\n <View style={[styles.badge, { backgroundColor: backgroundColor ?? palette[variant].bg }, style]}>\n <Text allowFontScaling={allowFontScaling} style={[styles.text, { color: textColor ?? palette[variant].fg }]}>\n {label}\n </Text>\n </View>\n );\n});\nBadge.displayName = 'Badge';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/BottomSheet.tsx",
|
|
12
|
-
"content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n Dimensions,\n Keyboard,\n KeyboardAvoidingView,\n Modal,\n PanResponder,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useModalSafeAreaBoundary } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\nimport { Transition } from '../atoms/Transition';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\nconst DISMISS_THRESHOLD = 100;\n\nexport type FooterPlacement = 'fixed' | 'scroll' | 'hide-with-keyboard';\n\nexport interface BottomSheetProps {\n visible: boolean;\n onClose: () => void;\n children: React.ReactNode;\n /**
|
|
12
|
+
"content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n Dimensions,\n Keyboard,\n KeyboardAvoidingView,\n Modal,\n PanResponder,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useModalSafeAreaBoundary } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\nimport { Transition } from '../atoms/Transition';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\nconst DISMISS_THRESHOLD = 100;\n\nexport type FooterPlacement = 'fixed' | 'scroll' | 'hide-with-keyboard';\n\nexport interface BottomSheetProps {\n visible: boolean;\n onClose: () => void;\n children: React.ReactNode;\n /** With a title, the header appears, with its close button. */\n title?: string;\n /** Sheet actions. Where they live is decided by `footerPlacement`. */\n footer?: React.ReactNode;\n /**\n * 'fixed' anchors it to the bottom of the sheet; 'scroll' leaves it at the\n * end of the content; 'hide-with-keyboard' hides it while typing, which in a\n * long form gives that strip back to the focused field. @default 'fixed'\n */\n footerPlacement?: FooterPlacement;\n /** The content scrolls when it doesn't fit. @default true */\n scrollable?: boolean;\n /** Dragging the sheet down closes it. @default true */\n draggable?: boolean;\n /** Fraction of the screen (0-1) or pixels. @default 0.9 */\n maxHeight?: number;\n closeAccessibilityLabel?: string;\n containerStyle?: ViewStyle;\n contentStyle?: ViewStyle;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1, justifyContent: 'flex-end' },\n backdrop: { backgroundColor: theme.colors.overlay },\n sheet: {\n backgroundColor: theme.colors.surface,\n borderTopLeftRadius: theme.tokens.radius.xxl,\n borderTopRightRadius: theme.tokens.radius.xxl,\n ...theme.tokens.shadow.lg,\n },\n handleArea: { paddingVertical: theme.tokens.spacing.md, alignItems: 'center' },\n handle: { width: 40, height: 5, borderRadius: 3, backgroundColor: theme.colors.border },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingBottom: theme.tokens.spacing.md,\n },\n headerSpaced: { paddingTop: theme.tokens.spacing.xl },\n title: { flex: 1, fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold },\n content: { paddingHorizontal: theme.tokens.spacing.xl },\n scroll: { flexShrink: 1 },\n footer: {\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingTop: theme.tokens.spacing.md,\n borderTopWidth: 1,\n borderTopColor: theme.colors.border,\n },\n footerInline: { paddingTop: theme.tokens.spacing.xl },\n}));\n\nconst absoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };\n\n/**\n * BottomSheet\n * Hoja que entra desde abajo, con arrastre para cerrar (PanResponder, no un\n * gesture-handler externo: la librería es cero dependencias). Header, footer y\n * scroll son opcionales, así que cubre tanto la hoja desnuda como el formulario\n * completo.\n */\nexport const BottomSheet = memo(\n ({\n visible,\n onClose,\n children,\n title,\n footer,\n footerPlacement = 'fixed',\n scrollable = true,\n draggable = true,\n maxHeight = 0.9,\n closeAccessibilityLabel = 'Close',\n containerStyle,\n contentStyle,\n testID,\n }: BottomSheetProps) => {\n const styles = useStyles();\n const colors = useColors();\n const ModalSafeAreaBoundary = useModalSafeAreaBoundary();\n const [mounted, setMounted] = useState(visible);\n const [keyboardOpen, setKeyboardOpen] = useState(false);\n const progress = useRef(new Animated.Value(0)).current;\n // El PanResponder de más abajo se crea una sola vez; leer `onClose`\n // directo del closure daría siempre el de ese primer render. El ref\n // refleja la última prop sin importar cuándo se creó la closure.\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n\n useEffect(() => {\n const animation = visible\n ? Animated.timing(progress, { toValue: 1, duration: 250, useNativeDriver: true })\n : Animated.timing(progress, { toValue: 0, duration: 200, useNativeDriver: true });\n if (visible) setMounted(true);\n animation.start(({ finished }) => {\n if (!visible && finished) setMounted(false);\n });\n // Si el efecto vuelve a correr (o el componente se desmonta) antes de\n // que termine, se corta: si no, un `finished` tardío podría llamar\n // `setMounted` sobre un componente ya desmontado.\n return () => animation.stop();\n }, [visible, progress]);\n\n useEffect(() => {\n if (footerPlacement !== 'hide-with-keyboard') return;\n const [show, hide] =\n Platform.OS === 'ios' ? ['keyboardWillShow', 'keyboardWillHide'] : ['keyboardDidShow', 'keyboardDidHide'];\n const subs = [\n Keyboard.addListener(show as 'keyboardDidShow', () => setKeyboardOpen(true)),\n Keyboard.addListener(hide as 'keyboardDidHide', () => setKeyboardOpen(false)),\n ];\n return () => subs.forEach((sub) => sub.remove());\n }, [footerPlacement]);\n\n const panResponder = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 5 && Math.abs(gesture.dy) > Math.abs(gesture.dx),\n onPanResponderMove: (_, gesture) => {\n if (gesture.dy > 0) progress.setValue(1 - Math.min(gesture.dy, SCREEN_HEIGHT) / SCREEN_HEIGHT);\n },\n onPanResponderRelease: (_, gesture) => {\n if (gesture.dy > DISMISS_THRESHOLD) {\n onCloseRef.current();\n } else {\n Animated.timing(progress, { toValue: 1, duration: 150, useNativeDriver: true }).start();\n }\n },\n })\n ).current;\n\n if (!mounted) return null;\n\n // Porcentaje y no píxeles: se mide contra el contenedor, que con el teclado\n // abierto es más bajo, así que la hoja entra siempre entera.\n const resolvedMaxHeight: number | `${number}%` = maxHeight <= 1 ? `${maxHeight * 100}%` : maxHeight;\n const hasHeader = !!title;\n\n const scrolledFooter = !!footer && footerPlacement === 'scroll' && (\n <View style={styles.footerInline}>{footer}</View>\n );\n const pinnedFooter =\n !!footer && footerPlacement !== 'scroll' && !(footerPlacement === 'hide-with-keyboard' && keyboardOpen) ? (\n <View style={styles.footer}>{footer}</View>\n ) : null;\n\n const body = scrollable ? (\n <ScrollView\n style={styles.scroll}\n contentContainerStyle={[styles.content, contentStyle]}\n keyboardShouldPersistTaps=\"handled\"\n showsVerticalScrollIndicator={false}\n >\n {children}\n {scrolledFooter}\n </ScrollView>\n ) : (\n <View style={[styles.content, contentStyle]}>\n {children}\n {scrolledFooter}\n </View>\n );\n\n return (\n <Modal visible transparent animationType=\"none\" statusBarTranslucent onRequestClose={onClose}>\n {/* La hoja está anclada abajo, así que el teclado la tapa entera: hay\n que empujarla, no ajustar insets adentro del scroll. Una sola\n compensación, la del contenedor. */}\n <KeyboardAvoidingView\n style={styles.container}\n behavior={Platform.OS === 'ios' ? 'padding' : 'height'}\n >\n <Transition value={progress} preset=\"fade\" style={[absoluteFill, styles.backdrop]}>\n {/* Con header, el botón de cerrar ya expone la acción: repetir el\n label acá deja dos elementos con el mismo nombre. */}\n <Pressable\n style={absoluteFill}\n onPress={onClose}\n testID=\"bottom-sheet-backdrop\"\n accessibilityRole={hasHeader ? undefined : 'button'}\n accessibilityLabel={hasHeader ? undefined : closeAccessibilityLabel}\n />\n </Transition>\n\n {/* Insets medidos dentro de esta ventana nativa (el Modal nativo que\n usa BottomSheet por debajo), no los de la raíz de la app. */}\n <ModalSafeAreaBoundary>\n {(insets) => (\n <Transition\n value={progress}\n preset=\"slide-up\"\n distance={SCREEN_HEIGHT}\n testID={testID}\n style={[\n styles.sheet,\n { maxHeight: resolvedMaxHeight, paddingBottom: Math.max(insets.bottom, 20) },\n containerStyle,\n ]}\n >\n {draggable && (\n <View style={styles.handleArea} testID=\"bottom-sheet-handle\" {...panResponder.panHandlers}>\n <View style={styles.handle} />\n </View>\n )}\n\n {hasHeader && (\n <View style={[styles.header, !draggable && styles.headerSpaced]}>\n <Title style={styles.title} numberOfLines={1}>\n {title}\n </Title>\n <IconButton\n iconName=\"close\"\n size={24}\n color={colors.text}\n onPress={onClose}\n accessibilityLabel={closeAccessibilityLabel}\n />\n </View>\n )}\n\n {body}\n\n {pinnedFooter}\n </Transition>\n )}\n </ModalSafeAreaBoundary>\n </KeyboardAvoidingView>\n </Modal>\n );\n }\n);\nBottomSheet.displayName = 'BottomSheet';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
package/registry/core.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "theme/UIProvider.tsx",
|
|
24
|
-
"content": "import React, { createContext, useContext, useMemo, useState, useCallback, useRef } from 'react';\nimport { Appearance, useColorScheme } from 'react-native';\nimport { defaultTheme } from './createTheme';\nimport { defaultLabels, type Labels } from './labels';\nimport { zeroInsets, type EdgeInsets } from './insets';\nimport type { Theme, ThemePair } from './types';\nimport { renderDefaultIcon } from '../icons/defaultIcons';\nimport type { IconRenderer } from '../icons/types';\n\nexport type ThemeMode = 'system' | 'light' | 'dark';\n\n/**\n * Un `<Modal>`/`<BottomSheet>` nativo abre su propia ventana (en Android\n * siempre; en iOS según `presentationStyle`), así que los insets medidos en\n * la raíz de la app pueden no valer ahí adentro. Este boundary es el punto de\n * inyección: el default sólo repite los insets ambientes (comportamiento de\n * siempre, cero deps), pero `SafeAreaUIProvider` lo reemplaza por uno que\n * monta un `<SafeAreaProvider>` nuevo y remide dentro de esa ventana.\n */\nexport interface ModalSafeAreaBoundaryProps {\n children: (insets: EdgeInsets) => React.ReactNode;\n}\nexport type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;\n\nfunction DefaultModalSafeAreaBoundary({ children }: ModalSafeAreaBoundaryProps) {\n const insets = useInsets();\n return <>{children(insets)}</>;\n}\n\ninterface UIContextValue {\n theme: Theme;\n mode: ThemeMode;\n setMode: (mode: ThemeMode) => void;\n icons: IconRenderer;\n insets: EdgeInsets;\n labels: Labels;\n allowFontScaling: boolean;\n ModalSafeAreaBoundary: ModalSafeAreaBoundaryComponent;\n}\n\nconst UIContext = createContext<UIContextValue | undefined>(undefined);\n\nexport interface UIProviderProps {\n children: React.ReactNode;\n /**
|
|
24
|
+
"content": "import React, { createContext, useContext, useMemo, useState, useCallback, useRef } from 'react';\nimport { Appearance, useColorScheme } from 'react-native';\nimport { defaultTheme } from './createTheme';\nimport { defaultLabels, type Labels } from './labels';\nimport { zeroInsets, type EdgeInsets } from './insets';\nimport type { Theme, ThemePair } from './types';\nimport { renderDefaultIcon } from '../icons/defaultIcons';\nimport type { IconRenderer } from '../icons/types';\n\nexport type ThemeMode = 'system' | 'light' | 'dark';\n\n/**\n * Un `<Modal>`/`<BottomSheet>` nativo abre su propia ventana (en Android\n * siempre; en iOS según `presentationStyle`), así que los insets medidos en\n * la raíz de la app pueden no valer ahí adentro. Este boundary es el punto de\n * inyección: el default sólo repite los insets ambientes (comportamiento de\n * siempre, cero deps), pero `SafeAreaUIProvider` lo reemplaza por uno que\n * monta un `<SafeAreaProvider>` nuevo y remide dentro de esa ventana.\n */\nexport interface ModalSafeAreaBoundaryProps {\n children: (insets: EdgeInsets) => React.ReactNode;\n}\nexport type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;\n\nfunction DefaultModalSafeAreaBoundary({ children }: ModalSafeAreaBoundaryProps) {\n const insets = useInsets();\n return <>{children(insets)}</>;\n}\n\ninterface UIContextValue {\n theme: Theme;\n mode: ThemeMode;\n setMode: (mode: ThemeMode) => void;\n icons: IconRenderer;\n insets: EdgeInsets;\n labels: Labels;\n allowFontScaling: boolean;\n ModalSafeAreaBoundary: ModalSafeAreaBoundaryComponent;\n}\n\nconst UIContext = createContext<UIContextValue | undefined>(undefined);\n\nexport interface UIProviderProps {\n children: React.ReactNode;\n /** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */\n theme?: ThemePair;\n /** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */\n defaultMode?: ThemeMode;\n /** Controlled mode. If passed together with onModeChange, the provider doesn't manage its own state. */\n mode?: ThemeMode;\n onModeChange?: (mode: ThemeMode) => void;\n /** Icon renderer. Defaults to the included zero-dep glyphs. */\n icons?: IconRenderer;\n /** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */\n insets?: EdgeInsets;\n labels?: Partial<Labels>;\n /** @default false, for parity with the rest of the library (accessibility first). */\n allowFontScaling?: boolean;\n /**\n * Source of insets used by `Modal`/`BottomSheet` for their own native\n * window. Don't pass this by hand — `SafeAreaUIProvider` wires it up itself.\n * @default repeats the ambient insets (behavior without safe-area-context)\n */\n ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;\n}\n\n/**\n * UIProvider\n * Único punto de entrada de la librería: resuelve el theme activo (sistema u\n * override manual), e inyecta íconos, insets y textos por defecto a todo el\n * árbol. No persiste el modo — si el consumidor quiere recordar la preferencia\n * entre sesiones, controla `mode`/`onModeChange` y persiste donde prefiera.\n */\nexport function UIProvider({\n children,\n theme = defaultTheme,\n defaultMode = 'system',\n mode: controlledMode,\n onModeChange,\n icons = renderDefaultIcon,\n insets = zeroInsets,\n labels: labelOverrides,\n allowFontScaling = false,\n ModalSafeAreaBoundary = DefaultModalSafeAreaBoundary,\n}: UIProviderProps) {\n const systemScheme = useColorScheme();\n const [uncontrolledMode, setUncontrolledMode] = useState<ThemeMode>(defaultMode);\n const isControlled = controlledMode !== undefined;\n const mode = isControlled ? controlledMode : uncontrolledMode;\n\n const setMode = useCallback(\n (next: ThemeMode) => {\n if (!isControlled) setUncontrolledMode(next);\n onModeChange?.(next);\n },\n [isControlled, onModeChange]\n );\n\n // Por instancia, no por módulo: dos `<UIProvider>` en el mismo árbol (tests,\n // un modal con su propio provider) avisan cada uno si le faltan insets, en\n // vez de que el segundo se calle porque el primero ya avisó una vez.\n const warnedAboutInsets = useRef(false);\n\n // Olvidar `insets` no rompe nada visible al montar: el default de ceros\n // recién se nota cuando abrís un Modal `full` (se mete abajo del notch) o un\n // BottomSheet (queda pegado a la barra de gestos). El aviso llega antes.\n if (__DEV__ && insets === zeroInsets && !warnedAboutInsets.current) {\n warnedAboutInsets.current = true;\n console.warn(\n 'orn-ui: <UIProvider> sin `insets`, se usan {top:0,bottom:0,left:0,right:0}. ' +\n 'Los modales y el Screen quedan sin safe area. Pasá `insets={useSafeAreaInsets()}` ' +\n \"o usá <SafeAreaUIProvider> de 'orn-ui/safe-area', que lo cablea solo.\"\n );\n }\n\n const scheme: 'light' | 'dark' =\n mode === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : mode;\n\n const resolvedTheme = theme[scheme];\n\n const labels = useMemo(\n () => (labelOverrides ? { ...defaultLabels, ...labelOverrides } : defaultLabels),\n [labelOverrides]\n );\n\n const value = useMemo<UIContextValue>(\n () => ({\n theme: resolvedTheme,\n mode,\n setMode,\n icons,\n insets,\n labels,\n allowFontScaling,\n ModalSafeAreaBoundary,\n }),\n [resolvedTheme, mode, setMode, icons, insets, labels, allowFontScaling, ModalSafeAreaBoundary]\n );\n\n return <UIContext.Provider value={value}>{children}</UIContext.Provider>;\n}\n\nfunction useUIContext(): UIContextValue {\n const ctx = useContext(UIContext);\n if (!ctx) {\n throw new Error('orn-ui: this hook must be used within a <UIProvider>.');\n }\n return ctx;\n}\n\nexport function useTheme(): Theme {\n return useUIContext().theme;\n}\n\nexport function useColors(): Theme['colors'] {\n return useUIContext().theme.colors;\n}\n\nexport function useThemeMode() {\n const { mode, setMode } = useUIContext();\n return { mode, setMode };\n}\n\nexport function useIcons(): IconRenderer {\n return useUIContext().icons;\n}\n\nexport function useInsets(): EdgeInsets {\n return useUIContext().insets;\n}\n\nexport function useLabels(): Labels {\n return useUIContext().labels;\n}\n\nexport function useAllowFontScaling(): boolean {\n return useUIContext().allowFontScaling;\n}\n\n/** Interno: lo consumen `Modal`/`BottomSheet` para remedir insets dentro de su propia ventana nativa. */\nexport function useModalSafeAreaBoundary(): ModalSafeAreaBoundaryComponent {\n return useUIContext().ModalSafeAreaBoundary;\n}\n\n/** Re-exportado para que tests/consumidores puedan simular cambios de sistema sin RN real. */\nexport const __testUtils = { Appearance };\n"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "theme/colors.ts",
|
package/registry/date-field.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/DateField.tsx",
|
|
12
|
-
"content": "import React, { memo, useCallback, useState } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Modal } from './Modal';\nimport { DatePicker, type DatePickerProps, type DateRange } from './DatePicker';\nimport { Button } from '../atoms/Button';\n\nexport interface DateFieldProps\n extends Pick<DatePickerProps, 'minDate' | 'maxDate' | 'monthNames' | 'weekdayNames' | 'firstDayOfWeek'> {\n label?: string;\n required?: boolean;\n /**\n * 'single'
|
|
12
|
+
"content": "import React, { memo, useCallback, useState } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Modal } from './Modal';\nimport { DatePicker, type DatePickerProps, type DateRange } from './DatePicker';\nimport { Button } from '../atoms/Button';\n\nexport interface DateFieldProps\n extends Pick<DatePickerProps, 'minDate' | 'maxDate' | 'monthNames' | 'weekdayNames' | 'firstDayOfWeek'> {\n label?: string;\n required?: boolean;\n /**\n * 'single' picks one date; 'range' picks an interval with two taps.\n * @default 'single'\n */\n mode?: 'single' | 'range';\n /** Selected date in mode=\"single\". */\n value?: Date;\n /** Called in mode=\"single\". */\n onChange?: (date: Date) => void;\n /** Selected range in mode=\"range\". */\n range?: DateRange;\n /** Called in mode=\"range\", both when setting the start and when closing the range. */\n onRangeChange?: (range: DateRange) => void;\n placeholder?: string;\n error?: string;\n disabled?: boolean;\n /** Formats the date in the field. @default \"Month D, YYYY\" */\n format?: (date: Date) => string;\n /** Title of the calendar modal. @default the `selectDate` from `useLabels()` */\n modalTitle?: string;\n /**\n * If provided, the modal shows a button that clears the selection and closes.\n * Without this there's no button: the previous \"Clear\" only closed, without clearing anything.\n */\n onClear?: () => void;\n /** Text for that button. @default the `clear` from `useLabels()` */\n clearLabel?: string;\n containerStyle?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nfunction defaultFormat(date: Date, monthNames: string[]): string {\n return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { marginBottom: theme.tokens.spacing.lg, width: '100%', gap: theme.tokens.spacing.sm },\n label: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.text },\n required: { color: theme.colors.errorText },\n field: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: theme.colors.inputBackground,\n borderRadius: theme.tokens.radius.lg,\n borderWidth: 2,\n borderColor: theme.colors.border,\n minHeight: 50,\n paddingHorizontal: theme.tokens.spacing.lg,\n },\n fieldError: { borderColor: theme.colors.error },\n fieldText: { fontSize: theme.tokens.fontSize.lg, color: theme.colors.text },\n placeholder: { color: theme.colors.textLight },\n errorText: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.errorText },\n}));\n\n/**\n * DateField\n * Campo de fecha: se ve como un Input, abre un `DatePicker` en un Modal al\n * tocarlo. Sin picker nativo del sistema, así que se ve y se comporta igual\n * en iOS y Android.\n */\nexport const DateField = memo(\n ({\n label,\n required = false,\n mode = 'single',\n value,\n onChange,\n range,\n onRangeChange,\n placeholder = 'Select a date',\n error,\n disabled = false,\n format,\n modalTitle,\n onClear,\n clearLabel,\n containerStyle,\n monthNames,\n testID,\n ...pickerProps\n }: DateFieldProps) => {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const allowFontScaling = useAllowFontScaling();\n const [open, setOpen] = useState(false);\n\n const months = monthNames ?? labels.months;\n const isRange = mode === 'range';\n\n const formatDate = (date: Date) => (format ? format(date) : defaultFormat(date, months));\n\n // El calendario se cierra solo cuando ya no falta nada por elegir: en\n // rango, recién cuando el segundo toque cierra el intervalo.\n const handleChange = useCallback(\n (date: Date) => {\n onChange?.(date);\n setOpen(false);\n },\n [onChange]\n );\n\n const handleRangeChange = useCallback(\n (next: DateRange) => {\n onRangeChange?.(next);\n if (next.end) setOpen(false);\n },\n [onRangeChange]\n );\n\n const handleClear = useCallback(() => {\n onClear?.();\n setOpen(false);\n }, [onClear]);\n\n const hasValue = isRange ? !!range?.start : !!value;\n let display = placeholder;\n if (isRange && range?.start) {\n display = range.end ? `${formatDate(range.start)} — ${formatDate(range.end)}` : formatDate(range.start);\n } else if (!isRange && value) {\n display = formatDate(value);\n }\n\n return (\n <View style={[styles.container, containerStyle]} testID={testID}>\n {!!label && (\n <Text allowFontScaling={allowFontScaling} style={styles.label}>\n {label}\n {required && <Text style={styles.required}> *</Text>}\n </Text>\n )}\n\n <TouchableOpacity\n style={[styles.field, !!error && styles.fieldError, disabled && { opacity: 0.5 }]}\n onPress={() => setOpen(true)}\n disabled={disabled}\n activeOpacity={0.7}\n accessibilityRole=\"button\"\n accessibilityLabel={hasValue ? `${label ?? placeholder}, ${display}` : (label ?? placeholder)}\n accessibilityState={{ disabled, expanded: open }}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !hasValue && styles.placeholder]}>\n {display}\n </Text>\n <Text allowFontScaling={allowFontScaling} style={{ color: colors.textLight }}>\n {'▾'}\n </Text>\n </TouchableOpacity>\n\n {!!error && (\n <Text allowFontScaling={allowFontScaling} style={styles.errorText}>\n {error}\n </Text>\n )}\n\n <Modal\n visible={open}\n onClose={() => setOpen(false)}\n title={modalTitle ?? labels.selectDate}\n variant=\"overlay\"\n scrollable={false}\n footer={\n onClear ? <Button title={clearLabel ?? labels.clear} variant=\"ghost\" onPress={handleClear} /> : undefined\n }\n >\n <DatePicker\n mode={mode}\n value={value}\n range={range}\n monthNames={months}\n {...pickerProps}\n onChange={handleChange}\n onRangeChange={handleRangeChange}\n />\n </Modal>\n </View>\n );\n }\n);\nDateField.displayName = 'DateField';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|