orn-ui 0.2.9 → 0.3.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/AGENTS.md +67 -52
- package/README.md +5 -9
- package/lib/commonjs/atoms/Avatar.js +77 -5
- package/lib/commonjs/atoms/Avatar.js.map +1 -1
- package/lib/commonjs/atoms/Badge.js +17 -8
- package/lib/commonjs/atoms/Badge.js.map +1 -1
- package/lib/commonjs/atoms/Button.js +108 -39
- package/lib/commonjs/atoms/Button.js.map +1 -1
- package/lib/commonjs/atoms/Card.js +24 -2
- package/lib/commonjs/atoms/Card.js.map +1 -1
- package/lib/commonjs/atoms/Checkbox.js +41 -10
- package/lib/commonjs/atoms/Checkbox.js.map +1 -1
- package/lib/commonjs/atoms/Divider.js +30 -5
- package/lib/commonjs/atoms/Divider.js.map +1 -1
- package/lib/commonjs/atoms/EmptyState.js +58 -20
- package/lib/commonjs/atoms/EmptyState.js.map +1 -1
- package/lib/commonjs/atoms/Fab.js +32 -12
- package/lib/commonjs/atoms/Fab.js.map +1 -1
- package/lib/commonjs/atoms/Gradient.js +102 -36
- package/lib/commonjs/atoms/Gradient.js.map +1 -1
- package/lib/commonjs/atoms/IconButton.js +25 -4
- package/lib/commonjs/atoms/IconButton.js.map +1 -1
- package/lib/commonjs/atoms/Image.js +61 -25
- package/lib/commonjs/atoms/Image.js.map +1 -1
- package/lib/commonjs/atoms/Input.js +105 -17
- package/lib/commonjs/atoms/Input.js.map +1 -1
- package/lib/commonjs/atoms/KeyValueRow.js +37 -9
- package/lib/commonjs/atoms/KeyValueRow.js.map +1 -1
- package/lib/commonjs/atoms/PressableScale.js +25 -5
- package/lib/commonjs/atoms/PressableScale.js.map +1 -1
- package/lib/commonjs/atoms/ShinyText.js +29 -29
- package/lib/commonjs/atoms/Skeleton.js +28 -13
- package/lib/commonjs/atoms/Skeleton.js.map +1 -1
- package/lib/commonjs/atoms/Spinner.js +81 -22
- package/lib/commonjs/atoms/Spinner.js.map +1 -1
- package/lib/commonjs/atoms/Text.js +56 -5
- package/lib/commonjs/atoms/Text.js.map +1 -1
- package/lib/commonjs/atoms/Transition.js +46 -43
- package/lib/commonjs/atoms/Transition.js.map +1 -1
- package/lib/commonjs/atoms/index.js +6 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/atoms/inputValidation.js +56 -0
- package/lib/commonjs/atoms/inputValidation.js.map +1 -0
- package/lib/commonjs/icons/Icon.js +4 -4
- package/lib/commonjs/icons/defaultIcons.js +576 -24
- package/lib/commonjs/icons/defaultIcons.js.map +1 -1
- package/lib/commonjs/icons/index.js +6 -0
- package/lib/commonjs/icons/index.js.map +1 -1
- package/lib/commonjs/molecules/AvatarHeader.js +47 -14
- package/lib/commonjs/molecules/AvatarHeader.js.map +1 -1
- package/lib/commonjs/molecules/FormActions.js +25 -9
- package/lib/commonjs/molecules/FormActions.js.map +1 -1
- package/lib/commonjs/molecules/InfoRow.js +30 -14
- package/lib/commonjs/molecules/InfoRow.js.map +1 -1
- package/lib/commonjs/molecules/OptionCard.js +37 -22
- package/lib/commonjs/molecules/OptionCard.js.map +1 -1
- package/lib/commonjs/molecules/SegmentedControl.js +103 -14
- package/lib/commonjs/molecules/SegmentedControl.js.map +1 -1
- package/lib/commonjs/molecules/Stepper.js +61 -16
- package/lib/commonjs/molecules/Stepper.js.map +1 -1
- package/lib/commonjs/molecules/Steps.js +105 -41
- package/lib/commonjs/molecules/Steps.js.map +1 -1
- package/lib/commonjs/molecules/SymmetricGrid.js +26 -18
- package/lib/commonjs/molecules/SymmetricGrid.js.map +1 -1
- package/lib/commonjs/organisms/Alert.js +49 -35
- package/lib/commonjs/organisms/Alert.js.map +1 -1
- package/lib/commonjs/organisms/AlertProvider.js +10 -10
- package/lib/commonjs/organisms/BottomSheet.js +137 -85
- package/lib/commonjs/organisms/BottomSheet.js.map +1 -1
- package/lib/commonjs/organisms/Chart.js +197 -156
- package/lib/commonjs/organisms/Chart.js.map +1 -1
- package/lib/commonjs/organisms/DateField.js +36 -20
- package/lib/commonjs/organisms/DateField.js.map +1 -1
- package/lib/commonjs/organisms/DatePicker.js +56 -33
- package/lib/commonjs/organisms/DatePicker.js.map +1 -1
- package/lib/commonjs/organisms/List.js +62 -34
- package/lib/commonjs/organisms/List.js.map +1 -1
- package/lib/commonjs/organisms/Modal.js +120 -82
- package/lib/commonjs/organisms/Modal.js.map +1 -1
- package/lib/commonjs/organisms/NavigationBar.js +63 -24
- package/lib/commonjs/organisms/NavigationBar.js.map +1 -1
- package/lib/commonjs/organisms/OptionWheel.js +64 -64
- package/lib/commonjs/organisms/OptionWheel.js.map +1 -1
- package/lib/commonjs/organisms/ReorderableList.js +48 -27
- package/lib/commonjs/organisms/ReorderableList.js.map +1 -1
- package/lib/commonjs/organisms/Screen.js +68 -35
- package/lib/commonjs/organisms/Screen.js.map +1 -1
- package/lib/commonjs/organisms/SearchList.js +56 -26
- package/lib/commonjs/organisms/SearchList.js.map +1 -1
- package/lib/commonjs/organisms/Select.js +45 -24
- package/lib/commonjs/organisms/Select.js.map +1 -1
- package/lib/commonjs/organisms/Slides.js +36 -23
- package/lib/commonjs/organisms/Slides.js.map +1 -1
- package/lib/commonjs/organisms/ThemeToggle.js +10 -9
- package/lib/commonjs/organisms/ThemeToggle.js.map +1 -1
- package/lib/commonjs/organisms/Timeline.js +73 -56
- package/lib/commonjs/organisms/Timeline.js.map +1 -1
- package/lib/commonjs/organisms/Toast.js +46 -15
- package/lib/commonjs/organisms/Toast.js.map +1 -1
- package/lib/commonjs/organisms/ToastProvider.js +14 -10
- package/lib/commonjs/organisms/ToastProvider.js.map +1 -1
- package/lib/commonjs/organisms/Wizard.js +40 -15
- package/lib/commonjs/organisms/Wizard.js.map +1 -1
- package/lib/commonjs/organisms/chart/frame.js +25 -25
- package/lib/commonjs/organisms/chart/frame.js.map +1 -1
- package/lib/commonjs/organisms/chart/marks.js +42 -42
- package/lib/commonjs/organisms/chart/math.js +84 -85
- package/lib/commonjs/organisms/chart/math.js.map +1 -1
- package/lib/commonjs/organisms/chart/palette.js +28 -28
- package/lib/commonjs/organisms/imperativeBridge.js +5 -5
- package/lib/commonjs/organisms/imperativeBridge.js.map +1 -1
- package/lib/commonjs/organisms/useSlides.js +53 -21
- package/lib/commonjs/organisms/useSlides.js.map +1 -1
- package/lib/commonjs/safe-area/SafeAreaUIProvider.js +9 -66
- package/lib/commonjs/safe-area/SafeAreaUIProvider.js.map +1 -1
- package/lib/commonjs/safe-area/index.js.map +1 -1
- package/lib/commonjs/theme/KeyboardAvoidance.js +206 -0
- package/lib/commonjs/theme/KeyboardAvoidance.js.map +1 -0
- package/lib/commonjs/theme/NativeOverlay.js +263 -0
- package/lib/commonjs/theme/NativeOverlay.js.map +1 -0
- package/lib/commonjs/theme/UIProvider.js +86 -33
- package/lib/commonjs/theme/UIProvider.js.map +1 -1
- package/lib/commonjs/theme/colors.js +17 -17
- package/lib/commonjs/theme/colors.js.map +1 -1
- package/lib/commonjs/theme/createStyles.js +19 -16
- package/lib/commonjs/theme/createStyles.js.map +1 -1
- package/lib/commonjs/theme/createTheme.js +6 -6
- package/lib/commonjs/theme/index.js +6 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/insets.js +1 -1
- package/lib/commonjs/theme/labels.js +29 -1
- package/lib/commonjs/theme/labels.js.map +1 -1
- package/lib/commonjs/theme/palettes.js +25 -23
- package/lib/commonjs/theme/palettes.js.map +1 -1
- package/lib/commonjs/theme/tokens.js +28 -14
- package/lib/commonjs/theme/tokens.js.map +1 -1
- package/lib/commonjs/theme/useKeyboardField.js +36 -0
- package/lib/commonjs/theme/useKeyboardField.js.map +1 -0
- package/lib/commonjs/theme/useReduceMotion.js +31 -0
- package/lib/commonjs/theme/useReduceMotion.js.map +1 -0
- package/lib/module/atoms/Avatar.js +79 -8
- package/lib/module/atoms/Avatar.js.map +1 -1
- package/lib/module/atoms/Badge.js +17 -8
- package/lib/module/atoms/Badge.js.map +1 -1
- package/lib/module/atoms/Button.js +110 -41
- package/lib/module/atoms/Button.js.map +1 -1
- package/lib/module/atoms/Card.js +24 -2
- package/lib/module/atoms/Card.js.map +1 -1
- package/lib/module/atoms/Checkbox.js +42 -11
- package/lib/module/atoms/Checkbox.js.map +1 -1
- package/lib/module/atoms/Divider.js +31 -6
- package/lib/module/atoms/Divider.js.map +1 -1
- package/lib/module/atoms/EmptyState.js +58 -20
- package/lib/module/atoms/EmptyState.js.map +1 -1
- package/lib/module/atoms/Fab.js +33 -13
- package/lib/module/atoms/Fab.js.map +1 -1
- package/lib/module/atoms/Gradient.js +103 -37
- package/lib/module/atoms/Gradient.js.map +1 -1
- package/lib/module/atoms/IconButton.js +25 -4
- package/lib/module/atoms/IconButton.js.map +1 -1
- package/lib/module/atoms/Image.js +61 -25
- package/lib/module/atoms/Image.js.map +1 -1
- package/lib/module/atoms/Input.js +108 -20
- package/lib/module/atoms/Input.js.map +1 -1
- package/lib/module/atoms/KeyValueRow.js +37 -9
- package/lib/module/atoms/KeyValueRow.js.map +1 -1
- package/lib/module/atoms/PressableScale.js +25 -5
- package/lib/module/atoms/PressableScale.js.map +1 -1
- package/lib/module/atoms/ShinyText.js +29 -29
- package/lib/module/atoms/Skeleton.js +30 -13
- package/lib/module/atoms/Skeleton.js.map +1 -1
- package/lib/module/atoms/Spinner.js +82 -23
- package/lib/module/atoms/Spinner.js.map +1 -1
- package/lib/module/atoms/Text.js +56 -5
- package/lib/module/atoms/Text.js.map +1 -1
- package/lib/module/atoms/Transition.js +41 -42
- package/lib/module/atoms/Transition.js.map +1 -1
- package/lib/module/atoms/index.js +1 -1
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/atoms/inputValidation.js +52 -0
- package/lib/module/atoms/inputValidation.js.map +1 -0
- package/lib/module/icons/Icon.js +4 -4
- package/lib/module/icons/defaultIcons.js +574 -24
- package/lib/module/icons/defaultIcons.js.map +1 -1
- package/lib/module/icons/index.js +1 -1
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/molecules/AvatarHeader.js +47 -14
- package/lib/module/molecules/AvatarHeader.js.map +1 -1
- package/lib/module/molecules/FormActions.js +25 -9
- package/lib/module/molecules/FormActions.js.map +1 -1
- package/lib/module/molecules/InfoRow.js +30 -14
- package/lib/module/molecules/InfoRow.js.map +1 -1
- package/lib/module/molecules/OptionCard.js +37 -22
- package/lib/module/molecules/OptionCard.js.map +1 -1
- package/lib/module/molecules/SegmentedControl.js +107 -18
- package/lib/module/molecules/SegmentedControl.js.map +1 -1
- package/lib/module/molecules/Stepper.js +63 -18
- package/lib/module/molecules/Stepper.js.map +1 -1
- package/lib/module/molecules/Steps.js +107 -43
- package/lib/module/molecules/Steps.js.map +1 -1
- package/lib/module/molecules/SymmetricGrid.js +26 -18
- package/lib/module/molecules/SymmetricGrid.js.map +1 -1
- package/lib/module/organisms/Alert.js +51 -37
- package/lib/module/organisms/Alert.js.map +1 -1
- package/lib/module/organisms/AlertProvider.js +10 -10
- package/lib/module/organisms/BottomSheet.js +142 -88
- package/lib/module/organisms/BottomSheet.js.map +1 -1
- package/lib/module/organisms/Chart.js +199 -158
- package/lib/module/organisms/Chart.js.map +1 -1
- package/lib/module/organisms/DateField.js +36 -20
- package/lib/module/organisms/DateField.js.map +1 -1
- package/lib/module/organisms/DatePicker.js +56 -33
- package/lib/module/organisms/DatePicker.js.map +1 -1
- package/lib/module/organisms/List.js +62 -34
- package/lib/module/organisms/List.js.map +1 -1
- package/lib/module/organisms/Modal.js +122 -84
- package/lib/module/organisms/Modal.js.map +1 -1
- package/lib/module/organisms/NavigationBar.js +64 -25
- package/lib/module/organisms/NavigationBar.js.map +1 -1
- package/lib/module/organisms/OptionWheel.js +64 -64
- package/lib/module/organisms/OptionWheel.js.map +1 -1
- package/lib/module/organisms/ReorderableList.js +48 -27
- package/lib/module/organisms/ReorderableList.js.map +1 -1
- package/lib/module/organisms/Screen.js +70 -37
- package/lib/module/organisms/Screen.js.map +1 -1
- package/lib/module/organisms/SearchList.js +56 -26
- package/lib/module/organisms/SearchList.js.map +1 -1
- package/lib/module/organisms/Select.js +46 -25
- package/lib/module/organisms/Select.js.map +1 -1
- package/lib/module/organisms/Slides.js +37 -24
- package/lib/module/organisms/Slides.js.map +1 -1
- package/lib/module/organisms/ThemeToggle.js +11 -10
- package/lib/module/organisms/ThemeToggle.js.map +1 -1
- package/lib/module/organisms/Timeline.js +73 -56
- package/lib/module/organisms/Timeline.js.map +1 -1
- package/lib/module/organisms/Toast.js +47 -16
- package/lib/module/organisms/Toast.js.map +1 -1
- package/lib/module/organisms/ToastProvider.js +15 -11
- package/lib/module/organisms/ToastProvider.js.map +1 -1
- package/lib/module/organisms/Wizard.js +41 -16
- package/lib/module/organisms/Wizard.js.map +1 -1
- package/lib/module/organisms/chart/frame.js +25 -25
- package/lib/module/organisms/chart/frame.js.map +1 -1
- package/lib/module/organisms/chart/marks.js +42 -42
- package/lib/module/organisms/chart/math.js +84 -85
- package/lib/module/organisms/chart/math.js.map +1 -1
- package/lib/module/organisms/chart/palette.js +28 -28
- package/lib/module/organisms/imperativeBridge.js +5 -5
- package/lib/module/organisms/imperativeBridge.js.map +1 -1
- package/lib/module/organisms/useSlides.js +53 -21
- package/lib/module/organisms/useSlides.js.map +1 -1
- package/lib/module/safe-area/SafeAreaUIProvider.js +10 -65
- package/lib/module/safe-area/SafeAreaUIProvider.js.map +1 -1
- package/lib/module/safe-area/index.js +1 -0
- package/lib/module/safe-area/index.js.map +1 -1
- package/lib/module/theme/KeyboardAvoidance.js +198 -0
- package/lib/module/theme/KeyboardAvoidance.js.map +1 -0
- package/lib/module/theme/NativeOverlay.js +257 -0
- package/lib/module/theme/NativeOverlay.js.map +1 -0
- package/lib/module/theme/UIProvider.js +88 -35
- package/lib/module/theme/UIProvider.js.map +1 -1
- package/lib/module/theme/colors.js +17 -17
- package/lib/module/theme/colors.js.map +1 -1
- package/lib/module/theme/createStyles.js +20 -16
- package/lib/module/theme/createStyles.js.map +1 -1
- package/lib/module/theme/createTheme.js +6 -6
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/insets.js +1 -1
- package/lib/module/theme/labels.js +29 -1
- package/lib/module/theme/labels.js.map +1 -1
- package/lib/module/theme/palettes.js +25 -23
- package/lib/module/theme/palettes.js.map +1 -1
- package/lib/module/theme/tokens.js +27 -13
- package/lib/module/theme/tokens.js.map +1 -1
- package/lib/module/theme/useKeyboardField.js +33 -0
- package/lib/module/theme/useKeyboardField.js.map +1 -0
- package/lib/module/theme/useReduceMotion.js +28 -0
- package/lib/module/theme/useReduceMotion.js.map +1 -0
- package/lib/typescript/atoms/Avatar.d.ts +35 -5
- package/lib/typescript/atoms/Avatar.d.ts.map +1 -1
- package/lib/typescript/atoms/Badge.d.ts +14 -3
- package/lib/typescript/atoms/Badge.d.ts.map +1 -1
- package/lib/typescript/atoms/Button.d.ts +34 -3
- package/lib/typescript/atoms/Button.d.ts.map +1 -1
- package/lib/typescript/atoms/Card.d.ts +13 -2
- package/lib/typescript/atoms/Card.d.ts.map +1 -1
- package/lib/typescript/atoms/Checkbox.d.ts +26 -1
- package/lib/typescript/atoms/Checkbox.d.ts.map +1 -1
- package/lib/typescript/atoms/Divider.d.ts +18 -1
- package/lib/typescript/atoms/Divider.d.ts.map +1 -1
- package/lib/typescript/atoms/EmptyState.d.ts +30 -3
- package/lib/typescript/atoms/EmptyState.d.ts.map +1 -1
- package/lib/typescript/atoms/Fab.d.ts +24 -4
- package/lib/typescript/atoms/Fab.d.ts.map +1 -1
- package/lib/typescript/atoms/Gradient.d.ts +21 -15
- package/lib/typescript/atoms/Gradient.d.ts.map +1 -1
- package/lib/typescript/atoms/IconButton.d.ts +18 -4
- package/lib/typescript/atoms/IconButton.d.ts.map +1 -1
- package/lib/typescript/atoms/Image.d.ts +39 -17
- package/lib/typescript/atoms/Image.d.ts.map +1 -1
- package/lib/typescript/atoms/Input.d.ts +57 -7
- package/lib/typescript/atoms/Input.d.ts.map +1 -1
- package/lib/typescript/atoms/KeyValueRow.d.ts +16 -4
- package/lib/typescript/atoms/KeyValueRow.d.ts.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +12 -5
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/ShinyText.d.ts +17 -17
- package/lib/typescript/atoms/ShinyText.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +12 -7
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
- package/lib/typescript/atoms/Spinner.d.ts +29 -1
- package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
- package/lib/typescript/atoms/Text.d.ts +46 -0
- package/lib/typescript/atoms/Text.d.ts.map +1 -1
- package/lib/typescript/atoms/Transition.d.ts +19 -15
- package/lib/typescript/atoms/Transition.d.ts.map +1 -1
- package/lib/typescript/atoms/index.d.ts +3 -3
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/atoms/inputValidation.d.ts +29 -0
- package/lib/typescript/atoms/inputValidation.d.ts.map +1 -0
- package/lib/typescript/icons/Icon.d.ts +4 -4
- package/lib/typescript/icons/defaultIcons.d.ts +19 -2
- package/lib/typescript/icons/defaultIcons.d.ts.map +1 -1
- package/lib/typescript/icons/index.d.ts +2 -2
- package/lib/typescript/icons/index.d.ts.map +1 -1
- package/lib/typescript/icons/types.d.ts +13 -3
- package/lib/typescript/icons/types.d.ts.map +1 -1
- package/lib/typescript/molecules/AvatarHeader.d.ts +23 -4
- package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
- package/lib/typescript/molecules/FormActions.d.ts +21 -4
- package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
- package/lib/typescript/molecules/InfoRow.d.ts +17 -3
- package/lib/typescript/molecules/InfoRow.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +20 -5
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/SegmentedControl.d.ts +18 -2
- package/lib/typescript/molecules/SegmentedControl.d.ts.map +1 -1
- package/lib/typescript/molecules/Stepper.d.ts +23 -7
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/Steps.d.ts +18 -4
- package/lib/typescript/molecules/Steps.d.ts.map +1 -1
- package/lib/typescript/molecules/SymmetricGrid.d.ts +15 -5
- package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
- package/lib/typescript/organisms/Alert.d.ts +28 -2
- package/lib/typescript/organisms/Alert.d.ts.map +1 -1
- package/lib/typescript/organisms/AlertProvider.d.ts +10 -10
- package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +20 -5
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/Chart.d.ts +86 -75
- package/lib/typescript/organisms/Chart.d.ts.map +1 -1
- package/lib/typescript/organisms/DateField.d.ts +17 -3
- package/lib/typescript/organisms/DateField.d.ts.map +1 -1
- package/lib/typescript/organisms/DatePicker.d.ts +7 -11
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +30 -3
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/Modal.d.ts +30 -3
- package/lib/typescript/organisms/Modal.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +26 -7
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
- package/lib/typescript/organisms/OptionWheel.d.ts +20 -16
- package/lib/typescript/organisms/OptionWheel.d.ts.map +1 -1
- package/lib/typescript/organisms/ReorderableList.d.ts +20 -11
- package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
- package/lib/typescript/organisms/Screen.d.ts +27 -8
- package/lib/typescript/organisms/Screen.d.ts.map +1 -1
- package/lib/typescript/organisms/SearchList.d.ts +46 -3
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +26 -6
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/organisms/Slides.d.ts +44 -26
- package/lib/typescript/organisms/Slides.d.ts.map +1 -1
- package/lib/typescript/organisms/ThemeToggle.d.ts +6 -2
- package/lib/typescript/organisms/ThemeToggle.d.ts.map +1 -1
- package/lib/typescript/organisms/Timeline.d.ts +26 -9
- package/lib/typescript/organisms/Timeline.d.ts.map +1 -1
- package/lib/typescript/organisms/Toast.d.ts +19 -4
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +8 -8
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +24 -6
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/organisms/chart/frame.d.ts +21 -21
- package/lib/typescript/organisms/chart/frame.d.ts.map +1 -1
- package/lib/typescript/organisms/chart/marks.d.ts +28 -28
- package/lib/typescript/organisms/chart/marks.d.ts.map +1 -1
- package/lib/typescript/organisms/chart/math.d.ts +69 -70
- package/lib/typescript/organisms/chart/math.d.ts.map +1 -1
- package/lib/typescript/organisms/chart/palette.d.ts +2 -2
- package/lib/typescript/organisms/chart/palette.d.ts.map +1 -1
- package/lib/typescript/organisms/imperativeBridge.d.ts +4 -4
- package/lib/typescript/organisms/useSlides.d.ts +8 -16
- package/lib/typescript/organisms/useSlides.d.ts.map +1 -1
- package/lib/typescript/safe-area/SafeAreaUIProvider.d.ts +9 -7
- package/lib/typescript/safe-area/SafeAreaUIProvider.d.ts.map +1 -1
- package/lib/typescript/safe-area/index.d.ts.map +1 -1
- package/lib/typescript/theme/KeyboardAvoidance.d.ts +62 -0
- package/lib/typescript/theme/KeyboardAvoidance.d.ts.map +1 -0
- package/lib/typescript/theme/NativeOverlay.d.ts +43 -0
- package/lib/typescript/theme/NativeOverlay.d.ts.map +1 -0
- package/lib/typescript/theme/UIProvider.d.ts +38 -20
- package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
- package/lib/typescript/theme/colors.d.ts +13 -13
- package/lib/typescript/theme/colors.d.ts.map +1 -1
- package/lib/typescript/theme/createStyles.d.ts +20 -14
- package/lib/typescript/theme/createStyles.d.ts.map +1 -1
- package/lib/typescript/theme/createTheme.d.ts +5 -5
- package/lib/typescript/theme/index.d.ts +1 -1
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/theme/insets.d.ts +1 -1
- package/lib/typescript/theme/insets.d.ts.map +1 -1
- package/lib/typescript/theme/labels.d.ts +57 -2
- package/lib/typescript/theme/labels.d.ts.map +1 -1
- package/lib/typescript/theme/palettes.d.ts +25 -19
- package/lib/typescript/theme/palettes.d.ts.map +1 -1
- package/lib/typescript/theme/tokens.d.ts +15 -8
- package/lib/typescript/theme/tokens.d.ts.map +1 -1
- package/lib/typescript/theme/types.d.ts +7 -7
- package/lib/typescript/theme/types.d.ts.map +1 -1
- package/lib/typescript/theme/useKeyboardField.d.ts +17 -0
- package/lib/typescript/theme/useKeyboardField.d.ts.map +1 -0
- package/lib/typescript/theme/useReduceMotion.d.ts +7 -0
- package/lib/typescript/theme/useReduceMotion.d.ts.map +1 -0
- package/llms.txt +4 -4
- package/package.json +15 -14
- package/registry/alert-provider.json +4 -3
- package/registry/alert.json +3 -2
- package/registry/avatar-header.json +1 -1
- package/registry/avatar.json +3 -2
- package/registry/badge.json +1 -1
- package/registry/bottom-sheet.json +1 -1
- package/registry/button.json +3 -2
- package/registry/card.json +1 -1
- package/registry/chart.json +5 -5
- package/registry/checkbox.json +3 -2
- package/registry/core.json +31 -15
- package/registry/date-field.json +1 -1
- package/registry/date-picker.json +3 -2
- package/registry/divider.json +1 -1
- package/registry/empty-state.json +4 -2
- package/registry/fab.json +3 -2
- package/registry/form-actions.json +3 -2
- package/registry/gradient.json +1 -1
- package/registry/icon-button.json +3 -2
- package/registry/image.json +1 -1
- package/registry/index.json +79 -61
- package/registry/info-row.json +1 -1
- package/registry/input.json +5 -1
- package/registry/key-value-row.json +1 -1
- package/registry/list.json +3 -1
- package/registry/manifest.json +3 -1
- package/registry/modal.json +1 -1
- package/registry/navigation-bar.json +1 -1
- package/registry/option-card.json +3 -2
- package/registry/option-wheel.json +1 -1
- package/registry/pressable-scale.json +3 -2
- package/registry/reorderable-list.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +7 -1
- package/registry/segmented-control.json +1 -1
- package/registry/select.json +1 -1
- package/registry/shiny-text.json +1 -1
- package/registry/skeleton.json +3 -2
- package/registry/slides.json +2 -2
- package/registry/spinner.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/steps.json +1 -1
- package/registry/symmetric-grid.json +1 -1
- package/registry/theme-toggle.json +1 -1
- package/registry/timeline.json +1 -1
- package/registry/title.json +1 -1
- package/registry/toast-provider.json +2 -2
- package/registry/toast.json +1 -1
- package/registry/transition.json +1 -1
- package/registry/wizard.json +1 -1
- package/src/atoms/Avatar.tsx +117 -26
- package/src/atoms/Badge.tsx +20 -9
- package/src/atoms/Button.tsx +102 -30
- package/src/atoms/Card.tsx +23 -3
- package/src/atoms/Checkbox.tsx +49 -12
- package/src/atoms/Divider.tsx +34 -4
- package/src/atoms/EmptyState.tsx +63 -16
- package/src/atoms/Fab.tsx +56 -9
- package/src/atoms/Gradient.tsx +92 -41
- package/src/atoms/IconButton.tsx +27 -4
- package/src/atoms/Image.tsx +74 -28
- package/src/atoms/Input.tsx +129 -19
- package/src/atoms/KeyValueRow.tsx +72 -28
- package/src/atoms/PressableScale.tsx +31 -7
- package/src/atoms/ShinyText.tsx +31 -31
- package/src/atoms/Skeleton.tsx +37 -17
- package/src/atoms/Spinner.tsx +87 -24
- package/src/atoms/Text.tsx +66 -6
- package/src/atoms/Transition.tsx +40 -44
- package/src/atoms/index.ts +3 -3
- package/src/atoms/inputValidation.ts +59 -0
- package/src/icons/Icon.tsx +4 -4
- package/src/icons/defaultIcons.tsx +445 -22
- package/src/icons/index.ts +2 -2
- package/src/icons/types.ts +26 -4
- package/src/molecules/AvatarHeader.tsx +88 -34
- package/src/molecules/FormActions.tsx +36 -6
- package/src/molecules/InfoRow.tsx +39 -12
- package/src/molecules/OptionCard.tsx +55 -20
- package/src/molecules/SegmentedControl.tsx +135 -15
- package/src/molecules/Stepper.tsx +73 -19
- package/src/molecules/Steps.tsx +121 -39
- package/src/molecules/SymmetricGrid.tsx +28 -12
- package/src/organisms/Alert.tsx +97 -26
- package/src/organisms/AlertProvider.tsx +13 -13
- package/src/organisms/BottomSheet.tsx +115 -43
- package/src/organisms/Chart.tsx +223 -165
- package/src/organisms/DateField.tsx +40 -15
- package/src/organisms/DatePicker.tsx +53 -32
- package/src/organisms/List.tsx +64 -15
- package/src/organisms/Modal.tsx +122 -63
- package/src/organisms/NavigationBar.tsx +73 -23
- package/src/organisms/OptionWheel.tsx +70 -66
- package/src/organisms/ReorderableList.tsx +49 -27
- package/src/organisms/Screen.tsx +79 -42
- package/src/organisms/SearchList.tsx +84 -20
- package/src/organisms/Select.tsx +63 -29
- package/src/organisms/Slides.tsx +63 -44
- package/src/organisms/ThemeToggle.tsx +15 -6
- package/src/organisms/Timeline.tsx +79 -62
- package/src/organisms/Toast.tsx +50 -15
- package/src/organisms/ToastProvider.tsx +17 -13
- package/src/organisms/Wizard.tsx +58 -21
- package/src/organisms/chart/frame.tsx +35 -35
- package/src/organisms/chart/marks.tsx +53 -53
- package/src/organisms/chart/math.ts +93 -94
- package/src/organisms/chart/palette.ts +28 -28
- package/src/organisms/imperativeBridge.ts +5 -5
- package/src/organisms/useSlides.ts +66 -31
- package/src/safe-area/SafeAreaUIProvider.tsx +9 -70
- package/src/safe-area/index.ts +1 -0
- package/src/theme/KeyboardAvoidance.tsx +216 -0
- package/src/theme/NativeOverlay.tsx +269 -0
- package/src/theme/UIProvider.tsx +109 -46
- package/src/theme/colors.ts +21 -21
- package/src/theme/createStyles.ts +23 -19
- package/src/theme/createTheme.ts +6 -6
- package/src/theme/index.ts +1 -1
- package/src/theme/insets.ts +1 -1
- package/src/theme/labels.ts +85 -2
- package/src/theme/palettes.ts +33 -25
- package/src/theme/tokens.ts +27 -13
- package/src/theme/types.ts +7 -7
- package/src/theme/useKeyboardField.ts +33 -0
- package/src/theme/useReduceMotion.ts +27 -0
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"files": [
|
|
14
14
|
{
|
|
15
15
|
"path": "organisms/ToastProvider.tsx",
|
|
16
|
-
"content": "import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { View } from 'react-native';\nimport { AnimatedToast, Toast, type ToastVariant } from './Toast';\nimport { useInsets } from '../theme/UIProvider';\nimport { createStyles } from '../theme/createStyles';\nimport { createImperativeBridge } from './imperativeBridge';\n\nexport interface ToastOptions {\n title: string;\n message?: string;\n variant?: ToastVariant;\n /**
|
|
16
|
+
"content": "import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { AccessibilityInfo, View } from 'react-native';\nimport { AnimatedToast, Toast, type ToastVariant } from './Toast';\nimport { useInsets } from '../theme/UIProvider';\nimport { createStyles } from '../theme/createStyles';\nimport { createImperativeBridge } from './imperativeBridge';\n\nexport interface ToastOptions {\n title: string;\n message?: string;\n variant?: ToastVariant;\n /** Milliseconds before auto-dismiss. 0 = never dismisses on its own. @default 4000 */\n duration?: number;\n onPress?: () => void;\n hideCloseButton?: boolean;\n}\n\ninterface ToastEntry extends ToastOptions {\n id: string;\n}\n\ninterface ToastContextValue {\n /** Shows a toast and returns its id (to close it early if needed). */\n show: (options: ToastOptions) => string;\n hide: (id: string) => void;\n hideAll: () => void;\n}\n\nconst ToastContext = createContext<ToastContextValue | undefined>(undefined);\nconst bridge = createImperativeBridge<ToastContextValue>('ToastProvider');\n\n/**\n * Shows a toast from outside the React tree —a service, an interceptor—\n * without going through the hook. Returns the id, or '' if there is no\n * mounted <ToastProvider>.\n */\nexport function showToast(options: ToastOptions): string {\n return bridge.get()?.show(options) ?? '';\n}\n\nexport function hideToast(id: string): void {\n bridge.get()?.hide(id);\n}\n\nexport function hideAllToasts(): void {\n bridge.get()?.hideAll();\n}\n\nconst useStyles = createStyles((theme) => ({\n host: { position: 'absolute', left: 0, right: 0, paddingHorizontal: theme.tokens.spacing.lg, gap: theme.tokens.spacing.sm },\n}));\n\nexport interface ToastProviderProps {\n children: React.ReactNode;\n /** @default 'top' */\n position?: 'top' | 'bottom';\n /** Maximum simultaneous toasts; the oldest ones are dropped. @default 3 */\n maxVisible?: number;\n /** Default duration in ms. @default 4000 */\n defaultDuration?: number;\n}\n\n/**\n * ToastProvider\n * Mounts the toast layer above the rest of the app. Place it once near the\n * root, inside UIProvider. The auto-dismiss timers are cleared on unmount and\n * when each toast closes, so no callbacks are left hanging.\n */\nexport function ToastProvider({\n children,\n position = 'top',\n maxVisible = 3,\n defaultDuration = 4000,\n}: ToastProviderProps) {\n const [toasts, setToasts] = useState<ToastEntry[]>([]);\n const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());\n const counter = useRef(0);\n const insets = useInsets();\n const styles = useStyles();\n\n const clearTimer = useCallback((id: string) => {\n const t = timers.current.get(id);\n if (t) {\n clearTimeout(t);\n timers.current.delete(id);\n }\n }, []);\n\n const hide = useCallback(\n (id: string) => {\n clearTimer(id);\n setToasts((prev) => prev.filter((t) => t.id !== id));\n },\n [clearTimer]\n );\n\n const hideAll = useCallback(() => {\n timers.current.forEach((t) => clearTimeout(t));\n timers.current.clear();\n setToasts([]);\n }, []);\n\n const show = useCallback(\n (options: ToastOptions) => {\n counter.current += 1;\n const id = `toast-${counter.current}`;\n const duration = options.duration ?? defaultDuration;\n\n setToasts((prev) => {\n const next = [...prev, { ...options, id }];\n // The oldest are dropped, not the newcomers: a new toast is always\n // visible.\n const overflow = next.length - maxVisible;\n if (overflow > 0) {\n next.slice(0, overflow).forEach((t) => clearTimer(t.id));\n return next.slice(overflow);\n }\n return next;\n });\n\n // It appears without focus: screen readers only hear it if it is\n // announced (iOS ignores accessibilityRole=\"alert\" on its own).\n AccessibilityInfo.announceForAccessibility(options.message ? `${options.title}. ${options.message}` : options.title);\n\n if (duration > 0) {\n timers.current.set(\n id,\n setTimeout(() => hide(id), duration)\n );\n }\n return id;\n },\n [defaultDuration, maxVisible, hide, clearTimer]\n );\n\n // When the provider unmounts no setTimeout must be left pointing at a\n // setState of an already unmounted tree.\n useEffect(() => {\n const pending = timers.current;\n return () => {\n pending.forEach((t) => clearTimeout(t));\n pending.clear();\n };\n }, []);\n\n const value = useMemo<ToastContextValue>(() => ({ show, hide, hideAll }), [show, hide, hideAll]);\n\n useEffect(() => {\n bridge.set(value);\n return () => bridge.set(null);\n }, [value]);\n\n const hostPosition =\n position === 'top'\n ? { top: insets.top + 8 }\n : { bottom: insets.bottom + 8, flexDirection: 'column-reverse' as const };\n\n return (\n <ToastContext.Provider value={value}>\n {children}\n {toasts.length > 0 && (\n <View style={[styles.host, hostPosition]} pointerEvents=\"box-none\" testID=\"toast-host\">\n {toasts.map((t) => (\n <AnimatedToast key={t.id}>\n <Toast\n title={t.title}\n message={t.message}\n variant={t.variant}\n hideCloseButton={t.hideCloseButton}\n onDismiss={() => hide(t.id)}\n onPress={\n t.onPress\n ? () => {\n t.onPress?.();\n hide(t.id);\n }\n : undefined\n }\n />\n </AnimatedToast>\n ))}\n </View>\n )}\n </ToastContext.Provider>\n );\n}\n\nexport function useToast(): ToastContextValue {\n const ctx = useContext(ToastContext);\n if (!ctx) {\n throw new Error('orn-ui: useToast must be used within a <ToastProvider>.');\n }\n return ctx;\n}\n"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "organisms/imperativeBridge.ts",
|
|
20
|
-
"content": "/**\n *
|
|
20
|
+
"content": "/**\n * Bridge between the mounted provider and code that isn't a component\n * (services, API layer, interceptors). The provider registers itself on mount\n * and clears itself on unmount, so outside the tree there is never a live\n * reference left to a dead context.\n */\nexport interface ImperativeBridge<T> {\n set: (value: T | null) => void;\n get: () => T | null;\n}\n\nexport function createImperativeBridge<T>(providerName: string): ImperativeBridge<T> {\n let current: T | null = null;\n\n return {\n set: (value) => {\n current = value;\n },\n get: () => {\n if (!current && __DEV__) {\n console.warn(\n `orn-ui: the imperative API was called without a mounted <${providerName}>. The call was ignored.`\n );\n }\n return current;\n },\n };\n}\n"
|
|
21
21
|
}
|
|
22
22
|
],
|
|
23
23
|
"registryDependencies": [
|
package/registry/toast.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/Toast.tsx",
|
|
12
|
-
"content": "import React, { memo } from 'react';\nimport { Pressable, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Transition } from '../atoms/Transition';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\n\nexport interface ToastProps {\n title: string;\n message?: string;\n variant?: ToastVariant;\n /** Fires when the toast is tapped (in addition to closing it). */\n onPress?: () => void;\n onDismiss?: () => void;\n /** Hides the close X. The toast still auto-dismisses by timeout. */\n hideCloseButton?: boolean;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nconst ICON_BY_VARIANT: Record<ToastVariant, IconName> = {\n success: 'check',\n error: 'alert',\n warning: 'warning',\n info: 'info',\n};\n\nconst useStyles = createStyles((theme) => ({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n backgroundColor: theme.colors.
|
|
12
|
+
"content": "import React, { memo } from 'react';\nimport { Pressable, Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Transition } from '../atoms/Transition';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport type ToastVariant = 'success' | 'error' | 'warning' | 'info';\n\nexport interface ToastProps {\n /** Main line. */\n title: string;\n /** Optional second line. */\n message?: string;\n /** Sets the icon and the color of the side bar. @default 'info' */\n variant?: ToastVariant;\n /** Fires when the toast is tapped (in addition to closing it). */\n onPress?: () => void;\n /** Shows the close X (unless `hideCloseButton`) and is called when it is pressed. */\n onDismiss?: () => void;\n /** Hides the close X. The toast still auto-dismisses by timeout. */\n hideCloseButton?: boolean;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nconst ICON_BY_VARIANT: Record<ToastVariant, IconName> = {\n success: 'check',\n error: 'alert',\n warning: 'warning',\n info: 'info',\n};\n\n/** Width of the colored bar on the left edge. */\nconst ACCENT_WIDTH = 4;\nconst ICON_SIZE = 22;\nconst CLOSE_ICON_SIZE = 18;\n/** Pads the close glyph out to a 44pt target without growing the toast. */\nconst CLOSE_HIT_SLOP = (44 - CLOSE_ICON_SIZE) / 2;\n/** How far it drops in from above. */\nconst ENTER_DISTANCE = 16;\n\nconst useStyles = createStyles((theme) => ({\n container: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n backgroundColor: theme.colors.surfaceRaised,\n borderRadius: theme.tokens.radius.lg,\n borderWidth: 1,\n borderColor: theme.colors.border,\n paddingVertical: theme.tokens.spacing.md,\n paddingHorizontal: theme.tokens.spacing.lg,\n ...theme.tokens.shadow.lg,\n },\n accent: {\n position: 'absolute',\n left: 0,\n top: 0,\n bottom: 0,\n width: ACCENT_WIDTH,\n borderTopLeftRadius: theme.tokens.radius.lg,\n borderBottomLeftRadius: theme.tokens.radius.lg,\n },\n text: { flex: 1, gap: theme.tokens.spacing.xxs },\n title: {\n fontSize: theme.tokens.fontSize.md,\n fontWeight: theme.tokens.fontWeight.semibold,\n color: theme.colors.text,\n },\n message: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.textLight, lineHeight: theme.tokens.lineHeight.sm },\n}));\n\n/**\n * Toast\n * Ephemeral notification. Normally not used directly: `ToastProvider` +\n * `useToast()` mount and dismiss it for you. It is exposed anyway in case\n * you want to place it yourself inside a specific screen.\n *\n * Design:\n * - Doherty threshold: immediate feedback for an action, without blocking\n * the screen; it leaves on its own.\n * - Similarity: the variant sets both the bar and the icon color, so success,\n * error, warning and info read at a glance (and not by color alone: each\n * has its own glyph).\n * - Figure/ground: a raised surface with a shadow, above the content.\n * - Fitts's law: the close button is a 44pt target.\n * - Screen readers: `ToastProvider` announces each toast as it appears; a\n * pressable toast reads title and message, and offers \"Close\" as an action.\n */\nexport const Toast = memo(\n ({ title, message, variant = 'info', onPress, onDismiss, hideCloseButton, style, testID }: ToastProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const labels = useLabels();\n\n // The side bar is a fill (full accent); the icon is content over the\n // toast's surface and needs the `*Text` role.\n const barColor: Record<ToastVariant, string> = {\n success: colors.success,\n error: colors.error,\n warning: colors.warning,\n info: colors.secondary,\n };\n const iconColor: Record<ToastVariant, string> = {\n success: colors.successText,\n error: colors.errorText,\n warning: colors.warningText,\n info: colors.secondaryText,\n };\n\n const body = (\n <View style={[styles.container, style]} testID={testID} accessibilityRole=\"alert\">\n <View style={[styles.accent, { backgroundColor: barColor[variant] }]} />\n <Icon name={ICON_BY_VARIANT[variant]} size={ICON_SIZE} color={iconColor[variant]} />\n <View style={styles.text}>\n <Text allowFontScaling={allowFontScaling} style={styles.title}>\n {title}\n </Text>\n {!!message && (\n <Text allowFontScaling={allowFontScaling} style={styles.message}>\n {message}\n </Text>\n )}\n </View>\n {!hideCloseButton && !!onDismiss && (\n <Pressable onPress={onDismiss} accessibilityRole=\"button\" accessibilityLabel={labels.close} hitSlop={CLOSE_HIT_SLOP}>\n <Icon name=\"close\" size={CLOSE_ICON_SIZE} color={colors.textLight} />\n </Pressable>\n )}\n </View>\n );\n\n if (!onPress) return body;\n\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={message ? `${title}, ${message}` : title}\n // The close X inside a pressable toast isn't reachable on its own:\n // the reader gets it as an action of the toast.\n accessibilityActions={!hideCloseButton && onDismiss ? [{ name: 'dismiss', label: labels.close }] : undefined}\n onAccessibilityAction={(event) => {\n if (event.nativeEvent.actionName === 'dismiss') onDismiss?.();\n }}\n >\n {body}\n </Pressable>\n );\n }\n);\nToast.displayName = 'Toast';\n\n/** Wraps a Toast with its enter/exit animation. Internal use by the provider. */\nexport const AnimatedToast = memo(\n ({ children, onHeight }: { children: React.ReactNode; onHeight?: (h: number) => void }) => (\n <Transition preset={['fade', 'slide-down']} distance={ENTER_DISTANCE} spring>\n <View onLayout={(e) => onHeight?.(e.nativeEvent.layout.height)}>{children}</View>\n </Transition>\n )\n);\nAnimatedToast.displayName = 'AnimatedToast';\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
package/registry/transition.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
{
|
|
12
12
|
"path": "atoms/Transition.tsx",
|
|
13
|
-
"content": "import React, { memo, useEffect, useMemo, useRef, useState } from 'react';\nimport {
|
|
13
|
+
"content": "import React, { memo, useEffect, useMemo, useRef, useState } from 'react';\nimport { Animated, Easing, type StyleProp, type ViewStyle } from 'react-native';\nimport { useTheme } from '../theme/UIProvider';\nimport { useReduceMotion } from '../theme/useReduceMotion';\n\nexport type TransitionPreset =\n | 'fade'\n | 'scale'\n | 'pop'\n | 'slide-up'\n | 'slide-down'\n | 'slide-left'\n | 'slide-right';\n\nexport interface TransitionProps {\n /** Content that enters and exits. */\n children: React.ReactNode;\n /** Enters when true, exits (and unmounts) when false. @default true */\n visible?: boolean;\n /** One or more: 'fade scale' combines opacity and scale. @default 'fade' */\n preset?: TransitionPreset | TransitionPreset[];\n /** Timing duration in ms. @default tokens.duration.base */\n duration?: number;\n /** ms before starting. A per-index delay staggers a list. @default 0 */\n delay?: number;\n /** Pixels traveled by the slide presets. @default tokens.spacing.xxl (24) */\n distance?: number;\n /** Spring instead of timing (implicit in 'pop'). */\n spring?: boolean;\n /** Also animates the first render. When false it starts already present. @default true */\n appear?: boolean;\n /** Keeps the node mounted on exit, only hides it. @default false */\n keepMounted?: boolean;\n /**\n * 0→1 progress driven from outside. With this, Transition only interpolates:\n * whoever passes it decides when and how it advances (a PanResponder, for example).\n */\n value?: Animated.Value;\n /** Called when the enter animation finishes. Not called when driven by `value`. */\n onEntered?: () => void;\n /** Called when the exit animation finishes, before unmounting. Not called when driven by `value`. */\n onExited?: () => void;\n style?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nconst SCALE_FROM: Record<'scale' | 'pop', number> = { scale: 0.92, pop: 0.8 };\nconst hiddenStyle: ViewStyle = { pointerEvents: 'none' };\n/** 'pop' spring: bounces once and settles (~350 ms), without oscillating. */\nconst POP_SPRING = { friction: 9, tension: 90 } as const;\n\n// Lives in theme/ (UIProvider uses it too); re-exported here so existing\n// imports don't break.\nexport { useReduceMotion };\n\n/**\n * Transition\n * Wraps any tree and animates it on enter and exit. Every preset boils down\n * to `opacity` and `transform` over a single Animated.Value, which is what\n * `useNativeDriver` knows how to send to the native thread: the animation runs\n * there and the JS thread takes part in no frame — that is why there are no\n * height, width or color presets, which would force a return to JS every frame.\n *\n * The node stays mounted until the exit animation finishes, and the behavior\n * is identical on iOS and Android: there is no per-platform branch. While\n * exiting (or hidden with `keepMounted`) it stops receiving touches and leaves\n * the accessibility tree: what is invisible isn't read.\n *\n * Gestalt, common fate: what enters or exits together is perceived as a group;\n * with a per-index `delay` the list enters in reading order (continuity).\n * Respects Reduce Motion: jumps to the final state, callbacks still fire.\n */\nexport const Transition = memo(\n ({\n children,\n visible = true,\n preset = 'fade',\n duration,\n delay = 0,\n distance,\n spring,\n appear = true,\n keepMounted = false,\n value,\n onEntered,\n onExited,\n style,\n testID,\n }: TransitionProps) => {\n const theme = useTheme();\n const travel = distance ?? theme.tokens.spacing.xxl;\n // Resolved here, not inside the effect: depending on `theme` restarted the\n // animation (and fired onEntered again) every time the theme changed.\n const resolvedDuration = duration ?? theme.tokens.duration.base;\n const reduceMotion = useReduceMotion();\n const internal = useRef(new Animated.Value(visible && !appear ? 1 : 0)).current;\n const progress = value ?? internal;\n const driven = value === undefined;\n\n const [mounted, setMounted] = useState(visible || keepMounted);\n\n // The callbacks live in a ref: passed inline they change identity on\n // every render and, as effect dependencies, would restart the animation.\n const callbacks = useRef({ onEntered, onExited });\n useEffect(() => {\n callbacks.current = { onEntered, onExited };\n });\n\n const presetKey = Array.isArray(preset) ? preset.join(' ') : preset;\n const presets = useMemo(() => presetKey.split(' ') as TransitionPreset[], [presetKey]);\n\n useEffect(() => {\n if (visible) setMounted(true);\n }, [visible]);\n\n useEffect(() => {\n if (!driven) return;\n const toValue = visible ? 1 : 0;\n const useSpring = spring ?? presets.includes('pop');\n\n const animation = reduceMotion\n ? Animated.timing(progress, { toValue, duration: 0, delay, useNativeDriver: true })\n : useSpring\n ? Animated.spring(progress, { toValue, delay, ...POP_SPRING, useNativeDriver: true })\n : Animated.timing(progress, {\n toValue,\n delay,\n duration: resolvedDuration,\n easing: visible ? Easing.out(Easing.cubic) : Easing.in(Easing.cubic),\n useNativeDriver: true,\n });\n\n animation.start(({ finished }) => {\n if (!finished) return;\n if (visible) {\n callbacks.current.onEntered?.();\n return;\n }\n callbacks.current.onExited?.();\n if (!keepMounted) setMounted(false);\n });\n\n return () => animation.stop();\n }, [visible, driven, reduceMotion, spring, delay, resolvedDuration, keepMounted, presets, progress]);\n\n const animatedStyle = useMemo(() => {\n type Interpolated = Animated.AnimatedInterpolation<number>;\n const transform: Array<{ scale: Interpolated } | { translateX: Interpolated } | { translateY: Interpolated }> = [];\n let opacity: Animated.Value | undefined;\n\n for (const item of presets) {\n if (item === 'fade') {\n opacity = progress;\n continue;\n }\n if (item === 'scale' || item === 'pop') {\n transform.push({\n scale: progress.interpolate({ inputRange: [0, 1], outputRange: [SCALE_FROM[item], 1] }),\n });\n continue;\n }\n const vertical = item === 'slide-up' || item === 'slide-down';\n const from = item === 'slide-up' || item === 'slide-left' ? travel : -travel;\n const offset = progress.interpolate({ inputRange: [0, 1], outputRange: [from, 0] });\n transform.push(vertical ? { translateY: offset } : { translateX: offset });\n }\n\n return { opacity, transform };\n }, [presets, progress, travel]);\n\n if (!mounted) return null;\n\n return (\n <Animated.View\n testID={testID}\n accessibilityElementsHidden={!visible}\n importantForAccessibility={visible ? 'auto' : 'no-hide-descendants'}\n style={[style, animatedStyle, !visible && hiddenStyle]}\n >\n {children}\n </Animated.View>\n );\n }\n);\nTransition.displayName = 'Transition';\n"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"registryDependencies": [
|
package/registry/wizard.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
{
|
|
11
11
|
"path": "organisms/Wizard.tsx",
|
|
12
|
-
"content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { ScrollView, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { Steps, type StepItem } from '../molecules/Steps';\nimport { Button } from '../atoms/Button';\nimport { Transition } from '../atoms/Transition';\n\nexport interface WizardStep extends StepItem {\n content: React.ReactNode;\n /** false
|
|
12
|
+
"content": "import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { AccessibilityInfo, Platform, ScrollView, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useLabels } from '../theme/UIProvider';\nimport { KeyboardHandled } from '../theme/KeyboardAvoidance';\nimport { Steps, type StepItem } from '../molecules/Steps';\nimport { Button } from '../atoms/Button';\nimport { Transition } from '../atoms/Transition';\n\nexport interface WizardStep extends StepItem {\n content: React.ReactNode;\n /** `false` blocks advancing from this step (validation). @default true */\n canGoNext?: boolean;\n}\n\nexport interface WizardProps {\n /** The steps, in order: label, optional description, content and `canGoNext`. */\n steps: WizardStep[];\n /** Current step (controlled). If omitted, the Wizard manages its own state. */\n current?: number;\n /** Called with the new index when the step changes (Back, Next or a tap on the indicator). */\n onStepChange?: (index: number) => void;\n /** Called when the finish button is pressed on the last step. */\n onFinish?: () => void;\n /** Layout of the step indicator: a row or a column. @default 'horizontal' */\n orientation?: 'horizontal' | 'vertical';\n /** What the circle of a completed step shows: a check, or its number. @default 'check' */\n completedIndicator?: 'check' | 'number';\n /** Allows returning to an already-completed step by tapping it in the indicator. @default true */\n allowStepNavigation?: boolean;\n /** Text of the back button (hidden on the first step). @default labels.back */\n backLabel?: string;\n /** Text of the next button. @default labels.next */\n nextLabel?: string;\n /** Text of the next button on the last step. @default labels.finish */\n finishLabel?: string;\n /**\n * The step content scrolls when it doesn't fit. Set to false only if\n * a step renders its own virtualized list (nesting a\n * VirtualizedList inside a ScrollView breaks virtualization).\n * @default true\n */\n scrollableContent?: boolean;\n /**\n * The step content slides in following the navigation direction.\n * Only opacity and transform, so the animation runs on the native thread.\n * @default true\n */\n animated?: boolean;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1, gap: theme.tokens.spacing.xl },\n content: { flex: 1 },\n contentInner: { flexGrow: 1, paddingBottom: theme.tokens.spacing.sm },\n footer: { flexDirection: 'row', gap: theme.tokens.spacing.md },\n footerButton: { flex: 1 },\n}));\n\n/**\n * Wizard\n * Multi-step flow: indicator (Steps) + content of the current step +\n * navigation bar. Works controlled (`current` + `onStepChange`) or on its\n * own. `canGoNext: false` on a step disables moving forward, which is where\n * that step's form validation hooks in.\n *\n * Design:\n * - Chunking (Miller) + Hick's law: a long form split into a few steps, one\n * decision set at a time.\n * - Continuity: the content slides in the direction of travel (a fade with\n * Reduce Motion), so forward and back feel like a path.\n * - Proximity + Fitts's law: Back and Next share one row at the bottom, full\n * width, within thumb reach; Next is the filled one (Von Restorff).\n * - Error prevention: Next stays disabled until the step is valid, and the\n * indicator only jumps back, never past an unvalidated step.\n * - Screen readers: each step change is announced (\"Step 2 of 3: Plan\").\n */\nexport function Wizard({\n steps,\n current: controlledCurrent,\n onStepChange,\n onFinish,\n orientation = 'horizontal',\n completedIndicator = 'check',\n allowStepNavigation = true,\n backLabel,\n nextLabel,\n finishLabel,\n scrollableContent = true,\n animated = true,\n style,\n}: WizardProps) {\n const styles = useStyles();\n const labels = useLabels();\n const [uncontrolled, setUncontrolled] = useState(0);\n const isControlled = controlledCurrent !== undefined;\n const current = isControlled ? controlledCurrent : uncontrolled;\n\n const goTo = useCallback(\n (index: number) => {\n const clamped = Math.max(0, Math.min(index, steps.length - 1));\n if (!isControlled) setUncontrolled(clamped);\n onStepChange?.(clamped);\n },\n [isControlled, onStepChange, steps.length]\n );\n\n const stepItems = useMemo(\n () => steps.map(({ label, description }) => ({ label, description })),\n [steps]\n );\n\n // The ref still holds the previous step during the render that brings the new one.\n const previous = useRef(current);\n const forward = current >= previous.current;\n const entered = useRef(false);\n useEffect(() => {\n // Next/Back keep the focus on the button: without this the reader never\n // learns that the content changed. Not on mount, only on a change.\n if (entered.current && previous.current !== current) {\n const step = steps[current];\n if (step) {\n AccessibilityInfo.announceForAccessibility(\n labels.stepOf\n .replace('{n}', String(current + 1))\n .replace('{total}', String(steps.length))\n .replace('{label}', step.label)\n );\n }\n }\n previous.current = current;\n entered.current = true;\n // Only a step change announces; new `steps` with the same step don't.\n }, [current]);\n\n const activeStep = steps[current];\n const isFirst = current === 0;\n const isLast = current === steps.length - 1;\n const canGoNext = activeStep?.canGoNext !== false;\n\n const handleNext = () => {\n if (isLast) onFinish?.();\n else goTo(current + 1);\n };\n\n // The outgoing step isn't animated: keeping both mounted at once would\n // duplicate the forms and their state.\n const renderContent = () =>\n animated ? (\n <Transition\n key={current}\n testID=\"wizard-step\"\n preset={['fade', forward ? 'slide-left' : 'slide-right']}\n appear={entered.current}\n >\n {activeStep?.content}\n </Transition>\n ) : (\n activeStep?.content\n );\n\n return (\n <View style={[styles.container, style]}>\n <Steps\n steps={stepItems}\n current={current}\n orientation={orientation}\n completedIndicator={completedIndicator}\n // You can only go back by tapping the indicator; jumping forward\n // would skip the validation of the steps in between.\n onStepPress={allowStepNavigation ? (index) => index < current && goTo(index) : undefined}\n />\n\n {/* The content scrolls instead of overflowing: with the keyboard open a\n long form ate the footer and the buttons ended up painted over the\n fields. */}\n {scrollableContent ? (\n <ScrollView\n testID=\"wizard-content\"\n style={styles.content}\n contentContainerStyle={styles.contentInner}\n keyboardShouldPersistTaps=\"handled\"\n automaticallyAdjustKeyboardInsets\n showsVerticalScrollIndicator={false}\n >\n {/* iOS: the ScrollView already lifts the field (automaticallyAdjustKeyboardInsets).\n Android doesn't have that prop: there the root host handles it. */}\n {Platform.OS === 'ios' ? <KeyboardHandled>{renderContent()}</KeyboardHandled> : renderContent()}\n </ScrollView>\n ) : (\n <View style={styles.content}>{renderContent()}</View>\n )}\n\n <View style={styles.footer}>\n {!isFirst && (\n <Button title={backLabel ?? labels.back} variant=\"outline\" onPress={() => goTo(current - 1)} style={styles.footerButton} />\n )}\n <Button\n title={isLast ? (finishLabel ?? labels.finish) : (nextLabel ?? labels.next)}\n onPress={handleNext}\n disabled={!canGoNext}\n style={styles.footerButton}\n />\n </View>\n </View>\n );\n}\n"
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"registryDependencies": [
|
package/src/atoms/Avatar.tsx
CHANGED
|
@@ -1,40 +1,131 @@
|
|
|
1
|
-
import React, { memo } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React, { memo, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Image as RNImage,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
type ImageSourcePropType,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
import { useAllowFontScaling, useColors, useTheme } from '../theme/UIProvider';
|
|
12
|
+
import { Icon } from '../icons/Icon';
|
|
13
|
+
import type { IconName } from '../icons/types';
|
|
4
14
|
|
|
5
15
|
export interface AvatarProps {
|
|
16
|
+
/** Diameter in pt. @default 44 */
|
|
6
17
|
size?: number;
|
|
18
|
+
/** Photo. If it fails to load, falls back to the initials of `name` (or the icon). */
|
|
19
|
+
source?: ImageSourcePropType;
|
|
20
|
+
/** Person's name: produces the initials and the accessible name. */
|
|
21
|
+
name?: string;
|
|
22
|
+
/** Icon shown when there is no photo or name. @default 'user' */
|
|
23
|
+
iconName?: IconName;
|
|
24
|
+
/** Fill of the circle. @default colors.primarySoft */
|
|
7
25
|
backgroundColor?: string;
|
|
8
|
-
|
|
26
|
+
/** Color of the initials and icon. @default colors.primaryText */
|
|
27
|
+
color?: string;
|
|
28
|
+
/** Custom content; takes precedence over source/name/iconName. */
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
/** Accessible name. Without it (and without `name`) the avatar is decorative. @default name */
|
|
31
|
+
accessibilityLabel?: string;
|
|
9
32
|
style?: StyleProp<ViewStyle>;
|
|
33
|
+
testID?: string;
|
|
10
34
|
}
|
|
11
35
|
|
|
12
36
|
const styles = StyleSheet.create({
|
|
13
|
-
circle: { justifyContent: 'center', alignItems: 'center' },
|
|
37
|
+
circle: { justifyContent: 'center', alignItems: 'center', overflow: 'hidden' },
|
|
14
38
|
});
|
|
15
39
|
|
|
40
|
+
/** "Ada Lovelace" → "AL", "ada" → "A", " " → "". Handles characters outside the BMP. */
|
|
41
|
+
export function initialsOf(name: string): string {
|
|
42
|
+
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
43
|
+
if (words.length === 0) return '';
|
|
44
|
+
const first = Array.from(words[0] as string)[0] ?? '';
|
|
45
|
+
const last = words.length > 1 ? (Array.from(words[words.length - 1] as string)[0] ?? '') : '';
|
|
46
|
+
return (first + last).toUpperCase();
|
|
47
|
+
}
|
|
48
|
+
|
|
16
49
|
/**
|
|
17
50
|
* Avatar
|
|
18
|
-
*
|
|
19
|
-
*
|
|
51
|
+
* Circle with a photo, initials or an icon. Used on the left of list rows
|
|
52
|
+
* or as a detail header (alongside AvatarHeader).
|
|
53
|
+
*
|
|
54
|
+
* Priority: `children` → `source` (if it loads) → initials of `name` →
|
|
55
|
+
* `iconName` → `user` icon. A broken photo doesn't leave an empty circle: it
|
|
56
|
+
* falls back to the initials.
|
|
57
|
+
*
|
|
58
|
+
* Gestalt:
|
|
59
|
+
* - Similarity: all avatars in a list share shape and size, so a column of
|
|
60
|
+
* people scans as a series even when it mixes photos, initials and icons.
|
|
61
|
+
* - Closure: the circle clips the photo (overflow hidden); initials and the
|
|
62
|
+
* icon scale with `size` so the content always stays inside.
|
|
63
|
+
*
|
|
64
|
+
* Accessibility: with `name` (or `accessibilityLabel`) it announces itself as
|
|
65
|
+
* an image with that name; with neither it is decorative.
|
|
20
66
|
*/
|
|
21
|
-
export const Avatar = memo(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
export const Avatar = memo(
|
|
68
|
+
({
|
|
69
|
+
size = 44,
|
|
70
|
+
source,
|
|
71
|
+
name,
|
|
72
|
+
iconName,
|
|
73
|
+
backgroundColor,
|
|
74
|
+
color,
|
|
75
|
+
children,
|
|
76
|
+
accessibilityLabel,
|
|
77
|
+
style,
|
|
78
|
+
testID,
|
|
79
|
+
}: AvatarProps) => {
|
|
80
|
+
const colors = useColors();
|
|
81
|
+
const theme = useTheme();
|
|
82
|
+
const allowFontScaling = useAllowFontScaling();
|
|
83
|
+
const [failed, setFailed] = useState(false);
|
|
84
|
+
const fg = color ?? colors.primaryText;
|
|
85
|
+
const label = accessibilityLabel ?? name;
|
|
86
|
+
const initials = name ? initialsOf(name) : '';
|
|
87
|
+
|
|
88
|
+
let content: React.ReactNode = children;
|
|
89
|
+
if (content == null) {
|
|
90
|
+
if (source && !failed) {
|
|
91
|
+
content = (
|
|
92
|
+
<RNImage
|
|
93
|
+
source={source}
|
|
94
|
+
style={{ width: size, height: size }}
|
|
95
|
+
onError={() => setFailed(true)}
|
|
96
|
+
accessibilityIgnoresInvertColors
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
} else if (initials) {
|
|
100
|
+
content = (
|
|
101
|
+
<Text
|
|
102
|
+
allowFontScaling={allowFontScaling}
|
|
103
|
+
numberOfLines={1}
|
|
104
|
+
style={{ color: fg, fontSize: Math.round(size * 0.4), fontWeight: theme.tokens.fontWeight.semibold }}
|
|
105
|
+
>
|
|
106
|
+
{initials}
|
|
107
|
+
</Text>
|
|
108
|
+
);
|
|
109
|
+
} else {
|
|
110
|
+
content = <Icon name={iconName ?? 'user'} size={Math.round(size * 0.5)} color={fg} />;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<View
|
|
116
|
+
testID={testID}
|
|
117
|
+
accessible={!!label}
|
|
118
|
+
accessibilityRole={label ? 'image' : undefined}
|
|
119
|
+
accessibilityLabel={label}
|
|
120
|
+
style={[
|
|
121
|
+
styles.circle,
|
|
122
|
+
{ width: size, height: size, borderRadius: size / 2, backgroundColor: backgroundColor || colors.primarySoft },
|
|
123
|
+
style,
|
|
124
|
+
]}
|
|
125
|
+
>
|
|
126
|
+
{content}
|
|
127
|
+
</View>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
);
|
|
40
131
|
Avatar.displayName = 'Avatar';
|
package/src/atoms/Badge.tsx
CHANGED
|
@@ -6,29 +6,40 @@ import { useAllowFontScaling, useTheme } from '../theme/UIProvider';
|
|
|
6
6
|
export type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral';
|
|
7
7
|
|
|
8
8
|
export interface BadgeProps {
|
|
9
|
+
/** Text of the pill, on a single line (truncated with "…"). */
|
|
9
10
|
label: string;
|
|
11
|
+
/** Semantic color set: success, error, warning, info or neutral. @default 'neutral' */
|
|
10
12
|
variant?: BadgeVariant;
|
|
11
13
|
/** Manual override, takes precedence over `variant`. */
|
|
12
14
|
backgroundColor?: string;
|
|
15
|
+
/** Manual text color, takes precedence over `variant`. */
|
|
13
16
|
textColor?: string;
|
|
14
17
|
style?: StyleProp<ViewStyle>;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const useStyles = createStyles((theme) => ({
|
|
18
21
|
badge: {
|
|
19
|
-
paddingHorizontal: theme.tokens.spacing.md
|
|
20
|
-
paddingVertical: theme.tokens.spacing.xs
|
|
22
|
+
paddingHorizontal: theme.tokens.spacing.md,
|
|
23
|
+
paddingVertical: theme.tokens.spacing.xs,
|
|
21
24
|
borderRadius: theme.tokens.radius.full,
|
|
22
25
|
alignSelf: 'flex-start',
|
|
23
26
|
},
|
|
24
|
-
text: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.bold, letterSpacing:
|
|
27
|
+
text: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.bold, letterSpacing: theme.tokens.letterSpacing.wide },
|
|
25
28
|
}));
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Badge
|
|
29
|
-
*
|
|
30
|
-
* info/neutral); `backgroundColor`/`textColor`
|
|
31
|
-
*
|
|
32
|
+
* Status pill. `variant` covers the common cases (success/error/warning/
|
|
33
|
+
* info/neutral); `backgroundColor`/`textColor` are for cases that don't
|
|
34
|
+
* fit the semantic set.
|
|
35
|
+
*
|
|
36
|
+
* Gestalt, similarity: all badges share shape, size and typography; only
|
|
37
|
+
* the color changes, and it changes by meaning (green = ok, red = error…),
|
|
38
|
+
* so a column of statuses scans at a glance. The meaning also goes in the
|
|
39
|
+
* text: never rely on color alone (color blindness).
|
|
40
|
+
* Every `*Soft`/`*Text` pair in the theme meets WCAG AA in light and dark;
|
|
41
|
+
* with custom colors, contrast is up to whoever passes them.
|
|
42
|
+
* A single line: a long label is truncated with "…" instead of deforming the pill.
|
|
32
43
|
*/
|
|
33
44
|
export const Badge = memo(({ label, variant = 'neutral', backgroundColor, textColor, style }: BadgeProps) => {
|
|
34
45
|
const theme = useTheme();
|
|
@@ -37,8 +48,8 @@ export const Badge = memo(({ label, variant = 'neutral', backgroundColor, textCo
|
|
|
37
48
|
const { colors } = theme;
|
|
38
49
|
|
|
39
50
|
const palette: Record<BadgeVariant, { bg: string; fg: string }> = {
|
|
40
|
-
// fg
|
|
41
|
-
//
|
|
51
|
+
// fg is the `*Text` role, not the full accent: the accent over its own
|
|
52
|
+
// tint gives ~2.2:1 in light — the label was unreadable.
|
|
42
53
|
success: { bg: colors.successSoft, fg: colors.successText },
|
|
43
54
|
error: { bg: colors.errorSoft, fg: colors.errorText },
|
|
44
55
|
warning: { bg: colors.warningSoft, fg: colors.warningText },
|
|
@@ -48,7 +59,7 @@ export const Badge = memo(({ label, variant = 'neutral', backgroundColor, textCo
|
|
|
48
59
|
|
|
49
60
|
return (
|
|
50
61
|
<View style={[styles.badge, { backgroundColor: backgroundColor ?? palette[variant].bg }, style]}>
|
|
51
|
-
<Text allowFontScaling={allowFontScaling} style={[styles.text, { color: textColor ?? palette[variant].fg }]}>
|
|
62
|
+
<Text allowFontScaling={allowFontScaling} numberOfLines={1} style={[styles.text, { color: textColor ?? palette[variant].fg }]}>
|
|
52
63
|
{label}
|
|
53
64
|
</Text>
|
|
54
65
|
</View>
|