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,137 @@
|
|
|
1
|
+
import React, { memo, useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Image as RNImage,
|
|
4
|
+
Platform,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
type ImageErrorEventData,
|
|
8
|
+
type ImageResizeMode,
|
|
9
|
+
type ImageSourcePropType,
|
|
10
|
+
type NativeSyntheticEvent,
|
|
11
|
+
type StyleProp,
|
|
12
|
+
type ViewStyle,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { createStyles } from '../theme/createStyles';
|
|
15
|
+
import { Spinner } from './Spinner';
|
|
16
|
+
|
|
17
|
+
export interface ImageProps {
|
|
18
|
+
source: ImageSourcePropType;
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
/** @default 'cover' */
|
|
22
|
+
resizeMode?: ImageResizeMode;
|
|
23
|
+
/** Radio de las esquinas, mismo patrón que Avatar/Card. @default 0 */
|
|
24
|
+
radius?: number;
|
|
25
|
+
/** Qué mostrar mientras carga. `false` para no mostrar nada. @default true (spinner chico) */
|
|
26
|
+
loading?: React.ReactNode | boolean;
|
|
27
|
+
/** Qué mostrar si la carga falla. @default un placeholder gris, sin contenido */
|
|
28
|
+
fallback?: React.ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* 'high' dispara Image.prefetch() al montar, antes de que <Image> arranque
|
|
31
|
+
* su propio ciclo de carga — pensado para la imagen above-the-fold de una
|
|
32
|
+
* pantalla (la primera que el usuario ve). Sin costo si ya está en caché.
|
|
33
|
+
* @default 'normal'
|
|
34
|
+
*/
|
|
35
|
+
priority?: 'low' | 'normal' | 'high';
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
onLoadEnd?: () => void;
|
|
38
|
+
onError?: (error: NativeSyntheticEvent<ImageErrorEventData>) => void;
|
|
39
|
+
testID?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const useStyles = createStyles((theme) => ({
|
|
43
|
+
container: { overflow: 'hidden', backgroundColor: theme.colors.border + '30' },
|
|
44
|
+
overlay: { alignItems: 'center', justifyContent: 'center' },
|
|
45
|
+
fallback: { flex: 1, backgroundColor: theme.colors.border + '30' },
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
function sourceUri(source: ImageSourcePropType): string | undefined {
|
|
49
|
+
return typeof source === 'object' && source !== null && 'uri' in source ? source.uri : undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Image
|
|
54
|
+
* Wrapper cero-dependencias sobre <Image> de RN: agrega estado de carga y
|
|
55
|
+
* error (con los mismos placeholders que el resto de la librería), y expone
|
|
56
|
+
* `priority="high"` para adelantar la descarga vía Image.prefetch(). El
|
|
57
|
+
* cache real (disco/memoria) lo maneja el motor de imágenes nativo de RN —
|
|
58
|
+
* esto no lo reimplementa, solo no lo deja apagado por accidente: en iOS
|
|
59
|
+
* fuerza `cache="force-cache"` (usa el disco aunque el header HTTP no lo
|
|
60
|
+
* pida); en Android el cache de disco de la imagen nativa ya está siempre
|
|
61
|
+
* activo, no hay knob equivalente que tocar.
|
|
62
|
+
*/
|
|
63
|
+
export const Image = memo(
|
|
64
|
+
({
|
|
65
|
+
source,
|
|
66
|
+
width,
|
|
67
|
+
height,
|
|
68
|
+
resizeMode = 'cover',
|
|
69
|
+
radius = 0,
|
|
70
|
+
loading = true,
|
|
71
|
+
fallback,
|
|
72
|
+
priority = 'normal',
|
|
73
|
+
style,
|
|
74
|
+
onLoadEnd,
|
|
75
|
+
onError,
|
|
76
|
+
testID,
|
|
77
|
+
}: ImageProps) => {
|
|
78
|
+
const styles = useStyles();
|
|
79
|
+
const [status, setStatus] = useState<'loading' | 'loaded' | 'error'>('loading');
|
|
80
|
+
const uri = sourceUri(source);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
// Resetea el estado si cambia la imagen (misma instancia de Image,
|
|
84
|
+
// source distinto: p.ej. avatar de un usuario a otro en la misma fila).
|
|
85
|
+
setStatus('loading');
|
|
86
|
+
}, [uri ?? source]);
|
|
87
|
+
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (priority === 'high' && uri) {
|
|
90
|
+
RNImage.prefetch(uri).catch(() => {});
|
|
91
|
+
}
|
|
92
|
+
}, [priority, uri]);
|
|
93
|
+
|
|
94
|
+
const handleError = useCallback(
|
|
95
|
+
(e: NativeSyntheticEvent<ImageErrorEventData>) => {
|
|
96
|
+
setStatus('error');
|
|
97
|
+
onError?.(e);
|
|
98
|
+
},
|
|
99
|
+
[onError]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const handleLoadEnd = useCallback(() => {
|
|
103
|
+
setStatus('loaded');
|
|
104
|
+
onLoadEnd?.();
|
|
105
|
+
}, [onLoadEnd]);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<View style={[styles.container, { width, height, borderRadius: radius }, style]} testID={testID}>
|
|
109
|
+
{status !== 'error' && (
|
|
110
|
+
<RNImage
|
|
111
|
+
source={source}
|
|
112
|
+
resizeMode={resizeMode}
|
|
113
|
+
style={StyleSheet.absoluteFill}
|
|
114
|
+
fadeDuration={Platform.OS === 'android' ? 150 : undefined}
|
|
115
|
+
{...(Platform.OS === 'ios' ? { cache: 'force-cache' as const } : null)}
|
|
116
|
+
onLoadEnd={handleLoadEnd}
|
|
117
|
+
onError={handleError}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
{status === 'loading' && loading !== false && (
|
|
121
|
+
<View style={[StyleSheet.absoluteFill, styles.overlay]}>
|
|
122
|
+
{loading === true ? <Spinner size="small" fullscreen={false} /> : loading}
|
|
123
|
+
</View>
|
|
124
|
+
)}
|
|
125
|
+
{status === 'error' && (fallback ?? <View style={styles.fallback} />)}
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
Image.displayName = 'Image';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Precarga una URL antes de montar el componente (p.ej. al recibir la
|
|
134
|
+
* respuesta de un listado, antes de navegar al detalle). Re-exporta
|
|
135
|
+
* Image.prefetch de RN — no hay nada zero-dep que agregarle encima.
|
|
136
|
+
*/
|
|
137
|
+
export const prefetchImage = RNImage.prefetch;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { memo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Animated,
|
|
5
|
+
Text,
|
|
6
|
+
TextInput,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
View,
|
|
9
|
+
type StyleProp,
|
|
10
|
+
type TextInputProps,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from 'react-native';
|
|
13
|
+
import { createStyles } from '../theme/createStyles';
|
|
14
|
+
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
15
|
+
import { Icon } from '../icons/Icon';
|
|
16
|
+
import type { IconName } from '../icons/types';
|
|
17
|
+
|
|
18
|
+
export interface InputProps extends Omit<TextInputProps, 'style'> {
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Muestra un asterisco junto al label. Puramente visual, no valida nada. */
|
|
21
|
+
required?: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
24
|
+
style?: TextInputProps['style'];
|
|
25
|
+
leftIconName?: IconName;
|
|
26
|
+
rightIconName?: IconName;
|
|
27
|
+
onRightIconPress?: () => void;
|
|
28
|
+
isPassword?: boolean;
|
|
29
|
+
isLoading?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const useStyles = createStyles((theme) => ({
|
|
33
|
+
container: { marginBottom: theme.tokens.spacing.lg, width: '100%' },
|
|
34
|
+
label: {
|
|
35
|
+
marginBottom: theme.tokens.spacing.sm,
|
|
36
|
+
fontSize: theme.tokens.fontSize.md,
|
|
37
|
+
fontWeight: theme.tokens.fontWeight.medium,
|
|
38
|
+
color: theme.colors.text,
|
|
39
|
+
},
|
|
40
|
+
required: { color: theme.colors.error },
|
|
41
|
+
inputWrapper: {
|
|
42
|
+
flexDirection: 'row',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
backgroundColor: theme.colors.inputBackground,
|
|
45
|
+
borderRadius: theme.tokens.radius.lg,
|
|
46
|
+
borderWidth: 2,
|
|
47
|
+
minHeight: 50,
|
|
48
|
+
},
|
|
49
|
+
input: {
|
|
50
|
+
flex: 1,
|
|
51
|
+
paddingVertical: theme.tokens.spacing.md,
|
|
52
|
+
paddingHorizontal: theme.tokens.spacing.lg,
|
|
53
|
+
fontSize: theme.tokens.fontSize.lg,
|
|
54
|
+
color: theme.colors.text,
|
|
55
|
+
},
|
|
56
|
+
leftIcon: { marginLeft: theme.tokens.spacing.lg, marginRight: -theme.tokens.spacing.xs },
|
|
57
|
+
rightIcon: { padding: theme.tokens.spacing.sm + 2, marginRight: theme.tokens.spacing.xs },
|
|
58
|
+
errorText: { marginTop: theme.tokens.spacing.xs, fontSize: theme.tokens.fontSize.sm, color: theme.colors.error },
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Input
|
|
63
|
+
* El borde anima color entre border/primary/error con Animated (no
|
|
64
|
+
* useNativeDriver: los navegadores de color en RN sin Fabric solo son
|
|
65
|
+
* confiables en el hilo de JS; el costo es despreciable, se anima un borde).
|
|
66
|
+
*/
|
|
67
|
+
export const Input = memo(
|
|
68
|
+
({
|
|
69
|
+
label,
|
|
70
|
+
required = false,
|
|
71
|
+
error,
|
|
72
|
+
containerStyle,
|
|
73
|
+
leftIconName,
|
|
74
|
+
rightIconName,
|
|
75
|
+
onRightIconPress,
|
|
76
|
+
isPassword = false,
|
|
77
|
+
isLoading = false,
|
|
78
|
+
style,
|
|
79
|
+
onFocus,
|
|
80
|
+
onBlur,
|
|
81
|
+
testID,
|
|
82
|
+
...props
|
|
83
|
+
}: InputProps) => {
|
|
84
|
+
const colors = useColors();
|
|
85
|
+
const styles = useStyles();
|
|
86
|
+
const allowFontScaling = useAllowFontScaling();
|
|
87
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
88
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
89
|
+
const focusProgress = useRef(new Animated.Value(0)).current;
|
|
90
|
+
|
|
91
|
+
const handleFocus: TextInputProps['onFocus'] = (e) => {
|
|
92
|
+
setIsFocused(true);
|
|
93
|
+
Animated.timing(focusProgress, { toValue: 1, duration: 200, useNativeDriver: false }).start();
|
|
94
|
+
onFocus?.(e);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleBlur: TextInputProps['onBlur'] = (e) => {
|
|
98
|
+
setIsFocused(false);
|
|
99
|
+
Animated.timing(focusProgress, { toValue: 0, duration: 200, useNativeDriver: false }).start();
|
|
100
|
+
onBlur?.(e);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const borderColor = error
|
|
104
|
+
? colors.error
|
|
105
|
+
: focusProgress.interpolate({ outputRange: [colors.border, colors.primary], inputRange: [0, 1] });
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<View style={[styles.container, containerStyle]} testID={testID}>
|
|
109
|
+
{label && (
|
|
110
|
+
<Text allowFontScaling={allowFontScaling} style={styles.label}>
|
|
111
|
+
{label}
|
|
112
|
+
{required && <Text style={styles.required}> *</Text>}
|
|
113
|
+
</Text>
|
|
114
|
+
)}
|
|
115
|
+
|
|
116
|
+
<Animated.View style={[styles.inputWrapper, { borderColor }]}>
|
|
117
|
+
{leftIconName && <Icon name={leftIconName} size={20} color={colors.textLight} style={styles.leftIcon} />}
|
|
118
|
+
|
|
119
|
+
<TextInput
|
|
120
|
+
style={[styles.input, style]}
|
|
121
|
+
placeholderTextColor={colors.textLight}
|
|
122
|
+
onFocus={handleFocus}
|
|
123
|
+
onBlur={handleBlur}
|
|
124
|
+
secureTextEntry={isPassword && !showPassword}
|
|
125
|
+
allowFontScaling={allowFontScaling}
|
|
126
|
+
textAlignVertical="center"
|
|
127
|
+
accessibilityLabel={label ?? props.placeholder}
|
|
128
|
+
{...props}
|
|
129
|
+
/>
|
|
130
|
+
|
|
131
|
+
{isLoading ? (
|
|
132
|
+
<View style={styles.rightIcon}>
|
|
133
|
+
<ActivityIndicator size="small" color={colors.primary} />
|
|
134
|
+
</View>
|
|
135
|
+
) : isPassword ? (
|
|
136
|
+
<TouchableOpacity
|
|
137
|
+
onPress={() => setShowPassword((v) => !v)}
|
|
138
|
+
style={styles.rightIcon}
|
|
139
|
+
accessibilityRole="button"
|
|
140
|
+
accessibilityLabel={showPassword ? 'Hide password' : 'Show password'}
|
|
141
|
+
>
|
|
142
|
+
<Icon name={showPassword ? 'eye-off' : 'eye'} size={20} color={colors.textLight} />
|
|
143
|
+
</TouchableOpacity>
|
|
144
|
+
) : rightIconName ? (
|
|
145
|
+
<TouchableOpacity
|
|
146
|
+
onPress={onRightIconPress}
|
|
147
|
+
style={styles.rightIcon}
|
|
148
|
+
disabled={!onRightIconPress}
|
|
149
|
+
accessibilityRole="button"
|
|
150
|
+
accessibilityLabel="Clear"
|
|
151
|
+
>
|
|
152
|
+
<Icon name={rightIconName} size={20} color={colors.textLight} />
|
|
153
|
+
</TouchableOpacity>
|
|
154
|
+
) : null}
|
|
155
|
+
</Animated.View>
|
|
156
|
+
|
|
157
|
+
{!!error && (
|
|
158
|
+
<Text allowFontScaling={allowFontScaling} style={styles.errorText}>
|
|
159
|
+
{error}
|
|
160
|
+
</Text>
|
|
161
|
+
)}
|
|
162
|
+
</View>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
Input.displayName = 'Input';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Text, View, type StyleProp, type TextStyle, type ViewStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
export interface KeyValueRowProps {
|
|
7
|
+
label: React.ReactNode;
|
|
8
|
+
value: React.ReactNode;
|
|
9
|
+
style?: StyleProp<ViewStyle>;
|
|
10
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
11
|
+
valueStyle?: StyleProp<TextStyle>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const useStyles = createStyles((theme) => ({
|
|
15
|
+
row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
|
16
|
+
label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.semibold, color: theme.colors.textLight },
|
|
17
|
+
value: { fontSize: theme.tokens.fontSize.sm, textAlign: 'right', color: theme.colors.text },
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* KeyValueRow
|
|
22
|
+
* Fila label (izquierda) / value (derecha). Si label/value son string o
|
|
23
|
+
* number, se renderizan con el estilo default; si ya vienen envueltos en un
|
|
24
|
+
* componente de texto propio, se respetan tal cual.
|
|
25
|
+
*/
|
|
26
|
+
export const KeyValueRow = memo(({ label, value, style, labelStyle, valueStyle }: KeyValueRowProps) => {
|
|
27
|
+
const styles = useStyles();
|
|
28
|
+
const allowFontScaling = useAllowFontScaling();
|
|
29
|
+
return (
|
|
30
|
+
<View style={[styles.row, style]}>
|
|
31
|
+
{typeof label === 'string' || typeof label === 'number' ? (
|
|
32
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.label, labelStyle]}>
|
|
33
|
+
{label}
|
|
34
|
+
</Text>
|
|
35
|
+
) : (
|
|
36
|
+
label
|
|
37
|
+
)}
|
|
38
|
+
{typeof value === 'string' || typeof value === 'number' ? (
|
|
39
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.value, valueStyle]}>
|
|
40
|
+
{value}
|
|
41
|
+
</Text>
|
|
42
|
+
) : (
|
|
43
|
+
value
|
|
44
|
+
)}
|
|
45
|
+
</View>
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
KeyValueRow.displayName = 'KeyValueRow';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { memo, useRef } from 'react';
|
|
2
|
+
import { Animated, Pressable, type PressableProps, type GestureResponderEvent } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface PressableScaleProps extends PressableProps {
|
|
5
|
+
/** Escala objetivo al presionar. @default 0.96 */
|
|
6
|
+
scaleTo?: number;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PressableScale
|
|
12
|
+
* Feedback táctil de "achicarse" al presionar, con Animated.spring de RN
|
|
13
|
+
* (useNativeDriver: true — corre en el hilo nativo, sin coste de JS por frame).
|
|
14
|
+
* No depende de reanimated: la librería entera es cero-dependencias.
|
|
15
|
+
*/
|
|
16
|
+
export const PressableScale = memo(
|
|
17
|
+
({ scaleTo = 0.96, style, children, onPressIn, onPressOut, disabled, ...props }: PressableScaleProps) => {
|
|
18
|
+
const scale = useRef(new Animated.Value(1)).current;
|
|
19
|
+
|
|
20
|
+
const animateTo = (value: number) => {
|
|
21
|
+
Animated.spring(scale, {
|
|
22
|
+
toValue: value,
|
|
23
|
+
useNativeDriver: true,
|
|
24
|
+
speed: 50,
|
|
25
|
+
bounciness: 6,
|
|
26
|
+
}).start();
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handlePressIn = (e: GestureResponderEvent) => {
|
|
30
|
+
if (!disabled) animateTo(scaleTo);
|
|
31
|
+
onPressIn?.(e);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handlePressOut = (e: GestureResponderEvent) => {
|
|
35
|
+
if (!disabled) animateTo(1);
|
|
36
|
+
onPressOut?.(e);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Pressable
|
|
41
|
+
{...props}
|
|
42
|
+
disabled={disabled}
|
|
43
|
+
onPressIn={handlePressIn}
|
|
44
|
+
onPressOut={handlePressOut}
|
|
45
|
+
style={style}
|
|
46
|
+
>
|
|
47
|
+
<Animated.View style={{ transform: [{ scale }] }}>{children}</Animated.View>
|
|
48
|
+
</Pressable>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
PressableScale.displayName = 'PressableScale';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { memo, useEffect, useRef } from 'react';
|
|
2
|
+
import { ActivityIndicator, Animated, Easing, Text, View } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
export type SpinnerVariant = 'native' | 'dots' | 'ring';
|
|
7
|
+
|
|
8
|
+
export interface SpinnerProps {
|
|
9
|
+
size?: 'small' | 'large';
|
|
10
|
+
color?: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
/** @default true. En false, no ocupa flex:1 (útil inline, dentro de un botón/fila). */
|
|
13
|
+
fullscreen?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 'native' usa el ActivityIndicator del sistema; 'dots' y 'ring' son
|
|
16
|
+
* indicadores propios, iguales en iOS y Android.
|
|
17
|
+
* @default 'native'
|
|
18
|
+
*/
|
|
19
|
+
variant?: SpinnerVariant;
|
|
20
|
+
/** Indicador totalmente propio. Gana sobre `variant`. */
|
|
21
|
+
indicator?: React.ReactNode;
|
|
22
|
+
testID?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const useStyles = createStyles((theme) => ({
|
|
26
|
+
container: { justifyContent: 'center', alignItems: 'center', padding: theme.tokens.spacing.xl },
|
|
27
|
+
text: { marginTop: theme.tokens.spacing.md, color: theme.colors.textLight, fontSize: theme.tokens.fontSize.md },
|
|
28
|
+
dots: { flexDirection: 'row', gap: theme.tokens.spacing.sm },
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
/** Tres puntos que laten en secuencia. Animated + useNativeDriver: corre en el hilo nativo. */
|
|
32
|
+
const DotsIndicator = memo(({ color, dotSize }: { color: string; dotSize: number }) => {
|
|
33
|
+
const dots = [useRef(new Animated.Value(0)).current, useRef(new Animated.Value(0)).current, useRef(new Animated.Value(0)).current];
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const animations = dots.map((dot, i) =>
|
|
37
|
+
Animated.loop(
|
|
38
|
+
Animated.sequence([
|
|
39
|
+
Animated.delay(i * 140),
|
|
40
|
+
Animated.timing(dot, { toValue: 1, duration: 320, useNativeDriver: true, easing: Easing.out(Easing.quad) }),
|
|
41
|
+
Animated.timing(dot, { toValue: 0, duration: 320, useNativeDriver: true, easing: Easing.in(Easing.quad) }),
|
|
42
|
+
Animated.delay((2 - i) * 140),
|
|
43
|
+
])
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
animations.forEach((a) => a.start());
|
|
47
|
+
// Sin stop() los loops siguen corriendo tras desmontar y mantienen vivo
|
|
48
|
+
// el árbol de Animated.
|
|
49
|
+
return () => animations.forEach((a) => a.stop());
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={{ flexDirection: 'row', gap: dotSize * 0.6 }}>
|
|
54
|
+
{dots.map((dot, i) => (
|
|
55
|
+
<Animated.View
|
|
56
|
+
key={i}
|
|
57
|
+
style={{
|
|
58
|
+
width: dotSize,
|
|
59
|
+
height: dotSize,
|
|
60
|
+
borderRadius: dotSize / 2,
|
|
61
|
+
backgroundColor: color,
|
|
62
|
+
opacity: dot.interpolate({ inputRange: [0, 1], outputRange: [0.3, 1] }),
|
|
63
|
+
transform: [{ scale: dot.interpolate({ inputRange: [0, 1], outputRange: [0.75, 1] }) }],
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</View>
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
DotsIndicator.displayName = 'DotsIndicator';
|
|
71
|
+
|
|
72
|
+
/** Anillo que gira: un borde con un solo lado coloreado. */
|
|
73
|
+
const RingIndicator = memo(({ color, diameter }: { color: string; diameter: number }) => {
|
|
74
|
+
const spin = useRef(new Animated.Value(0)).current;
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const animation = Animated.loop(
|
|
78
|
+
Animated.timing(spin, { toValue: 1, duration: 900, useNativeDriver: true, easing: Easing.linear })
|
|
79
|
+
);
|
|
80
|
+
animation.start();
|
|
81
|
+
return () => animation.stop();
|
|
82
|
+
}, []);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Animated.View
|
|
86
|
+
style={{
|
|
87
|
+
width: diameter,
|
|
88
|
+
height: diameter,
|
|
89
|
+
borderRadius: diameter / 2,
|
|
90
|
+
borderWidth: Math.max(2, diameter / 10),
|
|
91
|
+
borderColor: color + '33',
|
|
92
|
+
borderTopColor: color,
|
|
93
|
+
transform: [{ rotate: spin.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }) }],
|
|
94
|
+
}}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
RingIndicator.displayName = 'RingIndicator';
|
|
99
|
+
|
|
100
|
+
export const Spinner = memo(
|
|
101
|
+
({ size = 'large', color, text, fullscreen = true, variant = 'native', indicator, testID }: SpinnerProps) => {
|
|
102
|
+
const colors = useColors();
|
|
103
|
+
const styles = useStyles();
|
|
104
|
+
const allowFontScaling = useAllowFontScaling();
|
|
105
|
+
const resolvedColor = color || colors.primary;
|
|
106
|
+
const scale = size === 'small' ? 8 : 12;
|
|
107
|
+
|
|
108
|
+
const renderIndicator = () => {
|
|
109
|
+
if (indicator) return indicator;
|
|
110
|
+
if (variant === 'dots') return <DotsIndicator color={resolvedColor} dotSize={scale} />;
|
|
111
|
+
if (variant === 'ring') return <RingIndicator color={resolvedColor} diameter={scale * 3} />;
|
|
112
|
+
return <ActivityIndicator size={size} color={resolvedColor} />;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<View style={[styles.container, fullscreen && { flex: 1 }]} testID={testID}>
|
|
117
|
+
{renderIndicator()}
|
|
118
|
+
{text && (
|
|
119
|
+
<Text allowFontScaling={allowFontScaling} style={styles.text}>
|
|
120
|
+
{text}
|
|
121
|
+
</Text>
|
|
122
|
+
)}
|
|
123
|
+
</View>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
Spinner.displayName = 'Spinner';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Text as RNText, type StyleProp, type TextProps, type TextStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
export interface TypographyProps extends TextProps {
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
style?: StyleProp<TextStyle>;
|
|
9
|
+
color?: string;
|
|
10
|
+
align?: 'left' | 'center' | 'right';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const useStyles = createStyles((theme) => ({
|
|
14
|
+
title: {
|
|
15
|
+
fontSize: theme.tokens.fontSize.xxl,
|
|
16
|
+
fontWeight: theme.tokens.fontWeight.bold,
|
|
17
|
+
letterSpacing: -0.5,
|
|
18
|
+
color: theme.colors.text,
|
|
19
|
+
},
|
|
20
|
+
subtitle: {
|
|
21
|
+
fontSize: theme.tokens.fontSize.lg,
|
|
22
|
+
fontWeight: theme.tokens.fontWeight.medium,
|
|
23
|
+
color: theme.colors.text,
|
|
24
|
+
},
|
|
25
|
+
body: {
|
|
26
|
+
fontSize: theme.tokens.fontSize.md,
|
|
27
|
+
lineHeight: theme.tokens.lineHeight.md,
|
|
28
|
+
color: theme.colors.text,
|
|
29
|
+
},
|
|
30
|
+
caption: {
|
|
31
|
+
fontSize: theme.tokens.fontSize.sm,
|
|
32
|
+
color: theme.colors.textLight,
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
function makeVariant(variant: keyof ReturnType<typeof useStyles>, defaultColor: 'text' | 'textLight') {
|
|
37
|
+
const Component = memo(({ children, style, color, align = 'left', ...props }: TypographyProps) => {
|
|
38
|
+
const styles = useStyles();
|
|
39
|
+
const colors = useColors();
|
|
40
|
+
const allowFontScaling = useAllowFontScaling();
|
|
41
|
+
return (
|
|
42
|
+
<RNText
|
|
43
|
+
allowFontScaling={allowFontScaling}
|
|
44
|
+
style={[styles[variant], { color: color || colors[defaultColor], textAlign: align }, style]}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</RNText>
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
Component.displayName = variant;
|
|
52
|
+
return Component;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const Title = makeVariant('title', 'text');
|
|
56
|
+
export const Subtitle = makeVariant('subtitle', 'text');
|
|
57
|
+
export const Body = makeVariant('body', 'text');
|
|
58
|
+
export const Caption = makeVariant('caption', 'textLight');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export { Title, Subtitle, Body, Caption } from './Text';
|
|
2
|
+
export type { TypographyProps } from './Text';
|
|
3
|
+
export { Button } from './Button';
|
|
4
|
+
export type { ButtonProps, ButtonVariant, ButtonSize } from './Button';
|
|
5
|
+
export { IconButton } from './IconButton';
|
|
6
|
+
export type { IconButtonProps } from './IconButton';
|
|
7
|
+
export { Input } from './Input';
|
|
8
|
+
export type { InputProps } from './Input';
|
|
9
|
+
export { Checkbox } from './Checkbox';
|
|
10
|
+
export type { CheckboxProps } from './Checkbox';
|
|
11
|
+
export { Badge } from './Badge';
|
|
12
|
+
export type { BadgeProps, BadgeVariant } from './Badge';
|
|
13
|
+
export { Card } from './Card';
|
|
14
|
+
export type { CardProps } from './Card';
|
|
15
|
+
export { Divider } from './Divider';
|
|
16
|
+
export type { DividerProps } from './Divider';
|
|
17
|
+
export { Avatar } from './Avatar';
|
|
18
|
+
export type { AvatarProps } from './Avatar';
|
|
19
|
+
export { Image, prefetchImage } from './Image';
|
|
20
|
+
export type { ImageProps } from './Image';
|
|
21
|
+
export { Spinner } from './Spinner';
|
|
22
|
+
export type { SpinnerProps, SpinnerVariant } from './Spinner';
|
|
23
|
+
export { EmptyState } from './EmptyState';
|
|
24
|
+
export type { EmptyStateProps } from './EmptyState';
|
|
25
|
+
export { KeyValueRow } from './KeyValueRow';
|
|
26
|
+
export type { KeyValueRowProps } from './KeyValueRow';
|
|
27
|
+
export { Fab } from './Fab';
|
|
28
|
+
export type { FabProps } from './Fab';
|
|
29
|
+
export { PressableScale } from './PressableScale';
|
|
30
|
+
export type { PressableScaleProps } from './PressableScale';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useColors, useIcons } from '../theme/UIProvider';
|
|
4
|
+
import type { IconName } from './types';
|
|
5
|
+
|
|
6
|
+
export interface IconProps {
|
|
7
|
+
name: IconName;
|
|
8
|
+
size?: number;
|
|
9
|
+
color?: string;
|
|
10
|
+
style?: StyleProp<ViewStyle>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Icon
|
|
15
|
+
* Resuelve el glifo vía el renderer activo (UIProvider.icons, default zero-dep
|
|
16
|
+
* si no se configuró ninguno). Uso interno de la librería — los componentes
|
|
17
|
+
* públicos exponen `icon?: ReactNode` además de esto, para que el consumidor
|
|
18
|
+
* pueda inyectar cualquier ícono sin pasar por el sistema de nombres.
|
|
19
|
+
*/
|
|
20
|
+
export const Icon = memo(({ name, size = 20, color, style }: IconProps) => {
|
|
21
|
+
const colors = useColors();
|
|
22
|
+
const render = useIcons();
|
|
23
|
+
const glyph = render(name, { size, color: color ?? colors.text });
|
|
24
|
+
if (!style) return <>{glyph}</>;
|
|
25
|
+
return <View style={style}>{glyph}</View>;
|
|
26
|
+
});
|
|
27
|
+
Icon.displayName = 'Icon';
|