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,198 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
5
|
+
import { Icon } from '../icons/Icon';
|
|
6
|
+
|
|
7
|
+
export interface StepItem {
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type StepStatus = 'completed' | 'current' | 'pending';
|
|
13
|
+
|
|
14
|
+
export interface StepsProps {
|
|
15
|
+
steps: StepItem[];
|
|
16
|
+
/** Índice del paso actual (0-based). Los anteriores quedan 'completed'. */
|
|
17
|
+
current: number;
|
|
18
|
+
/** @default 'horizontal' */
|
|
19
|
+
orientation?: 'horizontal' | 'vertical';
|
|
20
|
+
/**
|
|
21
|
+
* Qué muestra el círculo de un paso ya completado: un check, o su número.
|
|
22
|
+
* @default 'check'
|
|
23
|
+
*/
|
|
24
|
+
completedIndicator?: 'check' | 'number';
|
|
25
|
+
/** Si se pasa, cada paso es tocable y reporta su índice. */
|
|
26
|
+
onStepPress?: (index: number) => void;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const CIRCLE = 32;
|
|
31
|
+
|
|
32
|
+
const useStyles = createStyles((theme) => ({
|
|
33
|
+
rowContainer: { flexDirection: 'row', alignItems: 'flex-start' },
|
|
34
|
+
columnContainer: { flexDirection: 'column' },
|
|
35
|
+
|
|
36
|
+
// horizontal
|
|
37
|
+
hStep: { flex: 1, alignItems: 'center', gap: theme.tokens.spacing.sm },
|
|
38
|
+
hLabels: { alignItems: 'center', gap: 2, paddingHorizontal: 4 },
|
|
39
|
+
// El conector toma una fracción chica del ancho: compitiendo de igual a
|
|
40
|
+
// igual con los pasos (flex: 1) cada etiqueta se quedaba con 1/(2n-1) del
|
|
41
|
+
// total y RN partía palabras a la mitad ("Paymen / t"). Con 0.3 un flujo de
|
|
42
|
+
// 4 pasos deja ~68px por etiqueta, suficiente para una palabra corriente.
|
|
43
|
+
hConnector: { height: 2, flex: 0.3, marginTop: CIRCLE / 2 - 1 },
|
|
44
|
+
|
|
45
|
+
// vertical
|
|
46
|
+
vStep: { flexDirection: 'row', gap: theme.tokens.spacing.md },
|
|
47
|
+
vIndicatorColumn: { alignItems: 'center' },
|
|
48
|
+
vConnector: { width: 2, flex: 1, minHeight: 20, marginVertical: 4 },
|
|
49
|
+
vLabels: { flex: 1, gap: 2, paddingBottom: theme.tokens.spacing.lg },
|
|
50
|
+
|
|
51
|
+
circle: {
|
|
52
|
+
width: CIRCLE,
|
|
53
|
+
height: CIRCLE,
|
|
54
|
+
borderRadius: CIRCLE / 2,
|
|
55
|
+
borderWidth: 2,
|
|
56
|
+
alignItems: 'center',
|
|
57
|
+
justifyContent: 'center',
|
|
58
|
+
},
|
|
59
|
+
circleNumber: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.semibold },
|
|
60
|
+
label: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.semibold, color: theme.colors.text },
|
|
61
|
+
labelCentered: { textAlign: 'center' },
|
|
62
|
+
description: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.textLight },
|
|
63
|
+
descriptionCentered: { textAlign: 'center' },
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
function statusOf(index: number, current: number): StepStatus {
|
|
67
|
+
if (index < current) return 'completed';
|
|
68
|
+
if (index === current) return 'current';
|
|
69
|
+
return 'pending';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Steps
|
|
74
|
+
* Indicador de progreso multi-paso. Base visual de `Wizard`, pero utilizable
|
|
75
|
+
* suelto para mostrar el avance de un flujo que se navega de otra forma.
|
|
76
|
+
*/
|
|
77
|
+
export const Steps = memo(
|
|
78
|
+
({ steps, current, orientation = 'horizontal', completedIndicator = 'check', onStepPress, style }: StepsProps) => {
|
|
79
|
+
const colors = useColors();
|
|
80
|
+
const styles = useStyles();
|
|
81
|
+
const allowFontScaling = useAllowFontScaling();
|
|
82
|
+
const isVertical = orientation === 'vertical';
|
|
83
|
+
|
|
84
|
+
const colorsFor = (status: StepStatus) => {
|
|
85
|
+
if (status === 'completed') return { bg: colors.primary, border: colors.primary, fg: colors.white };
|
|
86
|
+
if (status === 'current') return { bg: 'transparent', border: colors.primary, fg: colors.primary };
|
|
87
|
+
return { bg: 'transparent', border: colors.border, fg: colors.textLight };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const renderCircle = (index: number, status: StepStatus) => {
|
|
91
|
+
const c = colorsFor(status);
|
|
92
|
+
const showCheck = status === 'completed' && completedIndicator === 'check';
|
|
93
|
+
return (
|
|
94
|
+
<View style={[styles.circle, { backgroundColor: c.bg, borderColor: c.border }]}>
|
|
95
|
+
{showCheck ? (
|
|
96
|
+
<Icon name="check" size={18} color={c.fg} />
|
|
97
|
+
) : (
|
|
98
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.circleNumber, { color: c.fg }]}>
|
|
99
|
+
{index + 1}
|
|
100
|
+
</Text>
|
|
101
|
+
)}
|
|
102
|
+
</View>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const renderLabels = (step: StepItem, status: StepStatus, centered: boolean) => (
|
|
107
|
+
<View style={centered ? styles.hLabels : styles.vLabels}>
|
|
108
|
+
<Text
|
|
109
|
+
allowFontScaling={allowFontScaling}
|
|
110
|
+
style={[
|
|
111
|
+
styles.label,
|
|
112
|
+
centered && styles.labelCentered,
|
|
113
|
+
status === 'pending' && { color: colors.textLight },
|
|
114
|
+
]}
|
|
115
|
+
>
|
|
116
|
+
{step.label}
|
|
117
|
+
</Text>
|
|
118
|
+
{!!step.description && (
|
|
119
|
+
<Text
|
|
120
|
+
allowFontScaling={allowFontScaling}
|
|
121
|
+
style={[styles.description, centered && styles.descriptionCentered]}
|
|
122
|
+
>
|
|
123
|
+
{step.description}
|
|
124
|
+
</Text>
|
|
125
|
+
)}
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const stepAccessibility = (index: number, step: StepItem, status: StepStatus) => ({
|
|
130
|
+
accessibilityRole: (onStepPress ? 'button' : 'text') as 'button' | 'text',
|
|
131
|
+
accessibilityLabel: `Step ${index + 1}: ${step.label}`,
|
|
132
|
+
accessibilityState: { selected: status === 'current', disabled: false },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (isVertical) {
|
|
136
|
+
return (
|
|
137
|
+
<View style={[styles.columnContainer, style]}>
|
|
138
|
+
{steps.map((step, index) => {
|
|
139
|
+
const status = statusOf(index, current);
|
|
140
|
+
const isLast = index === steps.length - 1;
|
|
141
|
+
const Wrapper: any = onStepPress ? TouchableOpacity : View;
|
|
142
|
+
return (
|
|
143
|
+
<Wrapper
|
|
144
|
+
key={step.label}
|
|
145
|
+
style={styles.vStep}
|
|
146
|
+
onPress={onStepPress ? () => onStepPress(index) : undefined}
|
|
147
|
+
activeOpacity={0.7}
|
|
148
|
+
{...stepAccessibility(index, step, status)}
|
|
149
|
+
>
|
|
150
|
+
<View style={styles.vIndicatorColumn}>
|
|
151
|
+
{renderCircle(index, status)}
|
|
152
|
+
{!isLast && (
|
|
153
|
+
<View
|
|
154
|
+
style={[
|
|
155
|
+
styles.vConnector,
|
|
156
|
+
{ backgroundColor: index < current ? colors.primary : colors.border },
|
|
157
|
+
]}
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</View>
|
|
161
|
+
{renderLabels(step, status, false)}
|
|
162
|
+
</Wrapper>
|
|
163
|
+
);
|
|
164
|
+
})}
|
|
165
|
+
</View>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<View style={[styles.rowContainer, style]}>
|
|
171
|
+
{steps.map((step, index) => {
|
|
172
|
+
const status = statusOf(index, current);
|
|
173
|
+
const isLast = index === steps.length - 1;
|
|
174
|
+
const Wrapper: any = onStepPress ? TouchableOpacity : View;
|
|
175
|
+
return (
|
|
176
|
+
<React.Fragment key={step.label}>
|
|
177
|
+
<Wrapper
|
|
178
|
+
style={styles.hStep}
|
|
179
|
+
onPress={onStepPress ? () => onStepPress(index) : undefined}
|
|
180
|
+
activeOpacity={0.7}
|
|
181
|
+
{...stepAccessibility(index, step, status)}
|
|
182
|
+
>
|
|
183
|
+
{renderCircle(index, status)}
|
|
184
|
+
{renderLabels(step, status, true)}
|
|
185
|
+
</Wrapper>
|
|
186
|
+
{!isLast && (
|
|
187
|
+
<View
|
|
188
|
+
style={[styles.hConnector, { backgroundColor: index < current ? colors.primary : colors.border }]}
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
191
|
+
</React.Fragment>
|
|
192
|
+
);
|
|
193
|
+
})}
|
|
194
|
+
</View>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
Steps.displayName = 'Steps';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling, useColors, useThemeMode, type ThemeMode } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
export interface ThemeToggleProps {
|
|
7
|
+
style?: StyleProp<ViewStyle>;
|
|
8
|
+
labels?: Partial<Record<ThemeMode, string>>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MODES: ThemeMode[] = ['system', 'light', 'dark'];
|
|
12
|
+
const DEFAULT_LABELS: Record<ThemeMode, string> = { system: 'Auto', light: 'Light', dark: 'Dark' };
|
|
13
|
+
|
|
14
|
+
const useStyles = createStyles((theme) => ({
|
|
15
|
+
track: {
|
|
16
|
+
flexDirection: 'row',
|
|
17
|
+
backgroundColor: theme.colors.inputBackground,
|
|
18
|
+
borderRadius: theme.tokens.radius.md,
|
|
19
|
+
padding: 3,
|
|
20
|
+
},
|
|
21
|
+
segment: {
|
|
22
|
+
flex: 1,
|
|
23
|
+
paddingVertical: theme.tokens.spacing.sm,
|
|
24
|
+
borderRadius: theme.tokens.radius.sm,
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
},
|
|
27
|
+
segmentActive: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },
|
|
28
|
+
label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.textLight },
|
|
29
|
+
labelActive: { color: theme.colors.primary, fontWeight: theme.tokens.fontWeight.semibold },
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ThemeToggle
|
|
34
|
+
* Segmented control de 3 estados (system/light/dark) conectado a
|
|
35
|
+
* UIProvider.setMode. Sin persistencia — el consumidor la resuelve vía
|
|
36
|
+
* onModeChange en <UIProvider>.
|
|
37
|
+
*/
|
|
38
|
+
export const ThemeToggle = memo(({ style, labels: labelOverrides }: ThemeToggleProps) => {
|
|
39
|
+
const { mode, setMode } = useThemeMode();
|
|
40
|
+
const colors = useColors();
|
|
41
|
+
const styles = useStyles();
|
|
42
|
+
const allowFontScaling = useAllowFontScaling();
|
|
43
|
+
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={[styles.track, style]} accessibilityRole="tablist">
|
|
47
|
+
{MODES.map((m) => {
|
|
48
|
+
const active = mode === m;
|
|
49
|
+
return (
|
|
50
|
+
<TouchableOpacity
|
|
51
|
+
key={m}
|
|
52
|
+
style={[styles.segment, active && styles.segmentActive]}
|
|
53
|
+
onPress={() => setMode(m)}
|
|
54
|
+
accessibilityRole="tab"
|
|
55
|
+
accessibilityState={{ selected: active }}
|
|
56
|
+
accessibilityLabel={labels[m]}
|
|
57
|
+
>
|
|
58
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.label, active && styles.labelActive]}>
|
|
59
|
+
{labels[m]}
|
|
60
|
+
</Text>
|
|
61
|
+
</TouchableOpacity>
|
|
62
|
+
);
|
|
63
|
+
})}
|
|
64
|
+
</View>
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
ThemeToggle.displayName = 'ThemeToggle';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { Stepper } from './Stepper';
|
|
2
|
+
export type { StepperProps } from './Stepper';
|
|
3
|
+
export { OptionCard } from './OptionCard';
|
|
4
|
+
export type { OptionCardProps } from './OptionCard';
|
|
5
|
+
export { InfoRow } from './InfoRow';
|
|
6
|
+
export type { InfoRowProps } from './InfoRow';
|
|
7
|
+
export { FormActions } from './FormActions';
|
|
8
|
+
export type { FormActionsProps } from './FormActions';
|
|
9
|
+
export { AvatarHeader } from './AvatarHeader';
|
|
10
|
+
export type { AvatarHeaderProps } from './AvatarHeader';
|
|
11
|
+
export { ThemeToggle } from './ThemeToggle';
|
|
12
|
+
export type { ThemeToggleProps } from './ThemeToggle';
|
|
13
|
+
export { Steps } from './Steps';
|
|
14
|
+
export type { StepsProps, StepItem, StepStatus } from './Steps';
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Modal, Text, TouchableWithoutFeedback, View } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';
|
|
5
|
+
import { Button, type ButtonVariant } from '../atoms/Button';
|
|
6
|
+
import { Icon } from '../icons/Icon';
|
|
7
|
+
import type { IconName } from '../icons/types';
|
|
8
|
+
|
|
9
|
+
export type AlertType = 'success' | 'error' | 'warning' | 'info' | 'question';
|
|
10
|
+
|
|
11
|
+
export interface AlertButton {
|
|
12
|
+
text: string;
|
|
13
|
+
onPress?: () => void;
|
|
14
|
+
style?: 'cancel' | 'default' | 'destructive';
|
|
15
|
+
variant?: ButtonVariant;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AlertProps {
|
|
19
|
+
visible: boolean;
|
|
20
|
+
title: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
type?: AlertType;
|
|
23
|
+
onClose?: () => void;
|
|
24
|
+
buttons?: AlertButton[];
|
|
25
|
+
confirmText?: string;
|
|
26
|
+
cancelText?: string;
|
|
27
|
+
onConfirm?: () => void;
|
|
28
|
+
onCancel?: () => void;
|
|
29
|
+
inline?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ICON_BY_TYPE: Record<AlertType, IconName> = {
|
|
33
|
+
success: 'check',
|
|
34
|
+
error: 'alert',
|
|
35
|
+
warning: 'warning',
|
|
36
|
+
question: 'question',
|
|
37
|
+
info: 'info',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const useStyles = createStyles((theme) => ({
|
|
41
|
+
overlay: { flex: 1, backgroundColor: theme.colors.overlay, justifyContent: 'center', alignItems: 'center', padding: theme.tokens.spacing.xl },
|
|
42
|
+
overlayInline: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, zIndex: 9999 },
|
|
43
|
+
container: {
|
|
44
|
+
backgroundColor: theme.colors.surface,
|
|
45
|
+
borderRadius: theme.tokens.radius.xl,
|
|
46
|
+
padding: theme.tokens.spacing.xl,
|
|
47
|
+
width: '100%',
|
|
48
|
+
maxWidth: 340,
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
...theme.tokens.shadow.md,
|
|
51
|
+
},
|
|
52
|
+
iconContainer: { marginBottom: theme.tokens.spacing.lg - 1 },
|
|
53
|
+
title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, color: theme.colors.text, textAlign: 'center', marginBottom: theme.tokens.spacing.md - 2 },
|
|
54
|
+
message: { fontSize: theme.tokens.fontSize.md, color: theme.colors.textLight, textAlign: 'center', marginBottom: theme.tokens.spacing.xl, lineHeight: theme.tokens.lineHeight.md },
|
|
55
|
+
buttonContainer: { flexDirection: 'row', justifyContent: 'center', gap: theme.tokens.spacing.sm + 2, width: '100%' },
|
|
56
|
+
buttonContainerVertical: { flexDirection: 'column' },
|
|
57
|
+
button: { paddingVertical: theme.tokens.spacing.md - 2, paddingHorizontal: theme.tokens.spacing.xl },
|
|
58
|
+
buttonHorizontal: { flex: 1 },
|
|
59
|
+
buttonVertical: { width: '100%' },
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
/** Deriva la lista de botones a partir de props, sin mutar `buttons` recibido por props. */
|
|
63
|
+
function resolveButtons(props: AlertProps, labels: { confirm: string; cancel: string; close: string }): AlertButton[] {
|
|
64
|
+
const { buttons, confirmText, cancelText, onConfirm, onCancel, onClose } = props;
|
|
65
|
+
if (buttons && buttons.length > 0) return buttons;
|
|
66
|
+
|
|
67
|
+
if (confirmText || onConfirm) {
|
|
68
|
+
const list: AlertButton[] = [];
|
|
69
|
+
if (cancelText || onCancel) {
|
|
70
|
+
list.push({ text: cancelText || labels.cancel, onPress: onCancel || onClose, variant: 'outline', style: 'cancel' });
|
|
71
|
+
}
|
|
72
|
+
list.push({ text: confirmText || labels.confirm, onPress: onConfirm, variant: 'primary' });
|
|
73
|
+
return list;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return [{ text: labels.close, onPress: onClose, variant: 'outline' }];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Alert
|
|
81
|
+
* Componente declarativo de diálogo. Para uso imperativo (mostrar desde
|
|
82
|
+
* un handler sin levantar estado propio), usar AlertProvider + useAlert().
|
|
83
|
+
*/
|
|
84
|
+
export const Alert = memo((props: AlertProps) => {
|
|
85
|
+
const { visible, title, message, type = 'info', onClose, inline = false } = props;
|
|
86
|
+
const colors = useColors();
|
|
87
|
+
const styles = useStyles();
|
|
88
|
+
const labels = useLabels();
|
|
89
|
+
const allowFontScaling = useAllowFontScaling();
|
|
90
|
+
|
|
91
|
+
if (!visible) return null;
|
|
92
|
+
|
|
93
|
+
const iconColor: Record<AlertType, string> = {
|
|
94
|
+
success: colors.success,
|
|
95
|
+
error: colors.error,
|
|
96
|
+
warning: colors.warning,
|
|
97
|
+
question: colors.primary,
|
|
98
|
+
info: colors.secondary,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const effectiveButtons = resolveButtons(props, { confirm: labels.confirm, cancel: labels.cancel, close: labels.close });
|
|
102
|
+
const vertical = effectiveButtons.length > 2;
|
|
103
|
+
|
|
104
|
+
const content = (
|
|
105
|
+
<TouchableWithoutFeedback onPress={onClose} accessibilityRole="none">
|
|
106
|
+
<View style={[styles.overlay, inline && styles.overlayInline]}>
|
|
107
|
+
<TouchableWithoutFeedback>
|
|
108
|
+
<View style={styles.container} accessibilityRole="alert" accessibilityViewIsModal>
|
|
109
|
+
<View style={styles.iconContainer}>
|
|
110
|
+
<Icon name={ICON_BY_TYPE[type]} size={48} color={iconColor[type]} />
|
|
111
|
+
</View>
|
|
112
|
+
|
|
113
|
+
<Text allowFontScaling={allowFontScaling} style={styles.title}>
|
|
114
|
+
{title}
|
|
115
|
+
</Text>
|
|
116
|
+
|
|
117
|
+
{!!message && (
|
|
118
|
+
<Text allowFontScaling={allowFontScaling} style={styles.message}>
|
|
119
|
+
{message}
|
|
120
|
+
</Text>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
<View style={[styles.buttonContainer, vertical && styles.buttonContainerVertical]}>
|
|
124
|
+
{effectiveButtons.map((btn, index) => {
|
|
125
|
+
const isCancel = btn.style === 'cancel';
|
|
126
|
+
let variant: ButtonVariant = btn.variant || 'primary';
|
|
127
|
+
if (!btn.variant && isCancel) variant = 'outline';
|
|
128
|
+
if (!btn.variant && btn.style === 'destructive') variant = 'destructive';
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Button
|
|
132
|
+
key={index}
|
|
133
|
+
title={btn.text}
|
|
134
|
+
onPress={btn.onPress || onClose || (() => {})}
|
|
135
|
+
variant={variant}
|
|
136
|
+
style={[styles.button, vertical ? styles.buttonVertical : styles.buttonHorizontal]}
|
|
137
|
+
textStyle={{ fontSize: 14 }}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
})}
|
|
141
|
+
</View>
|
|
142
|
+
</View>
|
|
143
|
+
</TouchableWithoutFeedback>
|
|
144
|
+
</View>
|
|
145
|
+
</TouchableWithoutFeedback>
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (inline) {
|
|
149
|
+
return (
|
|
150
|
+
<View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} pointerEvents="box-none">
|
|
151
|
+
{content}
|
|
152
|
+
</View>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Modal transparent visible={visible} animationType="fade" statusBarTranslucent presentationStyle="overFullScreen" onRequestClose={onClose || (() => {})}>
|
|
158
|
+
{content}
|
|
159
|
+
</Modal>
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
Alert.displayName = 'Alert';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Alert, type AlertButton, type AlertType } from './Alert';
|
|
3
|
+
|
|
4
|
+
export interface AlertOptions {
|
|
5
|
+
title: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
type?: AlertType;
|
|
8
|
+
confirmText?: string;
|
|
9
|
+
cancelText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ConfirmOptions extends AlertOptions {
|
|
13
|
+
/** Botón de confirmar en rojo/destructivo (para "eliminar", etc). */
|
|
14
|
+
destructive?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AlertContextValue {
|
|
18
|
+
/** Muestra un alert de un solo botón. Resuelve cuando se cierra. */
|
|
19
|
+
alert: (options: AlertOptions) => Promise<void>;
|
|
20
|
+
/** Muestra confirmar/cancelar. Resuelve true si se confirmó, false si se canceló. */
|
|
21
|
+
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
|
22
|
+
hide: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const AlertContext = createContext<AlertContextValue | undefined>(undefined);
|
|
26
|
+
|
|
27
|
+
interface InternalState {
|
|
28
|
+
visible: boolean;
|
|
29
|
+
title: string;
|
|
30
|
+
message?: string;
|
|
31
|
+
type: AlertType;
|
|
32
|
+
buttons: AlertButton[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const HIDDEN: InternalState = { visible: false, title: '', type: 'info', buttons: [] };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* AlertProvider
|
|
39
|
+
* Monta un único <Alert> controlado imperativamente. Colocar una vez cerca
|
|
40
|
+
* de la raíz (dentro de UIProvider). Los handlers async pueden hacer
|
|
41
|
+
* `const ok = await confirm({ title: 'Eliminar', destructive: true })`.
|
|
42
|
+
*/
|
|
43
|
+
export function AlertProvider({ children }: { children: React.ReactNode }) {
|
|
44
|
+
const [state, setState] = useState<InternalState>(HIDDEN);
|
|
45
|
+
const resolverRef = useRef<((value: any) => void) | null>(null);
|
|
46
|
+
|
|
47
|
+
const hide = useCallback(() => {
|
|
48
|
+
setState(HIDDEN);
|
|
49
|
+
resolverRef.current?.(undefined);
|
|
50
|
+
resolverRef.current = null;
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const alertFn = useCallback((options: AlertOptions): Promise<void> => {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
resolverRef.current = resolve;
|
|
56
|
+
setState({
|
|
57
|
+
visible: true,
|
|
58
|
+
title: options.title,
|
|
59
|
+
message: options.message,
|
|
60
|
+
type: options.type ?? 'info',
|
|
61
|
+
buttons: [
|
|
62
|
+
{
|
|
63
|
+
text: options.confirmText ?? 'OK',
|
|
64
|
+
onPress: () => {
|
|
65
|
+
setState(HIDDEN);
|
|
66
|
+
resolve();
|
|
67
|
+
resolverRef.current = null;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const confirmFn = useCallback((options: ConfirmOptions): Promise<boolean> => {
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
resolverRef.current = resolve;
|
|
78
|
+
setState({
|
|
79
|
+
visible: true,
|
|
80
|
+
title: options.title,
|
|
81
|
+
message: options.message,
|
|
82
|
+
type: options.type ?? 'question',
|
|
83
|
+
buttons: [
|
|
84
|
+
{
|
|
85
|
+
text: options.cancelText ?? 'Cancel',
|
|
86
|
+
style: 'cancel',
|
|
87
|
+
variant: 'outline',
|
|
88
|
+
onPress: () => {
|
|
89
|
+
setState(HIDDEN);
|
|
90
|
+
resolve(false);
|
|
91
|
+
resolverRef.current = null;
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
text: options.confirmText ?? 'OK',
|
|
96
|
+
variant: options.destructive ? 'destructive' : 'primary',
|
|
97
|
+
style: options.destructive ? 'destructive' : 'default',
|
|
98
|
+
onPress: () => {
|
|
99
|
+
setState(HIDDEN);
|
|
100
|
+
resolve(true);
|
|
101
|
+
resolverRef.current = null;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
const value = useMemo<AlertContextValue>(() => ({ alert: alertFn, confirm: confirmFn, hide }), [alertFn, confirmFn, hide]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<AlertContext.Provider value={value}>
|
|
113
|
+
{children}
|
|
114
|
+
<Alert visible={state.visible} title={state.title} message={state.message} type={state.type} buttons={state.buttons} onClose={hide} />
|
|
115
|
+
</AlertContext.Provider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function useAlert(): AlertContextValue {
|
|
120
|
+
const ctx = useContext(AlertContext);
|
|
121
|
+
if (!ctx) {
|
|
122
|
+
throw new Error('orn-ui: useAlert must be used within an <AlertProvider>.');
|
|
123
|
+
}
|
|
124
|
+
return ctx;
|
|
125
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { memo, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Animated, Dimensions, Modal, PanResponder, Pressable, View, type ViewStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useInsets } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
7
|
+
const DISMISS_THRESHOLD = 100;
|
|
8
|
+
|
|
9
|
+
export interface BottomSheetProps {
|
|
10
|
+
visible: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
closeAccessibilityLabel?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const useStyles = createStyles((theme) => ({
|
|
17
|
+
container: { flex: 1, justifyContent: 'flex-end' },
|
|
18
|
+
backdrop: { backgroundColor: theme.colors.overlay },
|
|
19
|
+
sheet: {
|
|
20
|
+
backgroundColor: theme.colors.surface,
|
|
21
|
+
borderTopLeftRadius: theme.tokens.radius.xxl,
|
|
22
|
+
borderTopRightRadius: theme.tokens.radius.xxl,
|
|
23
|
+
paddingHorizontal: theme.tokens.spacing.xl,
|
|
24
|
+
...theme.tokens.shadow.lg,
|
|
25
|
+
},
|
|
26
|
+
handleArea: { paddingVertical: theme.tokens.spacing.md, alignItems: 'center' },
|
|
27
|
+
handle: { width: 40, height: 5, borderRadius: 3, backgroundColor: theme.colors.border },
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const absoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* BottomSheet
|
|
34
|
+
* Modal que aparece desde abajo: animación con Animated (no la del Modal
|
|
35
|
+
* nativo) y arrastre para cerrar con PanResponder — no un gesture-handler
|
|
36
|
+
* externo, para mantener la librería en cero dependencias.
|
|
37
|
+
*/
|
|
38
|
+
export const BottomSheet = memo(({ visible, onClose, children, closeAccessibilityLabel = 'Close' }: BottomSheetProps) => {
|
|
39
|
+
const styles = useStyles();
|
|
40
|
+
const insets = useInsets();
|
|
41
|
+
const [mounted, setMounted] = useState(visible);
|
|
42
|
+
const translateY = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
|
|
43
|
+
const backdropOpacity = useRef(new Animated.Value(0)).current;
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (visible) {
|
|
47
|
+
setMounted(true);
|
|
48
|
+
Animated.timing(translateY, { toValue: 0, duration: 250, useNativeDriver: true }).start();
|
|
49
|
+
Animated.timing(backdropOpacity, { toValue: 1, duration: 250, useNativeDriver: true }).start();
|
|
50
|
+
} else {
|
|
51
|
+
Animated.timing(translateY, { toValue: SCREEN_HEIGHT, duration: 200, useNativeDriver: true }).start(({ finished }) => {
|
|
52
|
+
if (finished) setMounted(false);
|
|
53
|
+
});
|
|
54
|
+
Animated.timing(backdropOpacity, { toValue: 0, duration: 200, useNativeDriver: true }).start();
|
|
55
|
+
}
|
|
56
|
+
}, [visible]);
|
|
57
|
+
|
|
58
|
+
const panResponder = useRef(
|
|
59
|
+
PanResponder.create({
|
|
60
|
+
onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 5 && Math.abs(gesture.dy) > Math.abs(gesture.dx),
|
|
61
|
+
onPanResponderMove: (_, gesture) => {
|
|
62
|
+
if (gesture.dy > 0) translateY.setValue(gesture.dy);
|
|
63
|
+
},
|
|
64
|
+
onPanResponderRelease: (_, gesture) => {
|
|
65
|
+
if (gesture.dy > DISMISS_THRESHOLD) {
|
|
66
|
+
onClose();
|
|
67
|
+
} else {
|
|
68
|
+
Animated.timing(translateY, { toValue: 0, duration: 150, useNativeDriver: true }).start();
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
).current;
|
|
73
|
+
|
|
74
|
+
if (!mounted) return null;
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Modal visible transparent animationType="none" statusBarTranslucent onRequestClose={onClose}>
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
<Animated.View style={[absoluteFill, styles.backdrop, { opacity: backdropOpacity }]}>
|
|
80
|
+
<Pressable style={absoluteFill} onPress={onClose} accessibilityRole="button" accessibilityLabel={closeAccessibilityLabel} />
|
|
81
|
+
</Animated.View>
|
|
82
|
+
|
|
83
|
+
<Animated.View
|
|
84
|
+
style={[styles.sheet, { paddingBottom: Math.max(insets.bottom, 20) }, { transform: [{ translateY }] }]}
|
|
85
|
+
>
|
|
86
|
+
<View style={styles.handleArea} {...panResponder.panHandlers}>
|
|
87
|
+
<View style={styles.handle} />
|
|
88
|
+
</View>
|
|
89
|
+
{children}
|
|
90
|
+
</Animated.View>
|
|
91
|
+
</View>
|
|
92
|
+
</Modal>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
BottomSheet.displayName = 'BottomSheet';
|