orn-ui 0.2.10 → 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 +66 -51
- package/README.md +3 -7
- 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 +174 -154
- 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 +176 -156
- 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 +193 -164
- 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
|
@@ -8,62 +8,116 @@ import type { IconName } from '../icons/types';
|
|
|
8
8
|
export interface AvatarHeaderProps {
|
|
9
9
|
/** Icon to show inside the circle. If omitted, `initials` is used instead. */
|
|
10
10
|
iconName?: IconName;
|
|
11
|
+
/** Icon color. Keep AA contrast against `primarySoft`, the circle's fill. @default colors.primary */
|
|
11
12
|
iconColor?: string;
|
|
12
|
-
/** Short text (e.g. 2 letters) shown when `iconName` isn't provided. */
|
|
13
|
+
/** Short text (e.g. 2 letters) shown when `iconName` isn't provided. Without either, a `user` icon. */
|
|
13
14
|
initials?: string;
|
|
15
|
+
/** Main heading of the screen or modal; announced as a header. */
|
|
14
16
|
title: string;
|
|
17
|
+
/** Supporting line under the title. A string gets the muted style; a node renders as is. */
|
|
15
18
|
subtitle?: React.ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Third and last line, the least important detail ("Joined March 2023").
|
|
21
|
+
* Three lines is the limit: beyond that the header stops being scannable.
|
|
22
|
+
*/
|
|
23
|
+
caption?: string;
|
|
16
24
|
style?: StyleProp<ViewStyle>;
|
|
25
|
+
testID?: string;
|
|
17
26
|
}
|
|
18
27
|
|
|
28
|
+
const CIRCLE = 80;
|
|
29
|
+
const ICON_SIZE = 40;
|
|
30
|
+
|
|
19
31
|
const useStyles = createStyles((theme) => ({
|
|
20
|
-
|
|
32
|
+
// No outer margin: the space below belongs to the parent's layout.
|
|
33
|
+
container: { alignItems: 'center' },
|
|
21
34
|
avatar: {
|
|
22
|
-
width:
|
|
23
|
-
height:
|
|
24
|
-
borderRadius:
|
|
35
|
+
width: CIRCLE,
|
|
36
|
+
height: CIRCLE,
|
|
37
|
+
borderRadius: CIRCLE / 2,
|
|
25
38
|
backgroundColor: theme.colors.primarySoft,
|
|
26
39
|
justifyContent: 'center',
|
|
27
40
|
alignItems: 'center',
|
|
28
41
|
marginBottom: theme.tokens.spacing.md,
|
|
29
42
|
},
|
|
30
|
-
initials: {
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
initials: {
|
|
44
|
+
fontSize: theme.tokens.fontSize.xxl,
|
|
45
|
+
fontWeight: theme.tokens.fontWeight.bold,
|
|
46
|
+
color: theme.colors.primaryText,
|
|
47
|
+
},
|
|
48
|
+
title: {
|
|
49
|
+
fontSize: theme.tokens.fontSize.xl,
|
|
50
|
+
lineHeight: theme.tokens.lineHeight.lg,
|
|
51
|
+
fontWeight: theme.tokens.fontWeight.bold,
|
|
52
|
+
textAlign: 'center',
|
|
53
|
+
color: theme.colors.text,
|
|
54
|
+
marginBottom: theme.tokens.spacing.xs,
|
|
55
|
+
},
|
|
56
|
+
subtitle: {
|
|
57
|
+
fontSize: theme.tokens.fontSize.md,
|
|
58
|
+
lineHeight: theme.tokens.lineHeight.md,
|
|
59
|
+
color: theme.colors.textLight,
|
|
60
|
+
textAlign: 'center',
|
|
61
|
+
},
|
|
62
|
+
caption: {
|
|
63
|
+
fontSize: theme.tokens.fontSize.sm,
|
|
64
|
+
lineHeight: theme.tokens.lineHeight.sm,
|
|
65
|
+
color: theme.colors.textLight,
|
|
66
|
+
textAlign: 'center',
|
|
67
|
+
marginTop: theme.tokens.spacing.xxs,
|
|
68
|
+
},
|
|
33
69
|
}));
|
|
34
70
|
|
|
35
71
|
/**
|
|
36
72
|
* AvatarHeader
|
|
37
|
-
*
|
|
38
|
-
*
|
|
73
|
+
* 80x80 circle with an icon or initials + centered title/subtitle. Typical
|
|
74
|
+
* header of detail modals (client, product, etc).
|
|
75
|
+
*
|
|
76
|
+
* Gestalt, symmetry and focal point: everything sits on one centered axis and
|
|
77
|
+
* the circle is the single large shape, so the eye lands there first and then
|
|
78
|
+
* drops to the title. Proximity: title and subtitle are closer to each other
|
|
79
|
+
* than to the circle, reading as one name + its detail. Hierarchy by size:
|
|
80
|
+
* title, subtitle, caption — at most three lines of text. Long text wraps
|
|
81
|
+
* centered, so the axis holds. No outer margin: the parent sets the spacing.
|
|
82
|
+
*
|
|
83
|
+
* Accessibility: the title is a header; the circle is decorative (the
|
|
84
|
+
* initials would only repeat the title).
|
|
39
85
|
*/
|
|
40
|
-
export const AvatarHeader = memo(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
86
|
+
export const AvatarHeader = memo(
|
|
87
|
+
({ iconName, iconColor, initials, title, subtitle, caption, style, testID }: AvatarHeaderProps) => {
|
|
88
|
+
const colors = useColors();
|
|
89
|
+
const styles = useStyles();
|
|
90
|
+
const allowFontScaling = useAllowFontScaling();
|
|
91
|
+
const fallbackIcon: IconName | undefined = iconName ?? (initials ? undefined : 'user');
|
|
44
92
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
93
|
+
return (
|
|
94
|
+
<View style={[styles.container, style]} testID={testID}>
|
|
95
|
+
<View style={styles.avatar} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
|
|
96
|
+
{fallbackIcon ? (
|
|
97
|
+
<Icon name={fallbackIcon} size={ICON_SIZE} color={iconColor ?? colors.primary} />
|
|
98
|
+
) : (
|
|
99
|
+
<Text allowFontScaling={allowFontScaling} style={styles.initials}>
|
|
100
|
+
{initials}
|
|
101
|
+
</Text>
|
|
102
|
+
)}
|
|
103
|
+
</View>
|
|
104
|
+
<Text allowFontScaling={allowFontScaling} style={styles.title} accessibilityRole="header">
|
|
105
|
+
{title}
|
|
106
|
+
</Text>
|
|
107
|
+
{typeof subtitle === 'string' ? (
|
|
108
|
+
<Text allowFontScaling={allowFontScaling} style={styles.subtitle}>
|
|
109
|
+
{subtitle}
|
|
110
|
+
</Text>
|
|
50
111
|
) : (
|
|
51
|
-
|
|
52
|
-
|
|
112
|
+
subtitle
|
|
113
|
+
)}
|
|
114
|
+
{!!caption && (
|
|
115
|
+
<Text allowFontScaling={allowFontScaling} style={styles.caption}>
|
|
116
|
+
{caption}
|
|
53
117
|
</Text>
|
|
54
118
|
)}
|
|
55
119
|
</View>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{typeof subtitle === 'string' ? (
|
|
60
|
-
<Text allowFontScaling={allowFontScaling} style={styles.subtitle}>
|
|
61
|
-
{subtitle}
|
|
62
|
-
</Text>
|
|
63
|
-
) : (
|
|
64
|
-
subtitle
|
|
65
|
-
)}
|
|
66
|
-
</View>
|
|
67
|
-
);
|
|
68
|
-
});
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
);
|
|
69
123
|
AvatarHeader.displayName = 'AvatarHeader';
|
|
@@ -3,23 +3,45 @@ import { View, type StyleProp, type ViewStyle } from 'react-native';
|
|
|
3
3
|
import { Button } from '../atoms/Button';
|
|
4
4
|
import type { IconName } from '../icons/types';
|
|
5
5
|
import { useLabels } from '../theme/UIProvider';
|
|
6
|
+
import { createStyles } from '../theme/createStyles';
|
|
6
7
|
|
|
7
8
|
export interface FormActionsProps {
|
|
8
|
-
/**
|
|
9
|
+
/** Label of the main action ("Save"). The primary button shows only with both this and `onPrimaryPress`. */
|
|
9
10
|
primaryLabel?: string;
|
|
11
|
+
/** Main action. */
|
|
10
12
|
onPrimaryPress?: () => void;
|
|
13
|
+
/** Blocks the main action, e.g. while the form is invalid. @default false */
|
|
11
14
|
primaryDisabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The main action is running: shows a spinner and ignores presses, so it
|
|
17
|
+
* can't be sent twice. The secondary button stays usable. @default false
|
|
18
|
+
*/
|
|
12
19
|
primaryLoading?: boolean;
|
|
20
|
+
/** Icon before the primary label. */
|
|
13
21
|
primaryLeftIconName?: IconName;
|
|
22
|
+
/** Label of the ghost button. @default labels.close */
|
|
14
23
|
secondaryLabel?: string;
|
|
24
|
+
/** Close / cancel action. */
|
|
15
25
|
onSecondaryPress: () => void;
|
|
16
26
|
style?: StyleProp<ViewStyle>;
|
|
27
|
+
/** Root testID; the buttons get `<testID>-primary` and `<testID>-secondary`. */
|
|
28
|
+
testID?: string;
|
|
17
29
|
}
|
|
18
30
|
|
|
31
|
+
const useStyles = createStyles((theme) => ({
|
|
32
|
+
root: { gap: theme.tokens.spacing.md },
|
|
33
|
+
}));
|
|
34
|
+
|
|
19
35
|
/**
|
|
20
36
|
* FormActions
|
|
21
|
-
*
|
|
22
|
-
*
|
|
37
|
+
* Pair of buttons: a primary one (save/edit) and a ghost one (close/cancel).
|
|
38
|
+
* Typical use: at the end of a form, or in a Modal's footer.
|
|
39
|
+
*
|
|
40
|
+
* Gestalt, proximity: both buttons sit close together and apart from the
|
|
41
|
+
* fields, so they read as the form's closing actions. Von Restorff: only the
|
|
42
|
+
* primary is filled; the secondary is ghost, so the main path is obvious and
|
|
43
|
+
* the two never compete (Hick: one clear choice). Fitts: full width, primary
|
|
44
|
+
* first, closest to the content it acts on.
|
|
23
45
|
*/
|
|
24
46
|
export const FormActions = memo(
|
|
25
47
|
({
|
|
@@ -31,20 +53,28 @@ export const FormActions = memo(
|
|
|
31
53
|
secondaryLabel,
|
|
32
54
|
onSecondaryPress,
|
|
33
55
|
style,
|
|
56
|
+
testID,
|
|
34
57
|
}: FormActionsProps) => {
|
|
35
58
|
const labels = useLabels();
|
|
59
|
+
const styles = useStyles();
|
|
36
60
|
return (
|
|
37
|
-
<View style={[
|
|
38
|
-
{primaryLabel && onPrimaryPress && (
|
|
61
|
+
<View style={[styles.root, style]} testID={testID}>
|
|
62
|
+
{!!primaryLabel && !!onPrimaryPress && (
|
|
39
63
|
<Button
|
|
40
64
|
title={primaryLabel}
|
|
41
65
|
onPress={onPrimaryPress}
|
|
42
66
|
disabled={primaryDisabled}
|
|
43
67
|
loading={primaryLoading}
|
|
44
68
|
leftIconName={primaryLeftIconName}
|
|
69
|
+
testID={testID && `${testID}-primary`}
|
|
45
70
|
/>
|
|
46
71
|
)}
|
|
47
|
-
<Button
|
|
72
|
+
<Button
|
|
73
|
+
title={secondaryLabel ?? labels.close}
|
|
74
|
+
onPress={onSecondaryPress}
|
|
75
|
+
variant="ghost"
|
|
76
|
+
testID={testID && `${testID}-secondary`}
|
|
77
|
+
/>
|
|
48
78
|
</View>
|
|
49
79
|
);
|
|
50
80
|
}
|
|
@@ -7,43 +7,70 @@ import type { IconName } from '../icons/types';
|
|
|
7
7
|
import { Avatar } from '../atoms/Avatar';
|
|
8
8
|
|
|
9
9
|
export interface InfoRowProps {
|
|
10
|
+
/** Icon that identifies the kind of data at a glance (decorative for screen readers). */
|
|
10
11
|
icon: IconName;
|
|
12
|
+
/** What the value is ("Email", "Address"). */
|
|
11
13
|
label: string;
|
|
14
|
+
/** The data itself. Empty, `null` or `undefined` shows `placeholder`. */
|
|
12
15
|
value: string | null | undefined;
|
|
16
|
+
/** Text shown, in italics, when there is no value ("No phone number"). */
|
|
13
17
|
placeholder: string;
|
|
14
18
|
style?: StyleProp<ViewStyle>;
|
|
19
|
+
testID?: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
const ICON_BADGE = 40;
|
|
23
|
+
const ICON_SIZE = 20;
|
|
24
|
+
|
|
17
25
|
const useStyles = createStyles((theme) => ({
|
|
18
|
-
row: { flexDirection: 'row', alignItems: 'center' },
|
|
19
|
-
icon: { marginRight: theme.tokens.spacing.md },
|
|
26
|
+
row: { flexDirection: 'row', alignItems: 'center', gap: theme.tokens.spacing.md },
|
|
20
27
|
textContainer: { flex: 1 },
|
|
21
|
-
label: {
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
label: {
|
|
29
|
+
fontSize: theme.tokens.fontSize.sm,
|
|
30
|
+
lineHeight: theme.tokens.lineHeight.sm,
|
|
31
|
+
color: theme.colors.textLight,
|
|
32
|
+
marginBottom: theme.tokens.spacing.xxs,
|
|
33
|
+
},
|
|
34
|
+
value: {
|
|
35
|
+
fontSize: theme.tokens.fontSize.md,
|
|
36
|
+
lineHeight: theme.tokens.lineHeight.md,
|
|
37
|
+
color: theme.colors.text,
|
|
38
|
+
fontWeight: theme.tokens.fontWeight.medium,
|
|
39
|
+
},
|
|
40
|
+
placeholder: { color: theme.colors.textLight, fontStyle: 'italic', fontWeight: theme.tokens.fontWeight.regular },
|
|
24
41
|
}));
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* InfoRow
|
|
28
|
-
*
|
|
29
|
-
* `placeholder`
|
|
45
|
+
* Circular icon on the left + stacked label/value on the right. Shows
|
|
46
|
+
* `placeholder` in italics when there is no value. Meant for detail modals.
|
|
47
|
+
*
|
|
48
|
+
* Gestalt, proximity: label and value sit 2pt apart and the icon a token
|
|
49
|
+
* further, so each row reads as one fact; stack rows with a larger gap.
|
|
50
|
+
* Similarity: every label shares the small muted style and every value the
|
|
51
|
+
* strong one, so a list of rows scans by column. A missing value changes
|
|
52
|
+
* style (muted italics) so it can't be mistaken for data.
|
|
53
|
+
*
|
|
54
|
+
* Accessibility: the row is a single element ("Email, jane@example.com");
|
|
55
|
+
* the icon is decorative.
|
|
30
56
|
*/
|
|
31
|
-
export const InfoRow = memo(({ icon, label, value, placeholder, style }: InfoRowProps) => {
|
|
57
|
+
export const InfoRow = memo(({ icon, label, value, placeholder, style, testID }: InfoRowProps) => {
|
|
32
58
|
const colors = useColors();
|
|
33
59
|
const styles = useStyles();
|
|
34
60
|
const allowFontScaling = useAllowFontScaling();
|
|
61
|
+
const shown = value || placeholder;
|
|
35
62
|
|
|
36
63
|
return (
|
|
37
|
-
<View style={[styles.row, style]}>
|
|
38
|
-
<Avatar size={
|
|
39
|
-
<Icon name={icon} size={
|
|
64
|
+
<View style={[styles.row, style]} accessible accessibilityLabel={`${label}, ${shown}`} testID={testID}>
|
|
65
|
+
<Avatar size={ICON_BADGE} backgroundColor={colors.inputBackground}>
|
|
66
|
+
<Icon name={icon} size={ICON_SIZE} color={colors.textLight} />
|
|
40
67
|
</Avatar>
|
|
41
68
|
<View style={styles.textContainer}>
|
|
42
69
|
<Text allowFontScaling={allowFontScaling} style={styles.label}>
|
|
43
70
|
{label}
|
|
44
71
|
</Text>
|
|
45
72
|
<Text allowFontScaling={allowFontScaling} style={[styles.value, !value && styles.placeholder]}>
|
|
46
|
-
{
|
|
73
|
+
{shown}
|
|
47
74
|
</Text>
|
|
48
75
|
</View>
|
|
49
76
|
</View>
|
|
@@ -7,12 +7,21 @@ import { PressableScale } from '../atoms/PressableScale';
|
|
|
7
7
|
import type { IconName } from '../icons/types';
|
|
8
8
|
|
|
9
9
|
export interface OptionCardProps {
|
|
10
|
+
/** Name of the option. It is also the start of its accessible name. */
|
|
10
11
|
label: string;
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Secondary line: the detail that decides the choice ("arrives in 3 days").
|
|
14
|
+
* Screen readers read it right after the label, not as a hint, because
|
|
15
|
+
* hints can be turned off and this detail is part of the choice.
|
|
16
|
+
*/
|
|
12
17
|
description?: string;
|
|
18
|
+
/** Icon that helps recognize the option. Avoid `check`: it reads as "selected". */
|
|
13
19
|
iconName?: IconName;
|
|
20
|
+
/** Whether this is the chosen option of its group. */
|
|
14
21
|
isSelected: boolean;
|
|
22
|
+
/** Called on press; the group decides which option is selected. */
|
|
15
23
|
onPress: () => void;
|
|
24
|
+
/** Dims the card and ignores presses. @default false */
|
|
16
25
|
disabled?: boolean;
|
|
17
26
|
/** 'vertical': icon on top, text centered below. 'horizontal': in a row. @default 'horizontal' */
|
|
18
27
|
layout?: 'vertical' | 'horizontal';
|
|
@@ -20,43 +29,70 @@ export interface OptionCardProps {
|
|
|
20
29
|
testID?: string;
|
|
21
30
|
}
|
|
22
31
|
|
|
32
|
+
const ICON_SIZE = 24;
|
|
33
|
+
/** Same height for every vertical card in a row, even with a short label. */
|
|
34
|
+
const VERTICAL_MIN_HEIGHT = 100;
|
|
35
|
+
const DISABLED_OPACITY = 0.5;
|
|
36
|
+
/** The selected border is thicker; its padding gives back the difference so content doesn't shift. */
|
|
37
|
+
const BORDER = 1;
|
|
38
|
+
const BORDER_SELECTED = 1.5;
|
|
39
|
+
|
|
23
40
|
const useStyles = createStyles((theme) => ({
|
|
24
41
|
card: {
|
|
25
|
-
//
|
|
42
|
+
// The cards share whatever row contains them, whether two or five.
|
|
26
43
|
flex: 1,
|
|
27
44
|
alignItems: 'center',
|
|
28
45
|
justifyContent: 'center',
|
|
29
|
-
|
|
46
|
+
// Raised: in dark it stays a visible tile on a surface (Card does the same).
|
|
47
|
+
backgroundColor: theme.colors.surfaceRaised,
|
|
30
48
|
borderRadius: theme.tokens.radius.md,
|
|
31
|
-
borderWidth:
|
|
49
|
+
borderWidth: BORDER,
|
|
32
50
|
borderColor: theme.colors.border,
|
|
51
|
+
padding: theme.tokens.spacing.md,
|
|
33
52
|
...theme.tokens.shadow.sm,
|
|
34
53
|
},
|
|
35
|
-
cardHorizontal: { flexDirection: 'row',
|
|
36
|
-
cardVertical: {
|
|
54
|
+
cardHorizontal: { flexDirection: 'row', gap: theme.tokens.spacing.sm },
|
|
55
|
+
cardVertical: { gap: theme.tokens.spacing.sm, minHeight: VERTICAL_MIN_HEIGHT },
|
|
37
56
|
cardSelected: {
|
|
38
57
|
borderColor: theme.colors.primary,
|
|
39
58
|
backgroundColor: theme.colors.primarySoft,
|
|
40
|
-
borderWidth:
|
|
59
|
+
borderWidth: BORDER_SELECTED,
|
|
60
|
+
padding: theme.tokens.spacing.md - (BORDER_SELECTED - BORDER),
|
|
41
61
|
elevation: 0,
|
|
42
62
|
shadowOpacity: 0,
|
|
43
63
|
},
|
|
44
|
-
cardDisabled: { opacity:
|
|
64
|
+
cardDisabled: { opacity: DISABLED_OPACITY },
|
|
45
65
|
text: { flexShrink: 1 },
|
|
46
66
|
textVertical: { alignItems: 'center' },
|
|
47
|
-
label: {
|
|
48
|
-
|
|
67
|
+
label: {
|
|
68
|
+
color: theme.colors.text,
|
|
69
|
+
fontSize: theme.tokens.fontSize.md,
|
|
70
|
+
lineHeight: theme.tokens.lineHeight.md,
|
|
71
|
+
fontWeight: theme.tokens.fontWeight.medium,
|
|
72
|
+
},
|
|
73
|
+
labelVertical: { textAlign: 'center' },
|
|
49
74
|
labelSelected: { color: theme.colors.primaryText },
|
|
50
|
-
description: {
|
|
75
|
+
description: {
|
|
76
|
+
fontSize: theme.tokens.fontSize.sm,
|
|
77
|
+
lineHeight: theme.tokens.lineHeight.sm,
|
|
78
|
+
color: theme.colors.textLight,
|
|
79
|
+
marginTop: theme.tokens.spacing.xxs,
|
|
80
|
+
},
|
|
51
81
|
descriptionVertical: { textAlign: 'center' },
|
|
52
82
|
}));
|
|
53
83
|
|
|
54
84
|
/**
|
|
55
85
|
* OptionCard
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
86
|
+
* Mutually exclusive option with an icon and label that highlights when
|
|
87
|
+
* `isSelected`. It is a radio with a card's face, so it announces itself as
|
|
88
|
+
* one: wrap the group in a View with `accessibilityRole="radiogroup"` so the
|
|
89
|
+
* screen reader reads "1 of 3".
|
|
90
|
+
*
|
|
91
|
+
* Gestalt, similarity: every card in a group shares shape, size and type, so
|
|
92
|
+
* they read as alternatives of one question. Von Restorff / figure-ground: the
|
|
93
|
+
* selected one is the only filled card with an accent border and no shadow —
|
|
94
|
+
* it stands out without the others losing legibility (their labels keep the
|
|
95
|
+
* `text` color). The thicker selected border doesn't move the content.
|
|
60
96
|
*/
|
|
61
97
|
export const OptionCard = memo(
|
|
62
98
|
({
|
|
@@ -75,8 +111,8 @@ export const OptionCard = memo(
|
|
|
75
111
|
const allowFontScaling = useAllowFontScaling();
|
|
76
112
|
const isVertical = layout === 'vertical';
|
|
77
113
|
|
|
78
|
-
// Feedback
|
|
79
|
-
//
|
|
114
|
+
// Feedback through transform, not opacity: with elevation, animating opacity
|
|
115
|
+
// triggers an Android bug that redraws the shadow as a dark border.
|
|
80
116
|
return (
|
|
81
117
|
<PressableScale
|
|
82
118
|
style={[
|
|
@@ -90,11 +126,10 @@ export const OptionCard = memo(
|
|
|
90
126
|
disabled={disabled}
|
|
91
127
|
accessibilityRole="radio"
|
|
92
128
|
accessibilityState={{ selected: isSelected, checked: isSelected, disabled }}
|
|
93
|
-
accessibilityLabel={label}
|
|
94
|
-
accessibilityHint={description}
|
|
129
|
+
accessibilityLabel={description ? `${label}, ${description}` : label}
|
|
95
130
|
testID={testID}
|
|
96
131
|
>
|
|
97
|
-
{!!iconName && <Icon name={iconName} size={
|
|
132
|
+
{!!iconName && <Icon name={iconName} size={ICON_SIZE} color={isSelected ? colors.primary : colors.textLight} />}
|
|
98
133
|
|
|
99
134
|
<View style={[styles.text, isVertical && styles.textVertical]}>
|
|
100
135
|
<Text
|
|
@@ -1,46 +1,103 @@
|
|
|
1
|
-
import React, { memo } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { memo, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Easing,
|
|
5
|
+
I18nManager,
|
|
6
|
+
Text,
|
|
7
|
+
TouchableOpacity,
|
|
8
|
+
View,
|
|
9
|
+
type LayoutChangeEvent,
|
|
10
|
+
type StyleProp,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from 'react-native';
|
|
3
13
|
import { createStyles } from '../theme/createStyles';
|
|
4
|
-
import { useAllowFontScaling } from '../theme/UIProvider';
|
|
14
|
+
import { useAllowFontScaling, useTheme } from '../theme/UIProvider';
|
|
15
|
+
import { useReduceMotion } from '../theme/useReduceMotion';
|
|
5
16
|
|
|
6
17
|
export interface SegmentedControlOption<T extends string = string> {
|
|
7
18
|
value: T;
|
|
19
|
+
/** Visible text. Kept to one line (ellipsized); screen readers get it whole. */
|
|
8
20
|
label: string;
|
|
21
|
+
/** Disables only this segment. */
|
|
9
22
|
disabled?: boolean;
|
|
10
23
|
}
|
|
11
24
|
|
|
12
25
|
export interface SegmentedControlProps<T extends string = string> {
|
|
26
|
+
/** The choices, all visible at once. Two to four read best (Hick); more belong in a Select. */
|
|
13
27
|
options: SegmentedControlOption<T>[];
|
|
28
|
+
/** The selected option's value. */
|
|
14
29
|
value: T;
|
|
30
|
+
/** Called with the new value; not called when pressing the segment already selected. */
|
|
15
31
|
onChange: (value: T) => void;
|
|
32
|
+
/** Disables the whole control. @default false */
|
|
16
33
|
disabled?: boolean;
|
|
17
34
|
style?: StyleProp<ViewStyle>;
|
|
18
35
|
testID?: string;
|
|
19
36
|
}
|
|
20
37
|
|
|
38
|
+
/** Inset between the track and the segments. */
|
|
39
|
+
const TRACK_PADDING = 3;
|
|
40
|
+
/** Minimum touch target (Fitts): the segment plus the track inset reach it. */
|
|
41
|
+
const MIN_TOUCH = 44;
|
|
42
|
+
const DISABLED_OPACITY = 0.5;
|
|
43
|
+
const HIT_SLOP = { top: TRACK_PADDING, bottom: TRACK_PADDING };
|
|
44
|
+
|
|
21
45
|
const useStyles = createStyles((theme) => ({
|
|
22
46
|
track: {
|
|
23
47
|
flexDirection: 'row',
|
|
24
48
|
backgroundColor: theme.colors.inputBackground,
|
|
25
49
|
borderRadius: theme.tokens.radius.md,
|
|
26
|
-
padding:
|
|
50
|
+
padding: TRACK_PADDING,
|
|
27
51
|
},
|
|
28
52
|
segment: {
|
|
29
53
|
flex: 1,
|
|
54
|
+
minHeight: MIN_TOUCH - TRACK_PADDING * 2,
|
|
30
55
|
paddingVertical: theme.tokens.spacing.sm,
|
|
56
|
+
paddingHorizontal: theme.tokens.spacing.xs,
|
|
31
57
|
borderRadius: theme.tokens.radius.sm,
|
|
32
58
|
alignItems: 'center',
|
|
59
|
+
justifyContent: 'center',
|
|
60
|
+
},
|
|
61
|
+
// Light: a white segment with a shadow over the grey track. Dark: shadows
|
|
62
|
+
// don't show and `surface` is darker than the track (the segment looked
|
|
63
|
+
// sunken), so the selected one takes the soft accent fill instead.
|
|
64
|
+
// One shared indicator slides under the segments; until the track is
|
|
65
|
+
// measured, the selected segment paints this same fill itself.
|
|
66
|
+
indicator: {
|
|
67
|
+
position: 'absolute',
|
|
68
|
+
top: TRACK_PADDING,
|
|
69
|
+
bottom: TRACK_PADDING,
|
|
70
|
+
start: TRACK_PADDING,
|
|
71
|
+
borderRadius: theme.tokens.radius.sm,
|
|
72
|
+
},
|
|
73
|
+
segmentActive:
|
|
74
|
+
theme.scheme === 'dark'
|
|
75
|
+
? { backgroundColor: theme.colors.primarySoft }
|
|
76
|
+
: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },
|
|
77
|
+
segmentDisabled: { opacity: DISABLED_OPACITY },
|
|
78
|
+
label: {
|
|
79
|
+
fontSize: theme.tokens.fontSize.md,
|
|
80
|
+
lineHeight: theme.tokens.lineHeight.md,
|
|
81
|
+
fontWeight: theme.tokens.fontWeight.medium,
|
|
82
|
+
color: theme.colors.textLight,
|
|
33
83
|
},
|
|
34
|
-
segmentActive: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },
|
|
35
|
-
segmentDisabled: { opacity: 0.5 },
|
|
36
|
-
label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.textLight },
|
|
37
84
|
labelActive: { color: theme.colors.primaryText, fontWeight: theme.tokens.fontWeight.semibold },
|
|
38
85
|
}));
|
|
39
86
|
|
|
40
87
|
/**
|
|
41
88
|
* SegmentedControl
|
|
42
|
-
*
|
|
43
|
-
*
|
|
89
|
+
* Picks one option among a few, all visible. It knows no global state:
|
|
90
|
+
* it receives `value` and reports through `onChange`.
|
|
91
|
+
*
|
|
92
|
+
* Gestalt, common region: one track holds every segment, so they read as a
|
|
93
|
+
* single question. Similarity: equal widths and one line each, so no option
|
|
94
|
+
* looks more important. Figure-ground: the selected segment is raised (surface
|
|
95
|
+
* + shadow) above the sunken track; in dark, where shadows vanish, it takes
|
|
96
|
+
* the soft accent fill. Fitts: every segment reaches 44pt.
|
|
97
|
+
*
|
|
98
|
+
* Continuity: the selection is one indicator that slides from the old segment
|
|
99
|
+
* to the new one, so the eye follows the change instead of seeing one fill
|
|
100
|
+
* vanish and another appear. With Reduce Motion it jumps.
|
|
44
101
|
*/
|
|
45
102
|
function SegmentedControlInner<T extends string>({
|
|
46
103
|
options,
|
|
@@ -51,25 +108,88 @@ function SegmentedControlInner<T extends string>({
|
|
|
51
108
|
testID,
|
|
52
109
|
}: SegmentedControlProps<T>) {
|
|
53
110
|
const styles = useStyles();
|
|
111
|
+
const theme = useTheme();
|
|
54
112
|
const allowFontScaling = useAllowFontScaling();
|
|
113
|
+
const reduceMotion = useReduceMotion();
|
|
114
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
115
|
+
const offset = useRef(new Animated.Value(0)).current;
|
|
116
|
+
const placed = useRef(false);
|
|
117
|
+
|
|
118
|
+
const index = options.findIndex((option) => option.value === value);
|
|
119
|
+
const segmentWidth = trackWidth > 0 && options.length > 0 ? (trackWidth - TRACK_PADDING * 2) / options.length : 0;
|
|
120
|
+
const showIndicator = segmentWidth > 0 && index >= 0;
|
|
121
|
+
// transform isn't mirrored in RTL: the indicator starts at the right edge
|
|
122
|
+
// (`start`), so it moves left.
|
|
123
|
+
const target = index * segmentWidth * (I18nManager.isRTL ? -1 : 1);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (!showIndicator) return;
|
|
127
|
+
// First placement (and Reduce Motion) jumps; only a change of value slides.
|
|
128
|
+
if (!placed.current || reduceMotion) {
|
|
129
|
+
placed.current = true;
|
|
130
|
+
offset.setValue(target);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const animation = Animated.timing(offset, {
|
|
134
|
+
toValue: target,
|
|
135
|
+
duration: theme.tokens.duration.base,
|
|
136
|
+
easing: Easing.out(Easing.cubic),
|
|
137
|
+
useNativeDriver: true,
|
|
138
|
+
});
|
|
139
|
+
animation.start();
|
|
140
|
+
return () => animation.stop();
|
|
141
|
+
}, [target, showIndicator, reduceMotion, offset, theme.tokens.duration.base]);
|
|
142
|
+
|
|
143
|
+
// A new width (rotation, resize) places the indicator without animating.
|
|
144
|
+
const onLayout = (e: LayoutChangeEvent) => {
|
|
145
|
+
const width = e.nativeEvent.layout.width;
|
|
146
|
+
if (width === trackWidth) return;
|
|
147
|
+
placed.current = false;
|
|
148
|
+
setTrackWidth(width);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const activeDisabled = disabled || !!options[index]?.disabled;
|
|
55
152
|
|
|
56
153
|
return (
|
|
57
|
-
<View style={[styles.track, style]} accessibilityRole="radiogroup" testID={testID}>
|
|
154
|
+
<View style={[styles.track, style]} accessibilityRole="radiogroup" testID={testID} onLayout={onLayout}>
|
|
155
|
+
{showIndicator && (
|
|
156
|
+
<Animated.View
|
|
157
|
+
pointerEvents="none"
|
|
158
|
+
testID={testID && `${testID}-indicator`}
|
|
159
|
+
style={[
|
|
160
|
+
styles.indicator,
|
|
161
|
+
styles.segmentActive,
|
|
162
|
+
{ width: segmentWidth, transform: [{ translateX: offset }] },
|
|
163
|
+
activeDisabled && styles.segmentDisabled,
|
|
164
|
+
]}
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
58
167
|
{options.map((option) => {
|
|
59
168
|
const active = option.value === value;
|
|
60
169
|
const isDisabled = disabled || !!option.disabled;
|
|
61
170
|
return (
|
|
62
171
|
<TouchableOpacity
|
|
63
172
|
key={option.value}
|
|
64
|
-
style={[
|
|
65
|
-
|
|
173
|
+
style={[
|
|
174
|
+
styles.segment,
|
|
175
|
+
active && !showIndicator && styles.segmentActive,
|
|
176
|
+
isDisabled && styles.segmentDisabled,
|
|
177
|
+
]}
|
|
178
|
+
onPress={() => {
|
|
179
|
+
if (!active) onChange(option.value);
|
|
180
|
+
}}
|
|
66
181
|
disabled={isDisabled}
|
|
182
|
+
hitSlop={HIT_SLOP}
|
|
67
183
|
accessibilityRole="radio"
|
|
68
184
|
accessibilityState={{ selected: active, checked: active, disabled: isDisabled }}
|
|
69
185
|
accessibilityLabel={option.label}
|
|
70
186
|
testID={testID && `${testID}-${option.value}`}
|
|
71
187
|
>
|
|
72
|
-
<Text
|
|
188
|
+
<Text
|
|
189
|
+
allowFontScaling={allowFontScaling}
|
|
190
|
+
numberOfLines={1}
|
|
191
|
+
style={[styles.label, active && styles.labelActive]}
|
|
192
|
+
>
|
|
73
193
|
{option.label}
|
|
74
194
|
</Text>
|
|
75
195
|
</TouchableOpacity>
|
|
@@ -79,6 +199,6 @@ function SegmentedControlInner<T extends string>({
|
|
|
79
199
|
);
|
|
80
200
|
}
|
|
81
201
|
|
|
82
|
-
// memo()
|
|
83
|
-
// `value`
|
|
202
|
+
// memo() loses generics when wrapping: the cast restores them so that
|
|
203
|
+
// `value` and `onChange` stay tied to the union of `options`.
|
|
84
204
|
export const SegmentedControl = memo(SegmentedControlInner) as typeof SegmentedControlInner;
|