orn-ui 0.1.0
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/README.md +107 -0
- package/bin/orn-ui.mjs +228 -0
- package/lib/commonjs/atoms/Avatar.js +42 -0
- package/lib/commonjs/atoms/Avatar.js.map +1 -0
- package/lib/commonjs/atoms/Badge.js +82 -0
- package/lib/commonjs/atoms/Badge.js.map +1 -0
- package/lib/commonjs/atoms/Button.js +222 -0
- package/lib/commonjs/atoms/Button.js.map +1 -0
- package/lib/commonjs/atoms/Card.js +33 -0
- package/lib/commonjs/atoms/Card.js.map +1 -0
- package/lib/commonjs/atoms/Checkbox.js +107 -0
- package/lib/commonjs/atoms/Checkbox.js.map +1 -0
- package/lib/commonjs/atoms/Divider.js +27 -0
- package/lib/commonjs/atoms/Divider.js.map +1 -0
- package/lib/commonjs/atoms/EmptyState.js +69 -0
- package/lib/commonjs/atoms/EmptyState.js.map +1 -0
- package/lib/commonjs/atoms/Fab.js +62 -0
- package/lib/commonjs/atoms/Fab.js.map +1 -0
- package/lib/commonjs/atoms/IconButton.js +60 -0
- package/lib/commonjs/atoms/IconButton.js.map +1 -0
- package/lib/commonjs/atoms/Image.js +113 -0
- package/lib/commonjs/atoms/Image.js.map +1 -0
- package/lib/commonjs/atoms/Input.js +173 -0
- package/lib/commonjs/atoms/Input.js.map +1 -0
- package/lib/commonjs/atoms/KeyValueRow.js +60 -0
- package/lib/commonjs/atoms/KeyValueRow.js.map +1 -0
- package/lib/commonjs/atoms/PressableScale.js +60 -0
- package/lib/commonjs/atoms/PressableScale.js.map +1 -0
- package/lib/commonjs/atoms/Spinner.js +156 -0
- package/lib/commonjs/atoms/Spinner.js.map +1 -0
- package/lib/commonjs/atoms/Text.js +63 -0
- package/lib/commonjs/atoms/Text.js.map +1 -0
- package/lib/commonjs/atoms/index.js +135 -0
- package/lib/commonjs/atoms/index.js.map +1 -0
- package/lib/commonjs/icons/Icon.js +40 -0
- package/lib/commonjs/icons/Icon.js.map +1 -0
- package/lib/commonjs/icons/defaultIcons.js +386 -0
- package/lib/commonjs/icons/defaultIcons.js.map +1 -0
- package/lib/commonjs/icons/index.js +20 -0
- package/lib/commonjs/icons/index.js.map +1 -0
- package/lib/commonjs/icons/types.js +6 -0
- package/lib/commonjs/icons/types.js.map +1 -0
- package/lib/commonjs/index.js +61 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/molecules/AvatarHeader.js +87 -0
- package/lib/commonjs/molecules/AvatarHeader.js.map +1 -0
- package/lib/commonjs/molecules/FormActions.js +47 -0
- package/lib/commonjs/molecules/FormActions.js.map +1 -0
- package/lib/commonjs/molecules/InfoRow.js +83 -0
- package/lib/commonjs/molecules/InfoRow.js.map +1 -0
- package/lib/commonjs/molecules/OptionCard.js +98 -0
- package/lib/commonjs/molecules/OptionCard.js.map +1 -0
- package/lib/commonjs/molecules/Stepper.js +104 -0
- package/lib/commonjs/molecules/Stepper.js.map +1 -0
- package/lib/commonjs/molecules/Steps.js +220 -0
- package/lib/commonjs/molecules/Steps.js.map +1 -0
- package/lib/commonjs/molecules/ThemeToggle.js +91 -0
- package/lib/commonjs/molecules/ThemeToggle.js.map +1 -0
- package/lib/commonjs/molecules/index.js +55 -0
- package/lib/commonjs/molecules/index.js.map +1 -0
- package/lib/commonjs/organisms/Alert.js +223 -0
- package/lib/commonjs/organisms/Alert.js.map +1 -0
- package/lib/commonjs/organisms/AlertProvider.js +109 -0
- package/lib/commonjs/organisms/AlertProvider.js.map +1 -0
- package/lib/commonjs/organisms/BottomSheet.js +154 -0
- package/lib/commonjs/organisms/BottomSheet.js.map +1 -0
- package/lib/commonjs/organisms/DateField.js +147 -0
- package/lib/commonjs/organisms/DateField.js.map +1 -0
- package/lib/commonjs/organisms/DatePicker.js +253 -0
- package/lib/commonjs/organisms/DatePicker.js.map +1 -0
- package/lib/commonjs/organisms/List.js +143 -0
- package/lib/commonjs/organisms/List.js.map +1 -0
- package/lib/commonjs/organisms/Modal.js +286 -0
- package/lib/commonjs/organisms/Modal.js.map +1 -0
- package/lib/commonjs/organisms/Screen.js +75 -0
- package/lib/commonjs/organisms/Screen.js.map +1 -0
- package/lib/commonjs/organisms/SearchList.js +180 -0
- package/lib/commonjs/organisms/SearchList.js.map +1 -0
- package/lib/commonjs/organisms/Select.js +178 -0
- package/lib/commonjs/organisms/Select.js.map +1 -0
- package/lib/commonjs/organisms/Toast.js +157 -0
- package/lib/commonjs/organisms/Toast.js.map +1 -0
- package/lib/commonjs/organisms/ToastProvider.js +131 -0
- package/lib/commonjs/organisms/ToastProvider.js.map +1 -0
- package/lib/commonjs/organisms/Wizard.js +116 -0
- package/lib/commonjs/organisms/Wizard.js.map +1 -0
- package/lib/commonjs/organisms/index.js +127 -0
- package/lib/commonjs/organisms/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/theme/UIProvider.js +110 -0
- package/lib/commonjs/theme/UIProvider.js.map +1 -0
- package/lib/commonjs/theme/createStyles.js +42 -0
- package/lib/commonjs/theme/createStyles.js.map +1 -0
- package/lib/commonjs/theme/createTheme.js +50 -0
- package/lib/commonjs/theme/createTheme.js.map +1 -0
- package/lib/commonjs/theme/index.js +151 -0
- package/lib/commonjs/theme/index.js.map +1 -0
- package/lib/commonjs/theme/insets.js +15 -0
- package/lib/commonjs/theme/insets.js.map +1 -0
- package/lib/commonjs/theme/labels.js +18 -0
- package/lib/commonjs/theme/labels.js.map +1 -0
- package/lib/commonjs/theme/palettes.js +42 -0
- package/lib/commonjs/theme/palettes.js.map +1 -0
- package/lib/commonjs/theme/tokens.js +120 -0
- package/lib/commonjs/theme/tokens.js.map +1 -0
- package/lib/commonjs/theme/types.js +6 -0
- package/lib/commonjs/theme/types.js.map +1 -0
- package/lib/module/atoms/Avatar.js +37 -0
- package/lib/module/atoms/Avatar.js.map +1 -0
- package/lib/module/atoms/Badge.js +77 -0
- package/lib/module/atoms/Badge.js.map +1 -0
- package/lib/module/atoms/Button.js +217 -0
- package/lib/module/atoms/Button.js.map +1 -0
- package/lib/module/atoms/Card.js +28 -0
- package/lib/module/atoms/Card.js.map +1 -0
- package/lib/module/atoms/Checkbox.js +102 -0
- package/lib/module/atoms/Checkbox.js.map +1 -0
- package/lib/module/atoms/Divider.js +22 -0
- package/lib/module/atoms/Divider.js.map +1 -0
- package/lib/module/atoms/EmptyState.js +64 -0
- package/lib/module/atoms/EmptyState.js.map +1 -0
- package/lib/module/atoms/Fab.js +57 -0
- package/lib/module/atoms/Fab.js.map +1 -0
- package/lib/module/atoms/IconButton.js +55 -0
- package/lib/module/atoms/IconButton.js.map +1 -0
- package/lib/module/atoms/Image.js +108 -0
- package/lib/module/atoms/Image.js.map +1 -0
- package/lib/module/atoms/Input.js +168 -0
- package/lib/module/atoms/Input.js.map +1 -0
- package/lib/module/atoms/KeyValueRow.js +55 -0
- package/lib/module/atoms/KeyValueRow.js.map +1 -0
- package/lib/module/atoms/PressableScale.js +55 -0
- package/lib/module/atoms/PressableScale.js.map +1 -0
- package/lib/module/atoms/Spinner.js +151 -0
- package/lib/module/atoms/Spinner.js.map +1 -0
- package/lib/module/atoms/Text.js +58 -0
- package/lib/module/atoms/Text.js.map +1 -0
- package/lib/module/atoms/index.js +18 -0
- package/lib/module/atoms/index.js.map +1 -0
- package/lib/module/icons/Icon.js +35 -0
- package/lib/module/icons/Icon.js.map +1 -0
- package/lib/module/icons/defaultIcons.js +381 -0
- package/lib/module/icons/defaultIcons.js.map +1 -0
- package/lib/module/icons/index.js +5 -0
- package/lib/module/icons/index.js.map +1 -0
- package/lib/module/icons/types.js +4 -0
- package/lib/module/icons/types.js.map +1 -0
- package/lib/module/index.js +8 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/molecules/AvatarHeader.js +82 -0
- package/lib/module/molecules/AvatarHeader.js.map +1 -0
- package/lib/module/molecules/FormActions.js +42 -0
- package/lib/module/molecules/FormActions.js.map +1 -0
- package/lib/module/molecules/InfoRow.js +78 -0
- package/lib/module/molecules/InfoRow.js.map +1 -0
- package/lib/module/molecules/OptionCard.js +93 -0
- package/lib/module/molecules/OptionCard.js.map +1 -0
- package/lib/module/molecules/Stepper.js +99 -0
- package/lib/module/molecules/Stepper.js.map +1 -0
- package/lib/module/molecules/Steps.js +215 -0
- package/lib/module/molecules/Steps.js.map +1 -0
- package/lib/module/molecules/ThemeToggle.js +86 -0
- package/lib/module/molecules/ThemeToggle.js.map +1 -0
- package/lib/module/molecules/index.js +10 -0
- package/lib/module/molecules/index.js.map +1 -0
- package/lib/module/organisms/Alert.js +218 -0
- package/lib/module/organisms/Alert.js.map +1 -0
- package/lib/module/organisms/AlertProvider.js +103 -0
- package/lib/module/organisms/AlertProvider.js.map +1 -0
- package/lib/module/organisms/BottomSheet.js +149 -0
- package/lib/module/organisms/BottomSheet.js.map +1 -0
- package/lib/module/organisms/DateField.js +142 -0
- package/lib/module/organisms/DateField.js.map +1 -0
- package/lib/module/organisms/DatePicker.js +247 -0
- package/lib/module/organisms/DatePicker.js.map +1 -0
- package/lib/module/organisms/List.js +138 -0
- package/lib/module/organisms/List.js.map +1 -0
- package/lib/module/organisms/Modal.js +281 -0
- package/lib/module/organisms/Modal.js.map +1 -0
- package/lib/module/organisms/Screen.js +70 -0
- package/lib/module/organisms/Screen.js.map +1 -0
- package/lib/module/organisms/SearchList.js +175 -0
- package/lib/module/organisms/SearchList.js.map +1 -0
- package/lib/module/organisms/Select.js +173 -0
- package/lib/module/organisms/Select.js.map +1 -0
- package/lib/module/organisms/Toast.js +152 -0
- package/lib/module/organisms/Toast.js.map +1 -0
- package/lib/module/organisms/ToastProvider.js +125 -0
- package/lib/module/organisms/ToastProvider.js.map +1 -0
- package/lib/module/organisms/Wizard.js +111 -0
- package/lib/module/organisms/Wizard.js.map +1 -0
- package/lib/module/organisms/index.js +16 -0
- package/lib/module/organisms/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/theme/UIProvider.js +97 -0
- package/lib/module/theme/UIProvider.js.map +1 -0
- package/lib/module/theme/createStyles.js +38 -0
- package/lib/module/theme/createStyles.js.map +1 -0
- package/lib/module/theme/createTheme.js +45 -0
- package/lib/module/theme/createTheme.js.map +1 -0
- package/lib/module/theme/index.js +10 -0
- package/lib/module/theme/index.js.map +1 -0
- package/lib/module/theme/insets.js +11 -0
- package/lib/module/theme/insets.js.map +1 -0
- package/lib/module/theme/labels.js +14 -0
- package/lib/module/theme/labels.js.map +1 -0
- package/lib/module/theme/palettes.js +38 -0
- package/lib/module/theme/palettes.js.map +1 -0
- package/lib/module/theme/tokens.js +117 -0
- package/lib/module/theme/tokens.js.map +1 -0
- package/lib/module/theme/types.js +4 -0
- package/lib/module/theme/types.js.map +1 -0
- package/lib/typescript/atoms/Avatar.d.ts +15 -0
- package/lib/typescript/atoms/Avatar.d.ts.map +1 -0
- package/lib/typescript/atoms/Badge.d.ts +19 -0
- package/lib/typescript/atoms/Badge.d.ts.map +1 -0
- package/lib/typescript/atoms/Button.d.ts +30 -0
- package/lib/typescript/atoms/Button.d.ts.map +1 -0
- package/lib/typescript/atoms/Card.d.ts +8 -0
- package/lib/typescript/atoms/Card.d.ts.map +1 -0
- package/lib/typescript/atoms/Checkbox.d.ts +11 -0
- package/lib/typescript/atoms/Checkbox.d.ts.map +1 -0
- package/lib/typescript/atoms/Divider.d.ts +8 -0
- package/lib/typescript/atoms/Divider.d.ts.map +1 -0
- package/lib/typescript/atoms/EmptyState.d.ts +12 -0
- package/lib/typescript/atoms/EmptyState.d.ts.map +1 -0
- package/lib/typescript/atoms/Fab.d.ts +22 -0
- package/lib/typescript/atoms/Fab.d.ts.map +1 -0
- package/lib/typescript/atoms/IconButton.d.ts +23 -0
- package/lib/typescript/atoms/IconButton.d.ts.map +1 -0
- package/lib/typescript/atoms/Image.d.ts +45 -0
- package/lib/typescript/atoms/Image.d.ts.map +1 -0
- package/lib/typescript/atoms/Input.d.ts +24 -0
- package/lib/typescript/atoms/Input.d.ts.map +1 -0
- package/lib/typescript/atoms/KeyValueRow.d.ts +17 -0
- package/lib/typescript/atoms/KeyValueRow.d.ts.map +1 -0
- package/lib/typescript/atoms/PressableScale.d.ts +15 -0
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -0
- package/lib/typescript/atoms/Spinner.d.ts +20 -0
- package/lib/typescript/atoms/Spinner.d.ts.map +1 -0
- package/lib/typescript/atoms/Text.d.ts +13 -0
- package/lib/typescript/atoms/Text.d.ts.map +1 -0
- package/lib/typescript/atoms/index.d.ts +31 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -0
- package/lib/typescript/icons/Icon.d.ts +18 -0
- package/lib/typescript/icons/Icon.d.ts.map +1 -0
- package/lib/typescript/icons/defaultIcons.d.ts +5 -0
- package/lib/typescript/icons/defaultIcons.d.ts.map +1 -0
- package/lib/typescript/icons/index.d.ts +5 -0
- package/lib/typescript/icons/index.d.ts.map +1 -0
- package/lib/typescript/icons/types.d.ts +10 -0
- package/lib/typescript/icons/types.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +6 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/molecules/AvatarHeader.d.ts +20 -0
- package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -0
- package/lib/typescript/molecules/FormActions.d.ts +21 -0
- package/lib/typescript/molecules/FormActions.d.ts.map +1 -0
- package/lib/typescript/molecules/InfoRow.d.ts +17 -0
- package/lib/typescript/molecules/InfoRow.d.ts.map +1 -0
- package/lib/typescript/molecules/OptionCard.d.ts +20 -0
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -0
- package/lib/typescript/molecules/Stepper.d.ts +15 -0
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -0
- package/lib/typescript/molecules/Steps.d.ts +29 -0
- package/lib/typescript/molecules/Steps.d.ts.map +1 -0
- package/lib/typescript/molecules/ThemeToggle.d.ts +15 -0
- package/lib/typescript/molecules/ThemeToggle.d.ts.map +1 -0
- package/lib/typescript/molecules/index.d.ts +15 -0
- package/lib/typescript/molecules/index.d.ts.map +1 -0
- package/lib/typescript/organisms/Alert.d.ts +29 -0
- package/lib/typescript/organisms/Alert.d.ts.map +1 -0
- package/lib/typescript/organisms/AlertProvider.d.ts +32 -0
- package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -0
- package/lib/typescript/organisms/BottomSheet.d.ts +15 -0
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -0
- package/lib/typescript/organisms/DateField.d.ts +26 -0
- package/lib/typescript/organisms/DateField.d.ts.map +1 -0
- package/lib/typescript/organisms/DatePicker.d.ts +49 -0
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -0
- package/lib/typescript/organisms/List.d.ts +35 -0
- package/lib/typescript/organisms/List.d.ts.map +1 -0
- package/lib/typescript/organisms/Modal.d.ts +25 -0
- package/lib/typescript/organisms/Modal.d.ts.map +1 -0
- package/lib/typescript/organisms/Screen.d.ts +26 -0
- package/lib/typescript/organisms/Screen.d.ts.map +1 -0
- package/lib/typescript/organisms/SearchList.d.ts +40 -0
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -0
- package/lib/typescript/organisms/Select.d.ts +25 -0
- package/lib/typescript/organisms/Select.d.ts.map +1 -0
- package/lib/typescript/organisms/Toast.d.ts +28 -0
- package/lib/typescript/organisms/Toast.d.ts.map +1 -0
- package/lib/typescript/organisms/ToastProvider.d.ts +36 -0
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -0
- package/lib/typescript/organisms/Wizard.d.ts +40 -0
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -0
- package/lib/typescript/organisms/index.d.ts +27 -0
- package/lib/typescript/organisms/index.d.ts.map +1 -0
- package/lib/typescript/theme/UIProvider.d.ts +47 -0
- package/lib/typescript/theme/UIProvider.d.ts.map +1 -0
- package/lib/typescript/theme/createStyles.d.ts +21 -0
- package/lib/typescript/theme/createStyles.d.ts.map +1 -0
- package/lib/typescript/theme/createTheme.d.ts +9 -0
- package/lib/typescript/theme/createTheme.d.ts.map +1 -0
- package/lib/typescript/theme/index.d.ts +14 -0
- package/lib/typescript/theme/index.d.ts.map +1 -0
- package/lib/typescript/theme/insets.d.ts +9 -0
- package/lib/typescript/theme/insets.d.ts.map +1 -0
- package/lib/typescript/theme/labels.d.ts +13 -0
- package/lib/typescript/theme/labels.d.ts.map +1 -0
- package/lib/typescript/theme/palettes.d.ts +24 -0
- package/lib/typescript/theme/palettes.d.ts.map +1 -0
- package/lib/typescript/theme/tokens.d.ts +41 -0
- package/lib/typescript/theme/tokens.d.ts.map +1 -0
- package/lib/typescript/theme/types.d.ts +24 -0
- package/lib/typescript/theme/types.d.ts.map +1 -0
- package/package.json +311 -0
- package/registry/alert-provider.json +21 -0
- package/registry/alert.json +19 -0
- package/registry/avatar-header.json +18 -0
- package/registry/avatar.json +18 -0
- package/registry/badge.json +18 -0
- package/registry/bottom-sheet.json +18 -0
- package/registry/button.json +18 -0
- package/registry/card.json +18 -0
- package/registry/checkbox.json +18 -0
- package/registry/core.json +64 -0
- package/registry/date-field.json +24 -0
- package/registry/date-picker.json +23 -0
- package/registry/divider.json +18 -0
- package/registry/empty-state.json +18 -0
- package/registry/fab.json +19 -0
- package/registry/form-actions.json +19 -0
- package/registry/icon-button.json +19 -0
- package/registry/image.json +20 -0
- package/registry/index.json +465 -0
- package/registry/info-row.json +19 -0
- package/registry/input.json +18 -0
- package/registry/key-value-row.json +18 -0
- package/registry/list.json +20 -0
- package/registry/manifest.json +263 -0
- package/registry/modal.json +21 -0
- package/registry/option-card.json +18 -0
- package/registry/pressable-scale.json +18 -0
- package/registry/screen.json +18 -0
- package/registry/search-list.json +23 -0
- package/registry/select.json +18 -0
- package/registry/spinner.json +18 -0
- package/registry/stepper.json +18 -0
- package/registry/steps.json +18 -0
- package/registry/theme-toggle.json +18 -0
- package/registry/title.json +21 -0
- package/registry/toast-provider.json +20 -0
- package/registry/toast.json +18 -0
- package/registry/wizard.json +20 -0
- package/src/atoms/Avatar.tsx +40 -0
- package/src/atoms/Badge.tsx +55 -0
- package/src/atoms/Button.tsx +186 -0
- package/src/atoms/Card.tsx +27 -0
- package/src/atoms/Checkbox.tsx +75 -0
- package/src/atoms/Divider.tsx +14 -0
- package/src/atoms/EmptyState.tsx +60 -0
- package/src/atoms/Fab.tsx +63 -0
- package/src/atoms/IconButton.tsx +53 -0
- package/src/atoms/Image.tsx +137 -0
- package/src/atoms/Input.tsx +166 -0
- package/src/atoms/KeyValueRow.tsx +48 -0
- package/src/atoms/PressableScale.tsx +52 -0
- package/src/atoms/Spinner.tsx +127 -0
- package/src/atoms/Text.tsx +58 -0
- package/src/atoms/index.ts +30 -0
- package/src/icons/Icon.tsx +27 -0
- package/src/icons/defaultIcons.tsx +292 -0
- package/src/icons/index.ts +4 -0
- package/src/icons/types.ts +27 -0
- package/src/index.ts +5 -0
- package/src/molecules/AvatarHeader.tsx +69 -0
- package/src/molecules/FormActions.tsx +52 -0
- package/src/molecules/InfoRow.tsx +52 -0
- package/src/molecules/OptionCard.tsx +82 -0
- package/src/molecules/Stepper.tsx +76 -0
- package/src/molecules/Steps.tsx +198 -0
- package/src/molecules/ThemeToggle.tsx +67 -0
- package/src/molecules/index.ts +14 -0
- package/src/organisms/Alert.tsx +162 -0
- package/src/organisms/AlertProvider.tsx +125 -0
- package/src/organisms/BottomSheet.tsx +95 -0
- package/src/organisms/DateField.tsx +134 -0
- package/src/organisms/DatePicker.tsx +255 -0
- package/src/organisms/List.tsx +154 -0
- package/src/organisms/Modal.tsx +251 -0
- package/src/organisms/Screen.tsx +84 -0
- package/src/organisms/SearchList.tsx +186 -0
- package/src/organisms/Select.tsx +156 -0
- package/src/organisms/Toast.tsx +135 -0
- package/src/organisms/ToastProvider.tsx +166 -0
- package/src/organisms/Wizard.tsx +137 -0
- package/src/organisms/index.ts +26 -0
- package/src/theme/UIProvider.tsx +129 -0
- package/src/theme/createStyles.ts +40 -0
- package/src/theme/createTheme.ts +49 -0
- package/src/theme/index.ts +22 -0
- package/src/theme/insets.ts +9 -0
- package/src/theme/labels.ts +23 -0
- package/src/theme/palettes.ts +53 -0
- package/src/theme/tokens.ts +113 -0
- package/src/theme/types.ts +27 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
{
|
|
2
|
+
"components": [
|
|
3
|
+
{
|
|
4
|
+
"name": "Title",
|
|
5
|
+
"siblingNames": [
|
|
6
|
+
"Subtitle",
|
|
7
|
+
"Body",
|
|
8
|
+
"Caption"
|
|
9
|
+
],
|
|
10
|
+
"category": "atoms",
|
|
11
|
+
"file": "atoms/Text.tsx",
|
|
12
|
+
"slug": "title"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "Button",
|
|
16
|
+
"siblingNames": [],
|
|
17
|
+
"category": "atoms",
|
|
18
|
+
"file": "atoms/Button.tsx",
|
|
19
|
+
"slug": "button"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "IconButton",
|
|
23
|
+
"siblingNames": [],
|
|
24
|
+
"category": "atoms",
|
|
25
|
+
"file": "atoms/IconButton.tsx",
|
|
26
|
+
"slug": "icon-button"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "Input",
|
|
30
|
+
"siblingNames": [],
|
|
31
|
+
"category": "atoms",
|
|
32
|
+
"file": "atoms/Input.tsx",
|
|
33
|
+
"slug": "input"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "Checkbox",
|
|
37
|
+
"siblingNames": [],
|
|
38
|
+
"category": "atoms",
|
|
39
|
+
"file": "atoms/Checkbox.tsx",
|
|
40
|
+
"slug": "checkbox"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Badge",
|
|
44
|
+
"siblingNames": [],
|
|
45
|
+
"category": "atoms",
|
|
46
|
+
"file": "atoms/Badge.tsx",
|
|
47
|
+
"slug": "badge"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Card",
|
|
51
|
+
"siblingNames": [],
|
|
52
|
+
"category": "atoms",
|
|
53
|
+
"file": "atoms/Card.tsx",
|
|
54
|
+
"slug": "card"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "Divider",
|
|
58
|
+
"siblingNames": [],
|
|
59
|
+
"category": "atoms",
|
|
60
|
+
"file": "atoms/Divider.tsx",
|
|
61
|
+
"slug": "divider"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "Avatar",
|
|
65
|
+
"siblingNames": [],
|
|
66
|
+
"category": "atoms",
|
|
67
|
+
"file": "atoms/Avatar.tsx",
|
|
68
|
+
"slug": "avatar"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "Image",
|
|
72
|
+
"siblingNames": [
|
|
73
|
+
"prefetchImage"
|
|
74
|
+
],
|
|
75
|
+
"category": "atoms",
|
|
76
|
+
"file": "atoms/Image.tsx",
|
|
77
|
+
"slug": "image"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Spinner",
|
|
81
|
+
"siblingNames": [],
|
|
82
|
+
"category": "atoms",
|
|
83
|
+
"file": "atoms/Spinner.tsx",
|
|
84
|
+
"slug": "spinner"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "EmptyState",
|
|
88
|
+
"siblingNames": [],
|
|
89
|
+
"category": "atoms",
|
|
90
|
+
"file": "atoms/EmptyState.tsx",
|
|
91
|
+
"slug": "empty-state"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "KeyValueRow",
|
|
95
|
+
"siblingNames": [],
|
|
96
|
+
"category": "atoms",
|
|
97
|
+
"file": "atoms/KeyValueRow.tsx",
|
|
98
|
+
"slug": "key-value-row"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "Fab",
|
|
102
|
+
"siblingNames": [],
|
|
103
|
+
"category": "atoms",
|
|
104
|
+
"file": "atoms/Fab.tsx",
|
|
105
|
+
"slug": "fab"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "PressableScale",
|
|
109
|
+
"siblingNames": [],
|
|
110
|
+
"category": "atoms",
|
|
111
|
+
"file": "atoms/PressableScale.tsx",
|
|
112
|
+
"slug": "pressable-scale"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "Stepper",
|
|
116
|
+
"siblingNames": [],
|
|
117
|
+
"category": "molecules",
|
|
118
|
+
"file": "molecules/Stepper.tsx",
|
|
119
|
+
"slug": "stepper"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"name": "OptionCard",
|
|
123
|
+
"siblingNames": [],
|
|
124
|
+
"category": "molecules",
|
|
125
|
+
"file": "molecules/OptionCard.tsx",
|
|
126
|
+
"slug": "option-card"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"name": "InfoRow",
|
|
130
|
+
"siblingNames": [],
|
|
131
|
+
"category": "molecules",
|
|
132
|
+
"file": "molecules/InfoRow.tsx",
|
|
133
|
+
"slug": "info-row"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "FormActions",
|
|
137
|
+
"siblingNames": [],
|
|
138
|
+
"category": "molecules",
|
|
139
|
+
"file": "molecules/FormActions.tsx",
|
|
140
|
+
"slug": "form-actions"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "AvatarHeader",
|
|
144
|
+
"siblingNames": [],
|
|
145
|
+
"category": "molecules",
|
|
146
|
+
"file": "molecules/AvatarHeader.tsx",
|
|
147
|
+
"slug": "avatar-header"
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "ThemeToggle",
|
|
151
|
+
"siblingNames": [],
|
|
152
|
+
"category": "molecules",
|
|
153
|
+
"file": "molecules/ThemeToggle.tsx",
|
|
154
|
+
"slug": "theme-toggle"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"name": "Steps",
|
|
158
|
+
"siblingNames": [],
|
|
159
|
+
"category": "molecules",
|
|
160
|
+
"file": "molecules/Steps.tsx",
|
|
161
|
+
"slug": "steps"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "Modal",
|
|
165
|
+
"siblingNames": [],
|
|
166
|
+
"category": "organisms",
|
|
167
|
+
"file": "organisms/Modal.tsx",
|
|
168
|
+
"slug": "modal"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"name": "BottomSheet",
|
|
172
|
+
"siblingNames": [],
|
|
173
|
+
"category": "organisms",
|
|
174
|
+
"file": "organisms/BottomSheet.tsx",
|
|
175
|
+
"slug": "bottom-sheet"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "Select",
|
|
179
|
+
"siblingNames": [],
|
|
180
|
+
"category": "organisms",
|
|
181
|
+
"file": "organisms/Select.tsx",
|
|
182
|
+
"slug": "select"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "Alert",
|
|
186
|
+
"siblingNames": [],
|
|
187
|
+
"category": "organisms",
|
|
188
|
+
"file": "organisms/Alert.tsx",
|
|
189
|
+
"slug": "alert"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "AlertProvider",
|
|
193
|
+
"siblingNames": [
|
|
194
|
+
"useAlert"
|
|
195
|
+
],
|
|
196
|
+
"category": "organisms",
|
|
197
|
+
"file": "organisms/AlertProvider.tsx",
|
|
198
|
+
"slug": "alert-provider"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "Screen",
|
|
202
|
+
"siblingNames": [],
|
|
203
|
+
"category": "organisms",
|
|
204
|
+
"file": "organisms/Screen.tsx",
|
|
205
|
+
"slug": "screen"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "List",
|
|
209
|
+
"siblingNames": [],
|
|
210
|
+
"category": "organisms",
|
|
211
|
+
"file": "organisms/List.tsx",
|
|
212
|
+
"slug": "list"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "SearchList",
|
|
216
|
+
"siblingNames": [],
|
|
217
|
+
"category": "organisms",
|
|
218
|
+
"file": "organisms/SearchList.tsx",
|
|
219
|
+
"slug": "search-list"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "Toast",
|
|
223
|
+
"siblingNames": [],
|
|
224
|
+
"category": "organisms",
|
|
225
|
+
"file": "organisms/Toast.tsx",
|
|
226
|
+
"slug": "toast"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "ToastProvider",
|
|
230
|
+
"siblingNames": [
|
|
231
|
+
"useToast"
|
|
232
|
+
],
|
|
233
|
+
"category": "organisms",
|
|
234
|
+
"file": "organisms/ToastProvider.tsx",
|
|
235
|
+
"slug": "toast-provider"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"name": "DatePicker",
|
|
239
|
+
"siblingNames": [
|
|
240
|
+
"buildMonthGrid",
|
|
241
|
+
"DEFAULT_MONTH_NAMES",
|
|
242
|
+
"DEFAULT_WEEKDAY_NAMES"
|
|
243
|
+
],
|
|
244
|
+
"category": "organisms",
|
|
245
|
+
"file": "organisms/DatePicker.tsx",
|
|
246
|
+
"slug": "date-picker"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
"name": "DateField",
|
|
250
|
+
"siblingNames": [],
|
|
251
|
+
"category": "organisms",
|
|
252
|
+
"file": "organisms/DateField.tsx",
|
|
253
|
+
"slug": "date-field"
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "Wizard",
|
|
257
|
+
"siblingNames": [],
|
|
258
|
+
"category": "organisms",
|
|
259
|
+
"file": "organisms/Wizard.tsx",
|
|
260
|
+
"slug": "wizard"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Modal",
|
|
3
|
+
"slug": "modal",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Modal"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "organisms/Modal.tsx",
|
|
12
|
+
"content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n Dimensions,\n KeyboardAvoidingView,\n Modal as RNModal,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ModalProps as RNModalProps,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useInsets } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\n\nexport type ModalVariant = 'full' | 'overlay' | 'fullScreen' | 'bottomSheet';\n\nexport interface ModalProps\n extends Pick<RNModalProps, 'statusBarTranslucent' | 'onRequestClose' | 'hardwareAccelerated'> {\n visible: boolean;\n onClose?: () => void;\n title?: string;\n closeAccessibilityLabel?: string;\n children: React.ReactNode;\n footer?: React.ReactNode;\n containerStyle?: ViewStyle;\n contentStyle?: ViewStyle;\n variant?: ModalVariant;\n scrollable?: boolean;\n}\n\nconst useStyles = createStyles((theme) => ({\n flex1: { flex: 1 },\n fullWrapper: { flex: 1, backgroundColor: theme.colors.background },\n overlayContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: theme.tokens.spacing.xxl - 4 },\n bottomSheetContainer: { justifyContent: 'flex-end', padding: 0 },\n backdrop: { backgroundColor: theme.colors.overlay },\n overlayCard: {\n width: '100%',\n maxHeight: '85%',\n backgroundColor: theme.colors.surface,\n borderRadius: theme.tokens.radius.xxl,\n overflow: 'hidden',\n ...theme.tokens.shadow.lg,\n },\n bottomSheetCard: {\n width: '100%',\n maxHeight: '90%',\n backgroundColor: theme.colors.surface,\n borderTopLeftRadius: theme.tokens.radius.xxl,\n borderTopRightRadius: theme.tokens.radius.xxl,\n overflow: 'hidden',\n ...theme.tokens.shadow.lg,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n padding: theme.tokens.spacing.xl,\n borderBottomWidth: 1,\n borderBottomColor: theme.colors.border,\n backgroundColor: theme.colors.surface,\n },\n title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, flex: 1 },\n closeButton: { marginLeft: theme.tokens.spacing.md },\n contentWrapper: { flexShrink: 1, backgroundColor: theme.colors.surface },\n scrollWrapper: { flexShrink: 1 },\n scrollContent: { padding: theme.tokens.spacing.xl, flexGrow: 1 },\n scrollContentFull: { padding: 0, flexGrow: 1 },\n footer: {\n padding: theme.tokens.spacing.xl,\n borderTopWidth: 1,\n borderTopColor: theme.colors.border,\n backgroundColor: theme.colors.surface,\n },\n}));\n\n/**\n * Modal\n * Un solo componente para todos los modales de la app: maneja scroll,\n * teclado, insets y layout según `variant`. overlay/bottomSheet/full animan\n * su entrada/salida con Animated (solo transform, nunca opacity sobre la\n * tarjeta: animar opacity ahí dispara un bug de Android donde los hijos con\n * elevation parpadean/redibujan). El fondo semitransparente sí anima opacity.\n */\nexport const Modal = memo(\n ({\n visible,\n onClose,\n title,\n closeAccessibilityLabel = 'Close',\n children,\n footer,\n containerStyle,\n contentStyle,\n variant = 'full',\n scrollable = true,\n statusBarTranslucent = true,\n onRequestClose,\n hardwareAccelerated,\n }: ModalProps) => {\n const isFull = variant === 'full';\n const isOverlay = variant === 'overlay';\n const isFullScreen = variant === 'fullScreen';\n const isBottomSheet = variant === 'bottomSheet';\n const isAnimatedVariant = isOverlay || isBottomSheet || isFull;\n const insets = useInsets();\n const colors = useColors();\n const styles = useStyles();\n\n const [mounted, setMounted] = useState(!isAnimatedVariant && visible);\n const backdropOpacity = useRef(new Animated.Value(0)).current;\n const cardProgress = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n if (!isAnimatedVariant) {\n setMounted(visible);\n return;\n }\n if (visible) {\n setMounted(true);\n Animated.timing(backdropOpacity, { toValue: 1, duration: 200, useNativeDriver: true }).start();\n Animated.timing(cardProgress, { toValue: 1, duration: 250, useNativeDriver: true }).start();\n } else {\n Animated.timing(backdropOpacity, { toValue: 0, duration: 180, useNativeDriver: true }).start();\n Animated.timing(cardProgress, { toValue: 0, duration: 200, useNativeDriver: true }).start(({ finished }) => {\n if (finished) setMounted(false);\n });\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [visible, isAnimatedVariant]);\n\n const cardAnimatedStyle =\n isBottomSheet || isFull\n ? { transform: [{ translateY: cardProgress.interpolate({ inputRange: [0, 1], outputRange: [SCREEN_HEIGHT, 0] }) }] }\n : {\n transform: [\n { translateY: cardProgress.interpolate({ inputRange: [0, 1], outputRange: [-40, 0] }) },\n { scale: cardProgress.interpolate({ inputRange: [0, 1], outputRange: [0.9, 1] }) },\n ],\n };\n\n const behavior = Platform.OS === 'ios' ? (isFull ? undefined : 'padding') : 'height';\n const keyboardOffset = Platform.OS === 'ios' ? 0 : 20;\n\n const renderHeader = () => {\n if (!title && !onClose) return null;\n return (\n <View style={styles.header}>\n {!!title && (\n <Title style={styles.title} numberOfLines={1}>\n {title}\n </Title>\n )}\n {!title && <View style={styles.flex1} />}\n {!!onClose && (\n <IconButton iconName=\"close\" size={24} color={colors.text} onPress={onClose} accessibilityLabel={closeAccessibilityLabel} style={styles.closeButton} />\n )}\n </View>\n );\n };\n\n const renderMainContent = () => (\n <View style={[styles.contentWrapper, isFull && styles.flex1]}>\n {scrollable ? (\n <ScrollView\n style={[styles.scrollWrapper, isFull && styles.flex1]}\n contentContainerStyle={[!isFull ? styles.scrollContent : styles.scrollContentFull, contentStyle]}\n keyboardShouldPersistTaps=\"handled\"\n showsVerticalScrollIndicator={false}\n bounces={Platform.OS === 'ios'}\n automaticallyAdjustKeyboardInsets\n >\n {children}\n </ScrollView>\n ) : (\n <View style={[!isFull ? styles.scrollContent : styles.scrollContentFull, isFull && styles.flex1, contentStyle]}>\n {children}\n </View>\n )}\n </View>\n );\n\n const renderFooter = () => (footer ? <View style={styles.footer}>{footer}</View> : null);\n\n if (!mounted) return null;\n\n return (\n <RNModal\n visible\n onRequestClose={onRequestClose ?? onClose}\n animationType={isAnimatedVariant ? 'none' : 'slide'}\n // `full` anima su propia entrada (translateY) con animationType=\"none\"\n // y necesita `transparent` para que no se vea un fondo negro detrás\n // mientras desliza. RN sólo admite `transparent` junto a\n // 'overFullScreen' — con 'pageSheet' avisa que la combinación no está\n // soportada. Por eso `full` usa overFullScreen: la presentación nativa\n // de pageSheet sería redundante con nuestra animación de todos modos.\n presentationStyle={\n Platform.OS === 'ios' ? (isFullScreen ? 'fullScreen' : 'overFullScreen') : undefined\n }\n transparent={isOverlay || isBottomSheet || isFull}\n statusBarTranslucent={statusBarTranslucent}\n hardwareAccelerated={hardwareAccelerated}\n >\n <KeyboardAvoidingView behavior={behavior} keyboardVerticalOffset={keyboardOffset} style={styles.flex1}>\n {isOverlay || isBottomSheet ? (\n <View style={[styles.overlayContainer, isBottomSheet && styles.bottomSheetContainer]}>\n {/* Sin accessibilityLabel propio: el header ya expone un botón \"Close\"\n accesible; duplicar el label aquí generaría dos elementos con el\n mismo nombre para lectores de pantalla. Tocar afuera es un atajo\n puramente táctil. */}\n <Animated.View style={[{ ...StyleSheetAbsoluteFill }, styles.backdrop, { opacity: backdropOpacity }]}>\n <Pressable style={StyleSheetAbsoluteFill} onPress={onClose} testID=\"modal-backdrop\" />\n </Animated.View>\n\n <Animated.View\n style={[\n isOverlay ? styles.overlayCard : styles.bottomSheetCard,\n isBottomSheet && { paddingBottom: Math.max(insets.bottom, Platform.OS === 'ios' ? 20 : 0) },\n containerStyle,\n cardAnimatedStyle,\n ]}\n >\n {renderHeader()}\n {renderMainContent()}\n {renderFooter()}\n </Animated.View>\n </View>\n ) : (\n <Animated.View style={[styles.fullWrapper, containerStyle, isFull && cardAnimatedStyle]}>\n <View style={[styles.flex1, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>\n {renderHeader()}\n {renderMainContent()}\n {renderFooter()}\n </View>\n </Animated.View>\n )}\n </KeyboardAvoidingView>\n </RNModal>\n );\n }\n);\nModal.displayName = 'Modal';\n\nconst StyleSheetAbsoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core",
|
|
17
|
+
"title",
|
|
18
|
+
"icon-button",
|
|
19
|
+
"pressable-scale"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "OptionCard",
|
|
3
|
+
"slug": "option-card",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "molecules",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"OptionCard"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "molecules/OptionCard.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, TouchableOpacity, 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 OptionCardProps {\n label: string;\n iconName: IconName;\n isSelected: boolean;\n onPress: () => void;\n /** 'vertical': ícono arriba, texto abajo centrado. 'horizontal': ícono y texto en fila. */\n layout?: 'vertical' | 'horizontal';\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n card: {\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.colors.surface,\n borderRadius: theme.tokens.radius.md,\n borderWidth: 1,\n borderColor: theme.colors.border,\n ...theme.tokens.shadow.sm,\n },\n cardHorizontal: { flex: 1, flexDirection: 'row', padding: theme.tokens.spacing.md - 2, gap: theme.tokens.spacing.sm },\n cardVertical: { width: '31%', padding: theme.tokens.spacing.sm, gap: 5, minHeight: 100 },\n cardSelected: {\n borderColor: theme.colors.primary,\n backgroundColor: theme.colors.primary + '15',\n borderWidth: 1.5,\n elevation: 0,\n shadowOpacity: 0,\n },\n label: { color: theme.colors.textLight, fontWeight: theme.tokens.fontWeight.medium },\n labelVertical: { fontSize: theme.tokens.fontSize.xs, textAlign: 'center' },\n labelSelected: { color: theme.colors.primary },\n}));\n\n/**\n * OptionCard\n * Card con ícono+label que resalta con borde/fondo primary cuando\n * `isSelected`. Para elegir entre opciones excluyentes (forma de pago, tipo\n * de documento, etc).\n */\nexport const OptionCard = memo(\n ({ label, iconName, isSelected, onPress, layout = 'horizontal', style }: OptionCardProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n\n return (\n <TouchableOpacity\n // activeOpacity=1: esta tarjeta tiene elevation (sombra Android); animar su\n // propia opacity en el press dispara un bug de Android que redibuja la\n // sombra como un borde oscuro pegado al borde del componente.\n activeOpacity={1}\n style={[\n styles.card,\n layout === 'vertical' ? styles.cardVertical : styles.cardHorizontal,\n isSelected && styles.cardSelected,\n style,\n ]}\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: isSelected }}\n accessibilityLabel={label}\n >\n <Icon name={iconName} size={24} color={isSelected ? colors.primary : colors.textLight} />\n <Text\n allowFontScaling={allowFontScaling}\n style={[styles.label, layout === 'vertical' && styles.labelVertical, isSelected && styles.labelSelected]}\n >\n {label}\n </Text>\n </TouchableOpacity>\n );\n }\n);\nOptionCard.displayName = 'OptionCard';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "PressableScale",
|
|
3
|
+
"slug": "pressable-scale",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "atoms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"PressableScale"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "atoms/PressableScale.tsx",
|
|
12
|
+
"content": "import React, { memo, useRef } from 'react';\nimport { Animated, Pressable, type PressableProps, type GestureResponderEvent } from 'react-native';\n\nexport interface PressableScaleProps extends PressableProps {\n /** Escala objetivo al presionar. @default 0.96 */\n scaleTo?: number;\n children: React.ReactNode;\n}\n\n/**\n * PressableScale\n * Feedback táctil de \"achicarse\" al presionar, con Animated.spring de RN\n * (useNativeDriver: true — corre en el hilo nativo, sin coste de JS por frame).\n * No depende de reanimated: la librería entera es cero-dependencias.\n */\nexport const PressableScale = memo(\n ({ scaleTo = 0.96, style, children, onPressIn, onPressOut, disabled, ...props }: PressableScaleProps) => {\n const scale = useRef(new Animated.Value(1)).current;\n\n const animateTo = (value: number) => {\n Animated.spring(scale, {\n toValue: value,\n useNativeDriver: true,\n speed: 50,\n bounciness: 6,\n }).start();\n };\n\n const handlePressIn = (e: GestureResponderEvent) => {\n if (!disabled) animateTo(scaleTo);\n onPressIn?.(e);\n };\n\n const handlePressOut = (e: GestureResponderEvent) => {\n if (!disabled) animateTo(1);\n onPressOut?.(e);\n };\n\n return (\n <Pressable\n {...props}\n disabled={disabled}\n onPressIn={handlePressIn}\n onPressOut={handlePressOut}\n style={style}\n >\n <Animated.View style={{ transform: [{ scale }] }}>{children}</Animated.View>\n </Pressable>\n );\n }\n);\nPressableScale.displayName = 'PressableScale';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Screen",
|
|
3
|
+
"slug": "screen",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Screen"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "organisms/Screen.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { KeyboardAvoidingView, Platform, ScrollView, View, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useInsets } from '../theme/UIProvider';\n\nexport type ScreenEdge = 'top' | 'bottom';\n\nexport interface ScreenProps {\n children: React.ReactNode;\n style?: ViewStyle;\n scrollable?: boolean;\n contentContainerStyle?: ViewStyle;\n /** Qué insets de safe area aplicar. @default ['top', 'bottom'] */\n edges?: ScreenEdge[];\n /**\n * Compensa el teclado con un KeyboardAvoidingView. Ponelo en false cuando\n * el contenido ya trae su propio scroller que maneja el teclado (Wizard,\n * List, SearchList): las dos compensaciones se suman y el campo enfocado\n * termina empujado fuera de la pantalla.\n * @default true\n */\n keyboardAvoiding?: boolean;\n}\n\nconst useStyles = createStyles((theme) => ({\n wrapper: { flex: 1, backgroundColor: theme.colors.background },\n container: { flex: 1 },\n contentContainer: { flexGrow: 1 },\n}));\n\n/**\n * Screen\n * Contenedor raíz de pantalla: fondo del theme, safe area (via insets del\n * UIProvider), teclado y scroll opcional resueltos de forma consistente.\n */\nexport const Screen = memo(({\n children,\n style,\n scrollable = true,\n contentContainerStyle,\n edges = ['top', 'bottom'],\n keyboardAvoiding = true,\n}: ScreenProps) => {\n const insets = useInsets();\n const styles = useStyles();\n const applyTop = edges.includes('top');\n const applyBottom = edges.includes('bottom');\n\n const KeyboardWrapper: React.ComponentType<any> = keyboardAvoiding ? KeyboardAvoidingView : View;\n const keyboardProps = keyboardAvoiding\n ? {\n behavior: Platform.OS === 'ios' ? ('padding' as const) : ('height' as const),\n keyboardVerticalOffset: Platform.OS === 'ios' ? 0 : 20,\n }\n : {};\n\n return (\n <View style={[styles.wrapper, style]}>\n <KeyboardWrapper style={styles.container} {...keyboardProps}>\n {scrollable ? (\n <ScrollView\n style={styles.container}\n contentInsetAdjustmentBehavior=\"automatic\"\n automaticallyAdjustKeyboardInsets\n contentContainerStyle={[\n styles.contentContainer,\n { paddingTop: applyTop ? insets.top : 0 },\n Platform.OS === 'android' && applyBottom && { paddingBottom: insets.bottom },\n contentContainerStyle,\n ]}\n keyboardShouldPersistTaps=\"handled\"\n >\n {children}\n </ScrollView>\n ) : (\n <View style={[styles.container, { paddingTop: applyTop ? insets.top : 0, paddingBottom: applyBottom ? insets.bottom : 0 }]}>\n {children}\n </View>\n )}\n </KeyboardWrapper>\n </View>\n );\n});\nScreen.displayName = 'Screen';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "SearchList",
|
|
3
|
+
"slug": "search-list",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"SearchList"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "organisms/SearchList.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { ActivityIndicator, FlatList, Text, View, type FlatListProps, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useLabels } from '../theme/UIProvider';\nimport { Input } from '../atoms/Input';\nimport { EmptyState } from '../atoms/EmptyState';\nimport { Spinner } from '../atoms/Spinner';\nimport { IconButton } from '../atoms/IconButton';\nimport type { IconName } from '../icons/types';\n\nexport interface SearchListProps<T> {\n /** Chrome opcional arriba del buscador (ej. back button + título). */\n header?: React.ReactNode;\n\n searchValue: string;\n onSearchChange: (text: string) => void;\n searchPlaceholder?: string;\n searchIsLoading?: boolean;\n onScanPress?: () => void;\n scanAccessibilityLabel?: string;\n extraActions?: React.ReactNode;\n\n data: T[];\n keyExtractor: (item: T) => string;\n renderItem: FlatListProps<T>['renderItem'];\n\n isLoading?: boolean;\n /** 'replace' cambia toda la lista por un spinner; 'overlay' lo muestra encima manteniendo el layout. */\n loadingMode?: 'replace' | 'overlay';\n loadingText?: string;\n\n onLoadMore?: () => void;\n isLoadingMore?: boolean;\n loadingMoreText?: string;\n hasMore?: boolean;\n noMoreText?: string;\n\n emptyTitle?: string;\n emptyDescription?: string;\n emptyIconName?: IconName;\n\n contentContainerStyle?: StyleProp<ViewStyle>;\n ListComponent?: React.ComponentType<any>;\n listProps?: Record<string, unknown>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1 },\n searchRow: { flexDirection: 'row', alignItems: 'center', marginBottom: theme.tokens.spacing.lg - 1, gap: theme.tokens.spacing.sm + 2 },\n searchInput: { flex: 1, marginBottom: 0 },\n scanButton: { backgroundColor: theme.colors.primary, width: 50, height: 50, borderRadius: theme.tokens.radius.lg, ...theme.tokens.shadow.sm },\n listArea: { flex: 1 },\n listContent: { paddingBottom: theme.tokens.spacing.xl },\n loadingOverlay: {\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: theme.colors.surface + 'B3',\n zIndex: 10,\n justifyContent: 'center',\n alignItems: 'center',\n },\n footer: { paddingVertical: theme.tokens.spacing.xl, alignItems: 'center', gap: theme.tokens.spacing.md },\n footerText: { color: theme.colors.textLight, fontSize: theme.tokens.fontSize.md },\n}));\n\n/**\n * SearchList\n * Buscador + lista + estados de carga/paginación/vacío. El contenido de\n * cada ítem (renderItem) queda a cargo de quien la usa, ya que difiere por\n * completo entre casos de uso.\n */\nexport function SearchList<T>({\n header,\n searchValue,\n onSearchChange,\n searchPlaceholder,\n searchIsLoading = false,\n onScanPress,\n scanAccessibilityLabel = 'Scan',\n extraActions,\n data,\n keyExtractor,\n renderItem,\n isLoading = false,\n loadingMode = 'replace',\n loadingText,\n onLoadMore,\n isLoadingMore = false,\n loadingMoreText,\n hasMore = false,\n noMoreText,\n emptyTitle,\n emptyDescription,\n emptyIconName,\n contentContainerStyle,\n ListComponent = FlatList,\n listProps,\n}: SearchListProps<T>) {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const resolvedSearchPlaceholder = searchPlaceholder ?? labels.search;\n const resolvedLoadingText = loadingText ?? labels.loading;\n const resolvedLoadingMoreText = loadingMoreText ?? labels.loadingMore;\n\n const renderFooter = () => {\n if (isLoadingMore) {\n return (\n <View style={styles.footer}>\n <Spinner text={resolvedLoadingMoreText} fullscreen={false} />\n </View>\n );\n }\n if (noMoreText && !hasMore && data.length > 0) {\n return (\n <View style={styles.footer}>\n <Text style={styles.footerText}>{noMoreText}</Text>\n </View>\n );\n }\n return null;\n };\n\n const showReplaceLoading = isLoading && loadingMode === 'replace' && data.length === 0;\n const showOverlayLoading = isLoading && loadingMode === 'overlay' && data.length === 0;\n const showEmpty = !isLoading && data.length === 0;\n\n return (\n <View style={styles.container}>\n {header}\n\n <View style={styles.searchRow}>\n <Input\n placeholder={resolvedSearchPlaceholder}\n leftIconName=\"search\"\n value={searchValue}\n onChangeText={onSearchChange}\n rightIconName={searchValue.length > 0 ? 'close' : undefined}\n onRightIconPress={() => onSearchChange('')}\n isLoading={searchIsLoading}\n containerStyle={styles.searchInput}\n />\n {onScanPress && (\n <IconButton\n iconName=\"search\"\n onPress={onScanPress}\n color={colors.white}\n style={styles.scanButton}\n accessibilityLabel={scanAccessibilityLabel}\n />\n )}\n {extraActions}\n </View>\n\n <View style={styles.listArea}>\n {showOverlayLoading && (\n <View style={styles.loadingOverlay}>\n <ActivityIndicator size=\"large\" color={colors.primary} />\n <Text style={styles.footerText}>{resolvedLoadingText}</Text>\n </View>\n )}\n\n {showReplaceLoading ? (\n <Spinner text={resolvedLoadingText} />\n ) : showEmpty ? (\n <EmptyState title={emptyTitle} description={emptyDescription} iconName={emptyIconName} />\n ) : (\n <ListComponent\n data={data}\n keyExtractor={keyExtractor}\n keyboardDismissMode=\"on-drag\"\n contentContainerStyle={contentContainerStyle ?? styles.listContent}\n onEndReached={onLoadMore}\n onEndReachedThreshold={0.2}\n ListFooterComponent={renderFooter}\n renderItem={renderItem}\n {...listProps}\n />\n )}\n </View>\n </View>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core",
|
|
17
|
+
"input",
|
|
18
|
+
"empty-state",
|
|
19
|
+
"spinner",
|
|
20
|
+
"icon-button",
|
|
21
|
+
"pressable-scale"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Select",
|
|
3
|
+
"slug": "select",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Select"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "organisms/Select.tsx",
|
|
12
|
+
"content": "import React, { memo, useState } from 'react';\nimport { ActivityIndicator, Pressable, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\n\nexport interface SelectOption<T> {\n label: string;\n value: T;\n}\n\nexport interface SelectProps<T> {\n label?: string;\n required?: boolean;\n options: SelectOption<T>[];\n selectedValue: T | undefined;\n onSelect: (value: T) => void;\n placeholder?: string;\n error?: string;\n isLoading?: boolean;\n containerStyle?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { gap: theme.tokens.spacing.sm },\n containerOpen: { zIndex: 20, elevation: 20 },\n label: { fontWeight: theme.tokens.fontWeight.bold, fontSize: theme.tokens.fontSize.md, color: theme.colors.text },\n required: { color: theme.colors.error },\n field: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n borderWidth: 2,\n borderColor: theme.colors.border,\n borderRadius: theme.tokens.radius.lg,\n backgroundColor: theme.colors.inputBackground,\n minHeight: 50,\n paddingHorizontal: theme.tokens.spacing.lg,\n },\n fieldText: { fontSize: theme.tokens.fontSize.lg, color: theme.colors.text },\n fieldPlaceholder: { color: theme.colors.textLight },\n optionsList: {\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: theme.tokens.spacing.sm,\n borderWidth: 2,\n borderColor: theme.colors.border,\n borderRadius: theme.tokens.radius.lg,\n backgroundColor: theme.colors.surface,\n overflow: 'hidden',\n zIndex: 20,\n elevation: 20,\n ...theme.tokens.shadow.md,\n },\n optionRow: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: theme.tokens.spacing.lg,\n paddingVertical: theme.tokens.spacing.md + 2,\n borderBottomWidth: 1,\n borderBottomColor: theme.colors.border,\n },\n optionRowLast: { borderBottomWidth: 0 },\n optionRowSelected: { backgroundColor: theme.colors.primary + '15' },\n optionText: { fontSize: theme.tokens.fontSize.lg - 1, color: theme.colors.text },\n optionTextSelected: { fontWeight: theme.tokens.fontWeight.semibold, color: theme.colors.primary },\n errorText: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.error },\n}));\n\n/**\n * Select\n * Dropdown que expande la lista de opciones flotando sobre el resto del\n * contenido (position: absolute), en vez de empujarlo hacia abajo o abrir\n * un picker nativo del sistema.\n */\nexport function Select<T extends string | number>({\n label,\n required = false,\n options,\n selectedValue,\n onSelect,\n placeholder,\n error,\n isLoading = false,\n containerStyle,\n}: SelectProps<T>) {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const allowFontScaling = useAllowFontScaling();\n const [open, setOpen] = useState(false);\n const selectedOption = options.find((o) => o.value === selectedValue);\n\n return (\n <View style={[styles.container, open && styles.containerOpen, containerStyle]}>\n {label && (\n <Text allowFontScaling={allowFontScaling} style={styles.label}>\n {label}\n {required && <Text style={styles.required}> *</Text>}\n </Text>\n )}\n\n <Pressable\n style={styles.field}\n onPress={() => !isLoading && setOpen((o) => !o)}\n disabled={isLoading}\n accessibilityRole=\"button\"\n accessibilityLabel={label ?? placeholder ?? labels.selectPlaceholder}\n accessibilityState={{ expanded: open, disabled: isLoading }}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !selectedOption && styles.fieldPlaceholder]}>\n {selectedOption?.label || placeholder || labels.selectPlaceholder}\n </Text>\n {isLoading ? (\n <ActivityIndicator size=\"small\" color={colors.primary} />\n ) : (\n <Icon name={open ? 'chevron-up' : 'chevron-down'} size={20} color={colors.textLight} />\n )}\n </Pressable>\n\n {open && !isLoading && (\n <View style={styles.optionsList}>\n {options.map((option, index) => {\n const isSelected = option.value === selectedValue;\n return (\n <Pressable\n key={String(option.value)}\n style={[styles.optionRow, index === options.length - 1 && styles.optionRowLast, isSelected && styles.optionRowSelected]}\n onPress={() => {\n onSelect(option.value);\n setOpen(false);\n }}\n accessibilityRole=\"menuitem\"\n accessibilityState={{ selected: isSelected }}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.optionText, isSelected && styles.optionTextSelected]}>\n {option.label}\n </Text>\n {isSelected && <Icon name=\"check\" size={18} color={colors.primary} />}\n </Pressable>\n );\n })}\n </View>\n )}\n\n {!!error && (\n <Text allowFontScaling={allowFontScaling} style={styles.errorText}>\n {error}\n </Text>\n )}\n </View>\n );\n}\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Spinner",
|
|
3
|
+
"slug": "spinner",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "atoms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Spinner"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "atoms/Spinner.tsx",
|
|
12
|
+
"content": "import React, { memo, useEffect, useRef } from 'react';\nimport { ActivityIndicator, Animated, Easing, Text, View } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\n\nexport type SpinnerVariant = 'native' | 'dots' | 'ring';\n\nexport interface SpinnerProps {\n size?: 'small' | 'large';\n color?: string;\n text?: string;\n /** @default true. En false, no ocupa flex:1 (útil inline, dentro de un botón/fila). */\n fullscreen?: boolean;\n /**\n * 'native' usa el ActivityIndicator del sistema; 'dots' y 'ring' son\n * indicadores propios, iguales en iOS y Android.\n * @default 'native'\n */\n variant?: SpinnerVariant;\n /** Indicador totalmente propio. Gana sobre `variant`. */\n indicator?: React.ReactNode;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { justifyContent: 'center', alignItems: 'center', padding: theme.tokens.spacing.xl },\n text: { marginTop: theme.tokens.spacing.md, color: theme.colors.textLight, fontSize: theme.tokens.fontSize.md },\n dots: { flexDirection: 'row', gap: theme.tokens.spacing.sm },\n}));\n\n/** Tres puntos que laten en secuencia. Animated + useNativeDriver: corre en el hilo nativo. */\nconst DotsIndicator = memo(({ color, dotSize }: { color: string; dotSize: number }) => {\n const dots = [useRef(new Animated.Value(0)).current, useRef(new Animated.Value(0)).current, useRef(new Animated.Value(0)).current];\n\n useEffect(() => {\n const animations = dots.map((dot, i) =>\n Animated.loop(\n Animated.sequence([\n Animated.delay(i * 140),\n Animated.timing(dot, { toValue: 1, duration: 320, useNativeDriver: true, easing: Easing.out(Easing.quad) }),\n Animated.timing(dot, { toValue: 0, duration: 320, useNativeDriver: true, easing: Easing.in(Easing.quad) }),\n Animated.delay((2 - i) * 140),\n ])\n )\n );\n animations.forEach((a) => a.start());\n // Sin stop() los loops siguen corriendo tras desmontar y mantienen vivo\n // el árbol de Animated.\n return () => animations.forEach((a) => a.stop());\n }, []);\n\n return (\n <View style={{ flexDirection: 'row', gap: dotSize * 0.6 }}>\n {dots.map((dot, i) => (\n <Animated.View\n key={i}\n style={{\n width: dotSize,\n height: dotSize,\n borderRadius: dotSize / 2,\n backgroundColor: color,\n opacity: dot.interpolate({ inputRange: [0, 1], outputRange: [0.3, 1] }),\n transform: [{ scale: dot.interpolate({ inputRange: [0, 1], outputRange: [0.75, 1] }) }],\n }}\n />\n ))}\n </View>\n );\n});\nDotsIndicator.displayName = 'DotsIndicator';\n\n/** Anillo que gira: un borde con un solo lado coloreado. */\nconst RingIndicator = memo(({ color, diameter }: { color: string; diameter: number }) => {\n const spin = useRef(new Animated.Value(0)).current;\n\n useEffect(() => {\n const animation = Animated.loop(\n Animated.timing(spin, { toValue: 1, duration: 900, useNativeDriver: true, easing: Easing.linear })\n );\n animation.start();\n return () => animation.stop();\n }, []);\n\n return (\n <Animated.View\n style={{\n width: diameter,\n height: diameter,\n borderRadius: diameter / 2,\n borderWidth: Math.max(2, diameter / 10),\n borderColor: color + '33',\n borderTopColor: color,\n transform: [{ rotate: spin.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }) }],\n }}\n />\n );\n});\nRingIndicator.displayName = 'RingIndicator';\n\nexport const Spinner = memo(\n ({ size = 'large', color, text, fullscreen = true, variant = 'native', indicator, testID }: SpinnerProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const resolvedColor = color || colors.primary;\n const scale = size === 'small' ? 8 : 12;\n\n const renderIndicator = () => {\n if (indicator) return indicator;\n if (variant === 'dots') return <DotsIndicator color={resolvedColor} dotSize={scale} />;\n if (variant === 'ring') return <RingIndicator color={resolvedColor} diameter={scale * 3} />;\n return <ActivityIndicator size={size} color={resolvedColor} />;\n };\n\n return (\n <View style={[styles.container, fullscreen && { flex: 1 }]} testID={testID}>\n {renderIndicator()}\n {text && (\n <Text allowFontScaling={allowFontScaling} style={styles.text}>\n {text}\n </Text>\n )}\n </View>\n );\n }\n);\nSpinner.displayName = 'Spinner';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Stepper",
|
|
3
|
+
"slug": "stepper",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "molecules",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Stepper"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "molecules/Stepper.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { TextInput, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\n\nexport interface StepperProps {\n value: string;\n onChangeText: (text: string) => void;\n onIncrement: () => void;\n onDecrement: () => void;\n size?: 'sm' | 'md';\n style?: StyleProp<ViewStyle>;\n incrementAccessibilityLabel?: string;\n decrementAccessibilityLabel?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n control: { flexDirection: 'row', alignItems: 'center', borderWidth: 1, borderColor: theme.colors.border, borderRadius: theme.tokens.radius.md },\n btn: { height: '100%', justifyContent: 'center', alignItems: 'center', backgroundColor: theme.colors.inputBackground },\n btnSm: { backgroundColor: 'transparent' },\n input: { flex: 1, textAlign: 'center', fontSize: theme.tokens.fontSize.lg, color: theme.colors.text, height: '100%' },\n inputSm: { flex: 0, width: 50, borderLeftWidth: 1, borderRightWidth: 1, borderColor: theme.colors.border },\n}));\n\n/** Stepper: botón \"-\" + input numérico editable + botón \"+\". */\nexport const Stepper = memo(\n ({\n value,\n onChangeText,\n onIncrement,\n onDecrement,\n size = 'md',\n style,\n incrementAccessibilityLabel = 'Increment',\n decrementAccessibilityLabel = 'Decrement',\n }: StepperProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const isSmall = size === 'sm';\n const btnSize = isSmall ? 36 : 48;\n const iconSize = isSmall ? 18 : 20;\n\n return (\n <View style={[styles.control, { height: isSmall ? 36 : 48 }, style]}>\n <TouchableOpacity\n onPress={onDecrement}\n style={[styles.btn, { width: btnSize }, isSmall && styles.btnSm]}\n accessibilityRole=\"button\"\n accessibilityLabel={decrementAccessibilityLabel}\n >\n <Icon name=\"minus\" size={iconSize} color={colors.primary} />\n </TouchableOpacity>\n <TextInput\n allowFontScaling={allowFontScaling}\n style={[styles.input, isSmall && styles.inputSm]}\n value={value}\n onChangeText={onChangeText}\n keyboardType=\"numeric\"\n selectTextOnFocus\n accessibilityLabel=\"Quantity\"\n />\n <TouchableOpacity\n onPress={onIncrement}\n style={[styles.btn, { width: btnSize }, isSmall && styles.btnSm]}\n accessibilityRole=\"button\"\n accessibilityLabel={incrementAccessibilityLabel}\n >\n <Icon name=\"plus\" size={iconSize} color={colors.primary} />\n </TouchableOpacity>\n </View>\n );\n }\n);\nStepper.displayName = 'Stepper';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Steps",
|
|
3
|
+
"slug": "steps",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "molecules",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Steps"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "molecules/Steps.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\n\nexport interface StepItem {\n label: string;\n description?: string;\n}\n\nexport type StepStatus = 'completed' | 'current' | 'pending';\n\nexport interface StepsProps {\n steps: StepItem[];\n /** Índice del paso actual (0-based). Los anteriores quedan 'completed'. */\n current: number;\n /** @default 'horizontal' */\n orientation?: 'horizontal' | 'vertical';\n /**\n * Qué muestra el círculo de un paso ya completado: un check, o su número.\n * @default 'check'\n */\n completedIndicator?: 'check' | 'number';\n /** Si se pasa, cada paso es tocable y reporta su índice. */\n onStepPress?: (index: number) => void;\n style?: StyleProp<ViewStyle>;\n}\n\nconst CIRCLE = 32;\n\nconst useStyles = createStyles((theme) => ({\n rowContainer: { flexDirection: 'row', alignItems: 'flex-start' },\n columnContainer: { flexDirection: 'column' },\n\n // horizontal\n hStep: { flex: 1, alignItems: 'center', gap: theme.tokens.spacing.sm },\n hLabels: { alignItems: 'center', gap: 2, paddingHorizontal: 4 },\n // El conector toma una fracción chica del ancho: compitiendo de igual a\n // igual con los pasos (flex: 1) cada etiqueta se quedaba con 1/(2n-1) del\n // total y RN partía palabras a la mitad (\"Paymen / t\"). Con 0.3 un flujo de\n // 4 pasos deja ~68px por etiqueta, suficiente para una palabra corriente.\n hConnector: { height: 2, flex: 0.3, marginTop: CIRCLE / 2 - 1 },\n\n // vertical\n vStep: { flexDirection: 'row', gap: theme.tokens.spacing.md },\n vIndicatorColumn: { alignItems: 'center' },\n vConnector: { width: 2, flex: 1, minHeight: 20, marginVertical: 4 },\n vLabels: { flex: 1, gap: 2, paddingBottom: theme.tokens.spacing.lg },\n\n circle: {\n width: CIRCLE,\n height: CIRCLE,\n borderRadius: CIRCLE / 2,\n borderWidth: 2,\n alignItems: 'center',\n justifyContent: 'center',\n },\n circleNumber: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.semibold },\n label: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.semibold, color: theme.colors.text },\n labelCentered: { textAlign: 'center' },\n description: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.textLight },\n descriptionCentered: { textAlign: 'center' },\n}));\n\nfunction statusOf(index: number, current: number): StepStatus {\n if (index < current) return 'completed';\n if (index === current) return 'current';\n return 'pending';\n}\n\n/**\n * Steps\n * Indicador de progreso multi-paso. Base visual de `Wizard`, pero utilizable\n * suelto para mostrar el avance de un flujo que se navega de otra forma.\n */\nexport const Steps = memo(\n ({ steps, current, orientation = 'horizontal', completedIndicator = 'check', onStepPress, style }: StepsProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const isVertical = orientation === 'vertical';\n\n const colorsFor = (status: StepStatus) => {\n if (status === 'completed') return { bg: colors.primary, border: colors.primary, fg: colors.white };\n if (status === 'current') return { bg: 'transparent', border: colors.primary, fg: colors.primary };\n return { bg: 'transparent', border: colors.border, fg: colors.textLight };\n };\n\n const renderCircle = (index: number, status: StepStatus) => {\n const c = colorsFor(status);\n const showCheck = status === 'completed' && completedIndicator === 'check';\n return (\n <View style={[styles.circle, { backgroundColor: c.bg, borderColor: c.border }]}>\n {showCheck ? (\n <Icon name=\"check\" size={18} color={c.fg} />\n ) : (\n <Text allowFontScaling={allowFontScaling} style={[styles.circleNumber, { color: c.fg }]}>\n {index + 1}\n </Text>\n )}\n </View>\n );\n };\n\n const renderLabels = (step: StepItem, status: StepStatus, centered: boolean) => (\n <View style={centered ? styles.hLabels : styles.vLabels}>\n <Text\n allowFontScaling={allowFontScaling}\n style={[\n styles.label,\n centered && styles.labelCentered,\n status === 'pending' && { color: colors.textLight },\n ]}\n >\n {step.label}\n </Text>\n {!!step.description && (\n <Text\n allowFontScaling={allowFontScaling}\n style={[styles.description, centered && styles.descriptionCentered]}\n >\n {step.description}\n </Text>\n )}\n </View>\n );\n\n const stepAccessibility = (index: number, step: StepItem, status: StepStatus) => ({\n accessibilityRole: (onStepPress ? 'button' : 'text') as 'button' | 'text',\n accessibilityLabel: `Step ${index + 1}: ${step.label}`,\n accessibilityState: { selected: status === 'current', disabled: false },\n });\n\n if (isVertical) {\n return (\n <View style={[styles.columnContainer, style]}>\n {steps.map((step, index) => {\n const status = statusOf(index, current);\n const isLast = index === steps.length - 1;\n const Wrapper: any = onStepPress ? TouchableOpacity : View;\n return (\n <Wrapper\n key={step.label}\n style={styles.vStep}\n onPress={onStepPress ? () => onStepPress(index) : undefined}\n activeOpacity={0.7}\n {...stepAccessibility(index, step, status)}\n >\n <View style={styles.vIndicatorColumn}>\n {renderCircle(index, status)}\n {!isLast && (\n <View\n style={[\n styles.vConnector,\n { backgroundColor: index < current ? colors.primary : colors.border },\n ]}\n />\n )}\n </View>\n {renderLabels(step, status, false)}\n </Wrapper>\n );\n })}\n </View>\n );\n }\n\n return (\n <View style={[styles.rowContainer, style]}>\n {steps.map((step, index) => {\n const status = statusOf(index, current);\n const isLast = index === steps.length - 1;\n const Wrapper: any = onStepPress ? TouchableOpacity : View;\n return (\n <React.Fragment key={step.label}>\n <Wrapper\n style={styles.hStep}\n onPress={onStepPress ? () => onStepPress(index) : undefined}\n activeOpacity={0.7}\n {...stepAccessibility(index, step, status)}\n >\n {renderCircle(index, status)}\n {renderLabels(step, status, true)}\n </Wrapper>\n {!isLast && (\n <View\n style={[styles.hConnector, { backgroundColor: index < current ? colors.primary : colors.border }]}\n />\n )}\n </React.Fragment>\n );\n })}\n </View>\n );\n }\n);\nSteps.displayName = 'Steps';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ThemeToggle",
|
|
3
|
+
"slug": "theme-toggle",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "molecules",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"ThemeToggle"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
{
|
|
11
|
+
"path": "molecules/ThemeToggle.tsx",
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useThemeMode, type ThemeMode } from '../theme/UIProvider';\n\nexport interface ThemeToggleProps {\n style?: StyleProp<ViewStyle>;\n labels?: Partial<Record<ThemeMode, string>>;\n}\n\nconst MODES: ThemeMode[] = ['system', 'light', 'dark'];\nconst DEFAULT_LABELS: Record<ThemeMode, string> = { system: 'Auto', light: 'Light', dark: 'Dark' };\n\nconst useStyles = createStyles((theme) => ({\n track: {\n flexDirection: 'row',\n backgroundColor: theme.colors.inputBackground,\n borderRadius: theme.tokens.radius.md,\n padding: 3,\n },\n segment: {\n flex: 1,\n paddingVertical: theme.tokens.spacing.sm,\n borderRadius: theme.tokens.radius.sm,\n alignItems: 'center',\n },\n segmentActive: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },\n label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.textLight },\n labelActive: { color: theme.colors.primary, fontWeight: theme.tokens.fontWeight.semibold },\n}));\n\n/**\n * ThemeToggle\n * Segmented control de 3 estados (system/light/dark) conectado a\n * UIProvider.setMode. Sin persistencia — el consumidor la resuelve vía\n * onModeChange en <UIProvider>.\n */\nexport const ThemeToggle = memo(({ style, labels: labelOverrides }: ThemeToggleProps) => {\n const { mode, setMode } = useThemeMode();\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const labels = { ...DEFAULT_LABELS, ...labelOverrides };\n\n return (\n <View style={[styles.track, style]} accessibilityRole=\"tablist\">\n {MODES.map((m) => {\n const active = mode === m;\n return (\n <TouchableOpacity\n key={m}\n style={[styles.segment, active && styles.segmentActive]}\n onPress={() => setMode(m)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: active }}\n accessibilityLabel={labels[m]}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.label, active && styles.labelActive]}>\n {labels[m]}\n </Text>\n </TouchableOpacity>\n );\n })}\n </View>\n );\n});\nThemeToggle.displayName = 'ThemeToggle';\n"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"registryDependencies": [
|
|
16
|
+
"core"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Title",
|
|
3
|
+
"slug": "title",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "atoms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"Title",
|
|
8
|
+
"Subtitle",
|
|
9
|
+
"Body",
|
|
10
|
+
"Caption"
|
|
11
|
+
],
|
|
12
|
+
"files": [
|
|
13
|
+
{
|
|
14
|
+
"path": "atoms/Text.tsx",
|
|
15
|
+
"content": "import React, { memo } from 'react';\nimport { Text as RNText, type StyleProp, type TextProps, type TextStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\n\nexport interface TypographyProps extends TextProps {\n children?: React.ReactNode;\n style?: StyleProp<TextStyle>;\n color?: string;\n align?: 'left' | 'center' | 'right';\n}\n\nconst useStyles = createStyles((theme) => ({\n title: {\n fontSize: theme.tokens.fontSize.xxl,\n fontWeight: theme.tokens.fontWeight.bold,\n letterSpacing: -0.5,\n color: theme.colors.text,\n },\n subtitle: {\n fontSize: theme.tokens.fontSize.lg,\n fontWeight: theme.tokens.fontWeight.medium,\n color: theme.colors.text,\n },\n body: {\n fontSize: theme.tokens.fontSize.md,\n lineHeight: theme.tokens.lineHeight.md,\n color: theme.colors.text,\n },\n caption: {\n fontSize: theme.tokens.fontSize.sm,\n color: theme.colors.textLight,\n },\n}));\n\nfunction makeVariant(variant: keyof ReturnType<typeof useStyles>, defaultColor: 'text' | 'textLight') {\n const Component = memo(({ children, style, color, align = 'left', ...props }: TypographyProps) => {\n const styles = useStyles();\n const colors = useColors();\n const allowFontScaling = useAllowFontScaling();\n return (\n <RNText\n allowFontScaling={allowFontScaling}\n style={[styles[variant], { color: color || colors[defaultColor], textAlign: align }, style]}\n {...props}\n >\n {children}\n </RNText>\n );\n });\n Component.displayName = variant;\n return Component;\n}\n\nexport const Title = makeVariant('title', 'text');\nexport const Subtitle = makeVariant('subtitle', 'text');\nexport const Body = makeVariant('body', 'text');\nexport const Caption = makeVariant('caption', 'textLight');\n"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"registryDependencies": [
|
|
19
|
+
"core"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ToastProvider",
|
|
3
|
+
"slug": "toast-provider",
|
|
4
|
+
"type": "component",
|
|
5
|
+
"category": "organisms",
|
|
6
|
+
"exportedNames": [
|
|
7
|
+
"ToastProvider",
|
|
8
|
+
"useToast"
|
|
9
|
+
],
|
|
10
|
+
"files": [
|
|
11
|
+
{
|
|
12
|
+
"path": "organisms/ToastProvider.tsx",
|
|
13
|
+
"content": "import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { View } from 'react-native';\nimport { AnimatedToast, Toast, type ToastVariant } from './Toast';\nimport { useInsets } from '../theme/UIProvider';\nimport { createStyles } from '../theme/createStyles';\n\nexport interface ToastOptions {\n title: string;\n message?: string;\n variant?: ToastVariant;\n /** ms antes de auto-descartar. 0 = no se descarta solo. @default 4000 */\n duration?: number;\n onPress?: () => void;\n hideCloseButton?: boolean;\n}\n\ninterface ToastEntry extends ToastOptions {\n id: string;\n}\n\ninterface ToastContextValue {\n /** Muestra un toast y devuelve su id (por si querés cerrarlo antes de tiempo). */\n show: (options: ToastOptions) => string;\n hide: (id: string) => void;\n hideAll: () => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | undefined>(undefined);\n\nconst useStyles = createStyles((theme) => ({\n host: { position: 'absolute', left: 0, right: 0, paddingHorizontal: theme.tokens.spacing.lg, gap: theme.tokens.spacing.sm },\n}));\n\nexport interface ToastProviderProps {\n children: React.ReactNode;\n /** @default 'top' */\n position?: 'top' | 'bottom';\n /** Máximo de toasts simultáneos; los más viejos se descartan. @default 3 */\n maxVisible?: number;\n /** Duración por defecto en ms. @default 4000 */\n defaultDuration?: number;\n}\n\n/**\n * ToastProvider\n * Monta la capa de toasts sobre el resto de la app. Colocar una vez cerca de\n * la raíz, dentro de UIProvider. Los timers de auto-dismiss se limpian al\n * desmontar y al cerrar cada toast, así que no quedan callbacks colgados.\n */\nexport function ToastProvider({\n children,\n position = 'top',\n maxVisible = 3,\n defaultDuration = 4000,\n}: ToastProviderProps) {\n const [toasts, setToasts] = useState<ToastEntry[]>([]);\n const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());\n const counter = useRef(0);\n const insets = useInsets();\n const styles = useStyles();\n\n const clearTimer = useCallback((id: string) => {\n const t = timers.current.get(id);\n if (t) {\n clearTimeout(t);\n timers.current.delete(id);\n }\n }, []);\n\n const hide = useCallback(\n (id: string) => {\n clearTimer(id);\n setToasts((prev) => prev.filter((t) => t.id !== id));\n },\n [clearTimer]\n );\n\n const hideAll = useCallback(() => {\n timers.current.forEach((t) => clearTimeout(t));\n timers.current.clear();\n setToasts([]);\n }, []);\n\n const show = useCallback(\n (options: ToastOptions) => {\n counter.current += 1;\n const id = `toast-${counter.current}`;\n const duration = options.duration ?? defaultDuration;\n\n setToasts((prev) => {\n const next = [...prev, { ...options, id }];\n // Se descartan los más viejos, no los recién llegados: un toast nuevo\n // siempre se ve.\n const overflow = next.length - maxVisible;\n if (overflow > 0) {\n next.slice(0, overflow).forEach((t) => clearTimer(t.id));\n return next.slice(overflow);\n }\n return next;\n });\n\n if (duration > 0) {\n timers.current.set(\n id,\n setTimeout(() => hide(id), duration)\n );\n }\n return id;\n },\n [defaultDuration, maxVisible, hide, clearTimer]\n );\n\n // Al desmontar el provider no debe quedar ningún setTimeout apuntando a un\n // setState de un árbol ya desmontado.\n useEffect(() => {\n const pending = timers.current;\n return () => {\n pending.forEach((t) => clearTimeout(t));\n pending.clear();\n };\n }, []);\n\n const value = useMemo<ToastContextValue>(() => ({ show, hide, hideAll }), [show, hide, hideAll]);\n\n const hostPosition =\n position === 'top'\n ? { top: insets.top + 8 }\n : { bottom: insets.bottom + 8, flexDirection: 'column-reverse' as const };\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n {toasts.length > 0 && (\n <View style={[styles.host, hostPosition]} pointerEvents=\"box-none\" testID=\"toast-host\">\n {toasts.map((t) => (\n <AnimatedToast key={t.id}>\n <Toast\n title={t.title}\n message={t.message}\n variant={t.variant}\n hideCloseButton={t.hideCloseButton}\n onDismiss={() => hide(t.id)}\n onPress={\n t.onPress\n ? () => {\n t.onPress?.();\n hide(t.id);\n }\n : undefined\n }\n />\n </AnimatedToast>\n ))}\n </View>\n )}\n </ToastContext.Provider>\n );\n}\n\nexport function useToast(): ToastContextValue {\n const ctx = useContext(ToastContext);\n if (!ctx) {\n throw new Error('orn-ui: useToast must be used within a <ToastProvider>.');\n }\n return ctx;\n}\n"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"registryDependencies": [
|
|
17
|
+
"core",
|
|
18
|
+
"toast"
|
|
19
|
+
]
|
|
20
|
+
}
|