@vellira-ui/react-native 2.39.5 → 2.41.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/dist/index.d.ts CHANGED
@@ -6,9 +6,9 @@ export type { RadioGroupProps } from './components/RadioGroup';
6
6
  export { RadioGroup } from './components/RadioGroup';
7
7
  export type { SelectOption, SelectProps } from './components/Select';
8
8
  export { Select } from './components/Select';
9
- export type { TabProps, TabsListProps, TabsPanelProps, TabsProps, } from './components/Tabs';
9
+ export type { TabsContentProps, TabsListProps, TabsProps, TabsTriggerProps, } from './components/Tabs';
10
10
  export { Tabs } from './components/Tabs';
11
- export type { TooltipProps } from './components/Tooltip';
11
+ export type { TooltipArrowProps, TooltipContentProps, TooltipProps, TooltipRootProps, TooltipTriggerProps, } from './components/Tooltip';
12
12
  export { Tooltip } from './components/Tooltip';
13
13
  export type { FormFieldProps } from './patterns/FormField';
14
14
  export { FormField } from './patterns/FormField';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
2
2
  import { Check, ChevronDown, Close, Search } from "@vellira-ui/icons";
3
- import { AccessibilityInfo, ActivityIndicator, Animated, Dimensions, FlatList, Modal as Modal$1, Pressable, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
3
+ import { AccessibilityInfo, ActivityIndicator, Animated, Dimensions, FlatList, Modal as Modal$1, Pressable, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
4
4
  import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/hooks/useControllableState.ts
@@ -378,15 +378,15 @@ const useSelect = ({ value, defaultValue, onValueChange, onChange, options, mult
378
378
  };
379
379
  //#endregion
380
380
  //#region src/hooks/useTabs.ts
381
- const useTabs$1 = ({ activeIndex: controlledActiveIndex, defaultActiveIndex = 0, onChange }) => {
382
- const [activeIndex, setActiveIndex] = useControllableState({
383
- value: controlledActiveIndex,
384
- defaultValue: defaultActiveIndex,
385
- onChange
381
+ const useTabs$1 = ({ value: controlledValue, defaultValue = "", onValueChange }) => {
382
+ const [value, setValue] = useControllableState({
383
+ value: controlledValue,
384
+ defaultValue,
385
+ onChange: onValueChange
386
386
  });
387
387
  return {
388
- activeIndex,
389
- setActiveIndex
388
+ value,
389
+ setValue
390
390
  };
391
391
  };
392
392
  //#endregion
@@ -3650,51 +3650,8 @@ const useTabs = () => {
3650
3650
  };
3651
3651
  TabsContext.displayName = "TabsContext";
3652
3652
  //#endregion
3653
- //#region src/components/Tabs/List/TabsList.styles.ts
3653
+ //#region src/components/Tabs/Content/TabsContent.styles.ts
3654
3654
  const createStyles$7 = (theme) => StyleSheet.create({
3655
- list: {
3656
- flexDirection: "row",
3657
- alignSelf: "stretch",
3658
- width: "100%",
3659
- gap: theme.tokens.spacing[5],
3660
- marginBottom: theme.tokens.spacing[4]
3661
- },
3662
- listPills: {
3663
- backgroundColor: theme.components.tabs.pills.default.bg,
3664
- borderRadius: theme.tokens.radius.lg
3665
- },
3666
- listVertical: {
3667
- flexDirection: "column",
3668
- alignSelf: "flex-start",
3669
- width: 140,
3670
- minWidth: 140,
3671
- maxWidth: 140,
3672
- flexGrow: 0,
3673
- flexShrink: 0,
3674
- gap: theme.tokens.spacing[1],
3675
- marginBottom: 0
3676
- }
3677
- });
3678
- //#endregion
3679
- //#region src/components/Tabs/List/TabsList.tsx
3680
- const TabsList = ({ children, style }) => {
3681
- const styles = useThemeStyles(createStyles$7);
3682
- const { orientation, appearance } = useTabs();
3683
- return /* @__PURE__ */ jsx(View, {
3684
- accessibilityRole: "tablist",
3685
- style: [
3686
- styles.list,
3687
- appearance === "pills" && styles.listPills,
3688
- orientation === "vertical" && styles.listVertical,
3689
- style
3690
- ],
3691
- children
3692
- });
3693
- };
3694
- TabsList.displayName = "TabsList";
3695
- //#endregion
3696
- //#region src/components/Tabs/Panel/TabsPanel.styles.ts
3697
- const createStyles$6 = (theme) => StyleSheet.create({
3698
3655
  panel: {
3699
3656
  width: "100%",
3700
3657
  minWidth: 0,
@@ -3718,39 +3675,135 @@ const createStyles$6 = (theme) => StyleSheet.create({
3718
3675
  }
3719
3676
  });
3720
3677
  //#endregion
3721
- //#region src/components/Tabs/Panel/TabsPanel.tsx
3722
- const TabsPanel = ({ index, children, style }) => {
3723
- const styles = useThemeStyles(createStyles$6);
3724
- const { activeIndex, orientation } = useTabs();
3725
- if (activeIndex !== index) return null;
3678
+ //#region src/components/Tabs/Content/TabsContent.tsx
3679
+ const TabsContent = ({ value, children, forceMount = false, style }) => {
3680
+ const styles = useThemeStyles(createStyles$7);
3681
+ const { value: selectedValue, orientation, keepMounted, lazyMount } = useTabs();
3682
+ const isActive = selectedValue === value;
3683
+ const [hasMounted, setHasMounted] = useState(isActive);
3684
+ useEffect(() => {
3685
+ if (isActive) setHasMounted(true);
3686
+ }, [isActive]);
3687
+ if (!(forceMount || isActive || keepMounted && !lazyMount || keepMounted && lazyMount && hasMounted)) return null;
3726
3688
  return /* @__PURE__ */ jsx(View, {
3727
3689
  style: [
3728
3690
  styles.panel,
3729
3691
  orientation === "vertical" && styles.panelVertical,
3730
3692
  style
3731
3693
  ],
3694
+ accessibilityElementsHidden: !isActive,
3695
+ importantForAccessibility: isActive ? "auto" : "no-hide-descendants",
3696
+ children
3697
+ });
3698
+ };
3699
+ TabsContent.displayName = "Tabs.Content";
3700
+ //#endregion
3701
+ //#region src/components/Tabs/List/TabsIndicator.tsx
3702
+ const TabsIndicator = ({ children, style }) => /* @__PURE__ */ jsx(View, {
3703
+ accessibilityElementsHidden: true,
3704
+ importantForAccessibility: "no-hide-descendants",
3705
+ pointerEvents: "none",
3706
+ style,
3707
+ children
3708
+ });
3709
+ TabsIndicator.displayName = "Tabs.Indicator";
3710
+ //#endregion
3711
+ //#region src/components/Tabs/List/TabsList.styles.ts
3712
+ const createStyles$6 = (theme) => StyleSheet.create({
3713
+ list: {
3714
+ flexDirection: "row",
3715
+ alignSelf: "stretch",
3716
+ width: "100%",
3717
+ gap: theme.tokens.spacing[5],
3718
+ marginBottom: theme.tokens.spacing[6]
3719
+ },
3720
+ listSegmented: {
3721
+ padding: 2,
3722
+ backgroundColor: theme.components.tabs.list.segmentedBg,
3723
+ borderColor: theme.components.tabs.list.border,
3724
+ borderRadius: theme.tokens.radius.xl,
3725
+ borderWidth: 1
3726
+ },
3727
+ listVertical: {
3728
+ flexDirection: "column",
3729
+ alignSelf: "flex-start",
3730
+ width: 156,
3731
+ minWidth: 156,
3732
+ maxWidth: 156,
3733
+ flexGrow: 0,
3734
+ flexShrink: 0,
3735
+ gap: theme.tokens.spacing[1],
3736
+ marginBottom: 0
3737
+ }
3738
+ });
3739
+ //#endregion
3740
+ //#region src/components/Tabs/List/TabsList.tsx
3741
+ const TabsList = ({ children, scrollable: scrollableProp, style }) => {
3742
+ const styles = useThemeStyles(createStyles$6);
3743
+ const { orientation, variant } = useTabs();
3744
+ const scrollable = scrollableProp ?? false;
3745
+ const listStyle = [
3746
+ styles.list,
3747
+ variant === "segmented" && styles.listSegmented,
3748
+ orientation === "vertical" && styles.listVertical,
3749
+ style
3750
+ ];
3751
+ if (scrollable && orientation === "horizontal") return /* @__PURE__ */ jsx(ScrollView, {
3752
+ horizontal: true,
3753
+ accessibilityRole: "tablist",
3754
+ showsHorizontalScrollIndicator: false,
3755
+ contentContainerStyle: listStyle,
3756
+ children
3757
+ });
3758
+ return /* @__PURE__ */ jsx(View, {
3759
+ accessibilityRole: "tablist",
3760
+ style: listStyle,
3732
3761
  children
3733
3762
  });
3734
3763
  };
3735
- TabsPanel.displayName = "TabsPanel";
3764
+ TabsList.displayName = "TabsList";
3736
3765
  //#endregion
3737
- //#region src/components/Tabs/Tab/Tab.styles.ts
3766
+ //#region src/components/Tabs/Trigger/TabsTrigger.styles.ts
3738
3767
  const fontWeight$1 = (value) => value;
3739
3768
  const createStyles$5 = (theme) => StyleSheet.create({
3740
3769
  tab: {
3741
- minHeight: 40,
3770
+ minHeight: 44,
3771
+ minWidth: 44,
3742
3772
  alignItems: "center",
3743
3773
  flexDirection: "row",
3744
3774
  gap: theme.tokens.spacing[2],
3745
3775
  justifyContent: "center",
3746
3776
  paddingHorizontal: theme.tokens.spacing[4],
3747
3777
  paddingVertical: theme.tokens.spacing[2],
3748
- backgroundColor: theme.components.tabs.trigger.default.bg
3778
+ borderWidth: 1
3779
+ },
3780
+ tabSm: {
3781
+ minHeight: 36,
3782
+ paddingHorizontal: theme.tokens.spacing[3],
3783
+ paddingVertical: 6
3784
+ },
3785
+ tabLg: {
3786
+ minHeight: 52,
3787
+ paddingHorizontal: theme.tokens.spacing[5],
3788
+ paddingVertical: theme.tokens.spacing[3]
3789
+ },
3790
+ tabSegmented: {
3791
+ minHeight: 32,
3792
+ paddingVertical: 5,
3793
+ borderRadius: theme.tokens.radius.lg
3794
+ },
3795
+ tabSegmentedSm: {
3796
+ minHeight: 30,
3797
+ paddingVertical: 4
3798
+ },
3799
+ tabSegmentedLg: {
3800
+ minHeight: 40,
3801
+ paddingVertical: 8
3749
3802
  },
3750
3803
  tabVertical: {
3751
3804
  position: "relative",
3752
3805
  width: "100%",
3753
- minHeight: 40,
3806
+ minHeight: 44,
3754
3807
  flexGrow: 0,
3755
3808
  flexShrink: 0,
3756
3809
  justifyContent: "flex-start"
@@ -3763,7 +3816,7 @@ const createStyles$5 = (theme) => StyleSheet.create({
3763
3816
  height: 3,
3764
3817
  backgroundColor: "transparent"
3765
3818
  },
3766
- horizontalIndicatorActive: { backgroundColor: theme.components.tabs.indicator.active.bg },
3819
+ horizontalIndicatorActive: { backgroundColor: "transparent" },
3767
3820
  verticalIndicator: {
3768
3821
  position: "absolute",
3769
3822
  top: 0,
@@ -3772,27 +3825,35 @@ const createStyles$5 = (theme) => StyleSheet.create({
3772
3825
  width: 3,
3773
3826
  backgroundColor: "transparent"
3774
3827
  },
3775
- verticalIndicatorActive: { backgroundColor: theme.components.tabs.indicator.active.bg },
3776
- verticalIndicatorPressed: { backgroundColor: theme.components.tabs.indicator.hover.bg },
3777
- tabHovered: { backgroundColor: theme.components.tabs.trigger.hover.bg },
3778
- tabPressed: { backgroundColor: theme.components.tabs.trigger.active.bg },
3828
+ verticalIndicatorActive: { backgroundColor: "transparent" },
3829
+ verticalIndicatorPressed: { backgroundColor: "transparent" },
3830
+ tabHovered: { backgroundColor: "transparent" },
3831
+ tabPressed: { backgroundColor: "transparent" },
3779
3832
  tabFocused: { borderColor: theme.semantic.focus.ring.color },
3780
- tabDefaultActive: { backgroundColor: theme.components.tabs.trigger.active.bg },
3781
- tabDisabled: { backgroundColor: theme.components.tabs.trigger.disabled.bg },
3833
+ tabDefaultActive: { backgroundColor: "transparent" },
3834
+ tabDisabled: { backgroundColor: theme.components.tabs.disabled.bg },
3782
3835
  tabText: {
3783
3836
  flexShrink: 0,
3784
3837
  textAlign: "center",
3785
3838
  lineHeight: theme.tokens.typography.lineHeight.md,
3786
- color: theme.components.tabs.trigger.default.fg,
3839
+ color: theme.components.tabs.primary.trigger.default.fg,
3787
3840
  fontFamily: theme.tokens.typography.family.regular,
3788
3841
  fontSize: theme.tokens.typography.size.md,
3789
3842
  fontWeight: fontWeight$1(theme.tokens.typography.weight.regular)
3790
3843
  },
3791
- tabTextHover: { color: theme.components.tabs.trigger.hover.fg },
3792
- tabTextPressed: { color: theme.components.tabs.trigger.active.fg },
3793
- tabTextDisabled: { color: theme.components.tabs.trigger.disabled.fg },
3794
- tabTextActive: { color: theme.components.tabs.trigger.active.fg },
3795
- tabTextPillsActive: { color: theme.components.tabs.pills.active.fg },
3844
+ tabTextSm: {
3845
+ fontSize: theme.tokens.typography.size.sm,
3846
+ lineHeight: theme.tokens.typography.lineHeight.sm
3847
+ },
3848
+ tabTextLg: {
3849
+ fontSize: theme.tokens.typography.size.lg,
3850
+ lineHeight: theme.tokens.typography.lineHeight.lg
3851
+ },
3852
+ tabTextHover: { color: theme.components.tabs.primary.trigger.hover.fg },
3853
+ tabTextPressed: { color: theme.components.tabs.primary.trigger.active.fg },
3854
+ tabTextDisabled: { color: theme.components.tabs.disabled.fg },
3855
+ tabTextActive: { color: theme.components.tabs.primary.trigger.active.fg },
3856
+ tabTextPillsActive: { color: theme.components.tabs.primary.pills.active.fg },
3796
3857
  tabPills: {
3797
3858
  minHeight: 36,
3798
3859
  paddingHorizontal: theme.tokens.spacing[3],
@@ -3800,64 +3861,145 @@ const createStyles$5 = (theme) => StyleSheet.create({
3800
3861
  borderRadius: theme.tokens.radius.md
3801
3862
  },
3802
3863
  tabPillsActive: {
3803
- backgroundColor: theme.components.tabs.pills.active.bg,
3864
+ backgroundColor: "transparent",
3804
3865
  borderRadius: theme.tokens.radius.md
3805
3866
  },
3806
- tabPillsHover: { backgroundColor: theme.components.tabs.pills.hover.bg },
3807
- tabPillsPressed: { backgroundColor: theme.components.tabs.pills.active.bg },
3867
+ tabPillsHover: { backgroundColor: "transparent" },
3868
+ tabPillsPressed: { backgroundColor: "transparent" },
3808
3869
  tabIcon: {
3809
3870
  alignItems: "center",
3810
3871
  justifyContent: "center"
3811
3872
  },
3812
- tabIconHover: { color: theme.components.tabs.trigger.hover.fg },
3813
- tabIconPressed: { color: theme.components.tabs.trigger.active.fg },
3814
- tabIconActive: { color: theme.components.tabs.trigger.active.fg },
3815
- tabIconPillsActive: { color: theme.components.tabs.pills.active.fg }
3873
+ tabDescription: {
3874
+ fontSize: theme.tokens.typography.size.sm,
3875
+ color: theme.components.tabs.primary.trigger.default.fg
3876
+ },
3877
+ tabBadge: {
3878
+ minHeight: 20,
3879
+ minWidth: 20,
3880
+ alignItems: "center",
3881
+ justifyContent: "center",
3882
+ paddingHorizontal: theme.tokens.spacing[2],
3883
+ backgroundColor: theme.components.tabs.primary.pills.active.bg,
3884
+ borderRadius: theme.tokens.radius.full
3885
+ },
3886
+ tabBadgeLg: {
3887
+ minHeight: 24,
3888
+ minWidth: 24
3889
+ },
3890
+ tabBadgeText: {
3891
+ color: theme.components.tabs.primary.pills.active.fg,
3892
+ fontSize: theme.tokens.typography.size.sm,
3893
+ fontWeight: fontWeight$1(theme.tokens.typography.weight.medium)
3894
+ },
3895
+ tabIconHover: { color: theme.components.tabs.primary.trigger.hover.fg },
3896
+ tabIconPressed: { color: theme.components.tabs.primary.trigger.active.fg },
3897
+ tabIconActive: { color: theme.components.tabs.primary.trigger.active.fg },
3898
+ tabIconPillsActive: { color: theme.components.tabs.primary.pills.active.fg }
3816
3899
  });
3817
3900
  //#endregion
3818
- //#region src/components/Tabs/Tab/Tab.tsx
3819
- const Tab = ({ index, children, icon, disabled, style, textStyle }) => {
3901
+ //#region src/components/Tabs/Trigger/TabsBadge.tsx
3902
+ const TabsBadge = ({ children, style }) => {
3903
+ const styles = useThemeStyles(createStyles$5);
3904
+ const { color } = useTabs();
3905
+ const { theme } = useTheme();
3906
+ const palette = theme.components.tabs[color];
3907
+ return /* @__PURE__ */ jsx(View, {
3908
+ style: [
3909
+ styles.tabBadge,
3910
+ { backgroundColor: palette.pills.active.bg },
3911
+ style
3912
+ ],
3913
+ children: /* @__PURE__ */ jsx(Text, {
3914
+ style: [styles.tabBadgeText, { color: palette.pills.active.fg }],
3915
+ children
3916
+ })
3917
+ });
3918
+ };
3919
+ TabsBadge.displayName = "Tabs.Badge";
3920
+ //#endregion
3921
+ //#region src/components/Tabs/Trigger/TabsIcon.tsx
3922
+ const TabsIcon = ({ children, style }) => {
3923
+ return /* @__PURE__ */ jsx(View, {
3924
+ style: [useThemeStyles(createStyles$5).tabIcon, style],
3925
+ children
3926
+ });
3927
+ };
3928
+ TabsIcon.displayName = "Tabs.Icon";
3929
+ //#endregion
3930
+ //#region src/components/Tabs/Trigger/TabsTrigger.tsx
3931
+ const TabsTrigger = ({ value, children, icon, badge, description, disabled, style, textStyle }) => {
3820
3932
  const { theme } = useTheme();
3821
3933
  const styles = useThemeStyles(createStyles$5);
3822
- const { activeIndex, appearance, orientation, setActiveIndex } = useTabs();
3823
- const isActive = activeIndex === index;
3824
- const isPills = appearance === "pills";
3825
- const isUnderline = appearance === "underline";
3826
- const isDefault = appearance === "default";
3934
+ const { value: selectedValue, variant, color, size, orientation, disabled: rootDisabled, setValue, registerTrigger } = useTabs();
3935
+ const isDisabled = rootDisabled || disabled;
3936
+ const isActive = selectedValue === value;
3937
+ const isPills = variant === "pills";
3938
+ const isLine = variant === "line";
3939
+ const isSegmented = variant === "segmented";
3827
3940
  const isVertical = orientation === "vertical";
3828
- const iconColor = isPills && isActive ? theme.components.tabs.pills.active.fg : isActive ? theme.components.tabs.trigger.active.fg : theme.components.tabs.trigger.default.fg;
3941
+ const isSm = size === "sm";
3942
+ const isLg = size === "lg";
3943
+ const palette = theme.components.tabs[color];
3944
+ const state = isDisabled ? theme.components.tabs.disabled : isPills ? isActive ? palette.pills.active : palette.pills.default : isActive ? palette.trigger.active : palette.trigger.default;
3945
+ const pressedState = isDisabled ? state : isPills ? isActive ? palette.pills.active : palette.pills.hover : isActive ? palette.trigger.active : palette.trigger.hover;
3946
+ useEffect(() => {
3947
+ registerTrigger(value, Boolean(isDisabled), true);
3948
+ return () => {
3949
+ registerTrigger(value, Boolean(isDisabled), false);
3950
+ };
3951
+ }, [
3952
+ isDisabled,
3953
+ registerTrigger,
3954
+ value
3955
+ ]);
3956
+ const iconColor = isPills && isActive ? palette.pills.active.fg : isActive ? palette.trigger.active.fg : state.fg;
3829
3957
  const renderedIcon = isValidElement(icon) ? cloneElement(icon, { color: iconColor }) : icon;
3830
3958
  return /* @__PURE__ */ jsx(Pressable, {
3831
- disabled,
3959
+ disabled: isDisabled,
3832
3960
  accessibilityRole: "tab",
3833
3961
  accessibilityState: {
3834
3962
  selected: isActive,
3835
- disabled
3963
+ disabled: isDisabled
3836
3964
  },
3837
3965
  onPress: () => {
3838
- if (!disabled && !isActive) setActiveIndex(index);
3966
+ if (!isDisabled && !isActive) setValue(value);
3839
3967
  },
3840
3968
  style: ({ pressed }) => [
3841
3969
  styles.tab,
3970
+ isSm && styles.tabSm,
3971
+ isLg && styles.tabLg,
3842
3972
  isVertical && styles.tabVertical,
3843
3973
  isPills && styles.tabPills,
3844
- isPills && isActive && styles.tabPillsActive,
3845
- pressed && !disabled && styles.tabPressed,
3846
- isDefault && isActive && styles.tabDefaultActive,
3847
- disabled && styles.tabDisabled,
3974
+ isSegmented && styles.tabSegmented,
3975
+ isSegmented && isSm && styles.tabSegmentedSm,
3976
+ isSegmented && isLg && styles.tabSegmentedLg,
3977
+ isPills && isActive && {
3978
+ borderColor: palette.pills.active.border,
3979
+ backgroundColor: palette.pills.active.bg
3980
+ },
3981
+ {
3982
+ borderColor: state.border,
3983
+ backgroundColor: pressed ? pressedState.bg : state.bg
3984
+ },
3985
+ isSegmented && isActive && {
3986
+ borderColor: palette.segmented.active.border,
3987
+ backgroundColor: palette.segmented.active.bg
3988
+ },
3989
+ isDisabled && styles.tabDisabled,
3848
3990
  style
3849
3991
  ],
3850
3992
  children: ({ pressed }) => /* @__PURE__ */ jsxs(Fragment, { children: [
3851
- isUnderline && !isVertical && /* @__PURE__ */ jsx(View, {
3993
+ isLine && !isVertical && /* @__PURE__ */ jsx(View, {
3852
3994
  pointerEvents: "none",
3853
- style: [styles.horizontalIndicator, isActive && styles.horizontalIndicatorActive]
3995
+ style: [styles.horizontalIndicator, isActive && { backgroundColor: palette.indicator.bg }]
3854
3996
  }),
3855
- isUnderline && isVertical && /* @__PURE__ */ jsx(View, {
3997
+ isLine && isVertical && /* @__PURE__ */ jsx(View, {
3856
3998
  pointerEvents: "none",
3857
3999
  style: [
3858
4000
  styles.verticalIndicator,
3859
- isActive && styles.verticalIndicatorActive,
3860
- pressed && !isActive && !disabled && styles.verticalIndicatorPressed
4001
+ isActive && { backgroundColor: palette.indicator.bg },
4002
+ pressed && !isActive && !isDisabled && { backgroundColor: palette.indicator.hoverBg }
3861
4003
  ]
3862
4004
  }),
3863
4005
  icon != null && /* @__PURE__ */ jsx(View, {
@@ -3869,17 +4011,40 @@ const Tab = ({ index, children, icon, disabled, style, textStyle }) => {
3869
4011
  ellipsizeMode: "tail",
3870
4012
  style: [
3871
4013
  styles.tabText,
3872
- isPills && isActive && styles.tabTextPillsActive,
3873
- !isPills && isActive && styles.tabTextActive,
3874
- disabled && styles.tabTextDisabled,
4014
+ isSm && styles.tabTextSm,
4015
+ isLg && styles.tabTextLg,
4016
+ { color: isSegmented && isActive ? palette.segmented.active.fg : state.fg },
3875
4017
  textStyle
3876
4018
  ],
3877
4019
  children
4020
+ }),
4021
+ description != null && /* @__PURE__ */ jsx(Text, {
4022
+ numberOfLines: 2,
4023
+ ellipsizeMode: "tail",
4024
+ style: [
4025
+ styles.tabText,
4026
+ styles.tabDescription,
4027
+ isSm && styles.tabTextSm,
4028
+ isLg && styles.tabTextLg,
4029
+ { color: isDisabled ? theme.components.tabs.disabled.fg : state.fg }
4030
+ ],
4031
+ children: description
4032
+ }),
4033
+ badge != null && /* @__PURE__ */ jsx(View, {
4034
+ style: [
4035
+ styles.tabBadge,
4036
+ isLg && styles.tabBadgeLg,
4037
+ { backgroundColor: palette.pills.active.bg }
4038
+ ],
4039
+ children: /* @__PURE__ */ jsx(Text, {
4040
+ style: [styles.tabBadgeText, { color: palette.pills.active.fg }],
4041
+ children: badge
4042
+ })
3878
4043
  })
3879
4044
  ] })
3880
4045
  });
3881
4046
  };
3882
- Tab.displayName = "Tab";
4047
+ TabsTrigger.displayName = "Tabs.Trigger";
3883
4048
  //#endregion
3884
4049
  //#region src/components/Tabs/Tabs.styles.ts
3885
4050
  const createStyles$4 = (theme) => StyleSheet.create({
@@ -3896,26 +4061,76 @@ const createStyles$4 = (theme) => StyleSheet.create({
3896
4061
  }
3897
4062
  });
3898
4063
  //#endregion
3899
- //#region src/components/Tabs/Tabs.tsx
3900
- const TabsRoot = ({ children, activeIndex: controlledActiveIndex, defaultActiveIndex = 0, onChange, orientation = "horizontal", appearance = "pills", style }) => {
4064
+ //#region src/components/Tabs/Root/TabsRoot.tsx
4065
+ const TabsRoot = ({ children, value: controlledValue, defaultValue, onValueChange, orientation = "horizontal", activationMode = "automatic", variant = "line", color = "primary", size = "md", keepMounted = false, lazyMount = false, disabled = false, style }) => {
3901
4066
  const styles = useThemeStyles(createStyles$4);
3902
- const { activeIndex, setActiveIndex } = useTabs$1({
3903
- activeIndex: controlledActiveIndex,
3904
- defaultActiveIndex,
3905
- onChange,
3906
- orientation
4067
+ const isControlled = controlledValue !== void 0;
4068
+ const [version, setVersion] = useState(0);
4069
+ const triggersRef = useRef([]);
4070
+ const { value, setValue } = useTabs$1({
4071
+ value: controlledValue,
4072
+ defaultValue,
4073
+ onValueChange
3907
4074
  });
4075
+ const registerTrigger = useCallback((triggerValue, triggerDisabled, mounted) => {
4076
+ const existingIndex = triggersRef.current.findIndex((tab) => tab.value === triggerValue);
4077
+ if (!mounted) {
4078
+ if (existingIndex >= 0) {
4079
+ triggersRef.current.splice(existingIndex, 1);
4080
+ setVersion((current) => current + 1);
4081
+ }
4082
+ return;
4083
+ }
4084
+ const nextTab = {
4085
+ value: triggerValue,
4086
+ disabled: triggerDisabled
4087
+ };
4088
+ if (existingIndex >= 0) triggersRef.current[existingIndex] = nextTab;
4089
+ else triggersRef.current.push(nextTab);
4090
+ setVersion((current) => current + 1);
4091
+ }, []);
4092
+ useEffect(() => {
4093
+ const enabledTabs = triggersRef.current.filter((tab) => !tab.disabled);
4094
+ const selectedExists = triggersRef.current.some((tab) => tab.value === value);
4095
+ if (isControlled) {
4096
+ if (value && !selectedExists && triggersRef.current.length > 0) console.warn(`Tabs: value "${value}" does not match any Tabs.Trigger.`);
4097
+ return;
4098
+ }
4099
+ if (!value || !selectedExists) {
4100
+ const firstEnabledValue = enabledTabs[0]?.value;
4101
+ if (firstEnabledValue) setValue(firstEnabledValue);
4102
+ }
4103
+ }, [
4104
+ isControlled,
4105
+ setValue,
4106
+ value,
4107
+ version
4108
+ ]);
3908
4109
  return /* @__PURE__ */ jsx(TabsProvider, {
3909
4110
  value: useMemo(() => ({
3910
- activeIndex,
3911
- appearance,
4111
+ value,
4112
+ setValue,
3912
4113
  orientation,
3913
- setActiveIndex
4114
+ activationMode,
4115
+ variant,
4116
+ color,
4117
+ size,
4118
+ keepMounted,
4119
+ lazyMount,
4120
+ disabled,
4121
+ registerTrigger
3914
4122
  }), [
3915
- activeIndex,
3916
- appearance,
4123
+ activationMode,
4124
+ color,
4125
+ disabled,
4126
+ keepMounted,
4127
+ lazyMount,
3917
4128
  orientation,
3918
- setActiveIndex
4129
+ registerTrigger,
4130
+ setValue,
4131
+ size,
4132
+ value,
4133
+ variant
3919
4134
  ]),
3920
4135
  children: /* @__PURE__ */ jsx(View, {
3921
4136
  style: [
@@ -3927,14 +4142,62 @@ const TabsRoot = ({ children, activeIndex: controlledActiveIndex, defaultActiveI
3927
4142
  })
3928
4143
  });
3929
4144
  };
3930
- TabsRoot.displayName = "TabsRoot";
4145
+ TabsRoot.displayName = "Tabs";
3931
4146
  //#endregion
3932
- //#region src/components/Tabs/index.ts
4147
+ //#region src/components/Tabs/Tabs.tsx
3933
4148
  const Tabs = Object.assign(TabsRoot, {
3934
4149
  List: TabsList,
3935
- Tab,
3936
- Panel: TabsPanel
4150
+ Trigger: TabsTrigger,
4151
+ Content: TabsContent,
4152
+ Indicator: TabsIndicator,
4153
+ Icon: TabsIcon,
4154
+ Badge: TabsBadge
3937
4155
  });
4156
+ Tabs.displayName = "Tabs";
4157
+ //#endregion
4158
+ //#region src/components/Tooltip/internal/TooltipContext.tsx
4159
+ const TooltipContext = createContext(null);
4160
+ const TooltipProvider = TooltipContext.Provider;
4161
+ const useTooltipContext = () => {
4162
+ const context = useContext(TooltipContext);
4163
+ if (!context) throw new Error("Tooltip components must be used inside <Tooltip>.");
4164
+ return context;
4165
+ };
4166
+ TooltipContext.displayName = "TooltipContext";
4167
+ //#endregion
4168
+ //#region src/components/Tooltip/Arrow/TooltipArrow.tsx
4169
+ const TooltipArrow = ({ style }) => {
4170
+ const { theme } = useTheme();
4171
+ const tooltip = useTooltipContext();
4172
+ const size = theme.components.tooltip.arrow.size;
4173
+ const side = tooltip.placement.split("-")[0];
4174
+ const staticSide = {
4175
+ top: "bottom",
4176
+ right: "left",
4177
+ bottom: "top",
4178
+ left: "right"
4179
+ }[side];
4180
+ const crossAxisStyle = side === "left" || side === "right" ? {
4181
+ top: "50%",
4182
+ marginTop: -size / 2
4183
+ } : {
4184
+ left: "50%",
4185
+ marginLeft: -size / 2
4186
+ };
4187
+ return /* @__PURE__ */ jsx(View, {
4188
+ pointerEvents: "none",
4189
+ style: [{
4190
+ position: "absolute",
4191
+ width: size,
4192
+ height: size,
4193
+ backgroundColor: theme.components.tooltip.arrow.bg,
4194
+ transform: [{ rotate: "45deg" }],
4195
+ [staticSide]: -size / 2,
4196
+ ...crossAxisStyle
4197
+ }, style]
4198
+ });
4199
+ };
4200
+ TooltipArrow.displayName = "Tooltip.Arrow";
3938
4201
  //#endregion
3939
4202
  //#region src/components/Tooltip/Tooltip.styles.ts
3940
4203
  const createStyles$3 = (theme) => StyleSheet.create({
@@ -3943,13 +4206,13 @@ const createStyles$3 = (theme) => StyleSheet.create({
3943
4206
  bubble: {
3944
4207
  position: "absolute",
3945
4208
  zIndex: 1e3,
3946
- maxWidth: 280,
3947
- paddingHorizontal: theme.tokens.spacing[3],
3948
- paddingVertical: theme.tokens.spacing[2],
4209
+ maxWidth: theme.components.tooltip.content.maxWidth,
4210
+ paddingHorizontal: theme.components.tooltip.content.paddingX,
4211
+ paddingVertical: theme.components.tooltip.content.paddingY,
3949
4212
  backgroundColor: theme.components.tooltip.content.bg,
3950
4213
  borderColor: theme.components.tooltip.content.border,
3951
- borderRadius: theme.tokens.radius.sm,
3952
- borderWidth: 1,
4214
+ borderRadius: theme.components.tooltip.content.radius,
4215
+ borderWidth: theme.components.tooltip.content.borderWidth,
3953
4216
  shadowColor: theme.tokens.shadows.md.color,
3954
4217
  shadowOffset: {
3955
4218
  width: theme.tokens.shadows.md.x,
@@ -3963,83 +4226,192 @@ const createStyles$3 = (theme) => StyleSheet.create({
3963
4226
  flexShrink: 1,
3964
4227
  color: theme.components.tooltip.content.fg,
3965
4228
  fontFamily: theme.tokens.typography.family.regular,
3966
- fontSize: theme.tokens.typography.size.sm,
3967
- lineHeight: theme.tokens.typography.lineHeight.sm,
4229
+ fontSize: theme.components.tooltip.content.fontSize,
4230
+ lineHeight: theme.components.tooltip.content.lineHeight,
3968
4231
  textAlign: "center"
3969
4232
  }
3970
4233
  });
3971
4234
  //#endregion
3972
- //#region src/components/Tooltip/Tooltip.tsx
3973
- function Tooltip({ children, content, placement = "top", disabled = false, maxWidth = 240, delay, style, contentStyle, textStyle }) {
4235
+ //#region src/components/Tooltip/Content/TooltipContent.tsx
4236
+ const TooltipContent = ({ children, forceMount = false, style, textStyle }) => {
3974
4237
  const styles = useThemeStyles(createStyles$3);
3975
- const overlayId = useId();
3976
- const [visible, setVisible] = useState(false);
4238
+ const tooltip = useTooltipContext();
4239
+ const visible = tooltip.open && !tooltip.disabled;
4240
+ if (!forceMount && !visible) return null;
4241
+ const bubble = /* @__PURE__ */ jsx(View, {
4242
+ nativeID: tooltip.contentId,
4243
+ pointerEvents: "none",
4244
+ style: [
4245
+ styles.bubble,
4246
+ {
4247
+ top: tooltip.position.top,
4248
+ left: tooltip.position.left
4249
+ },
4250
+ !visible && { display: "none" },
4251
+ style
4252
+ ],
4253
+ onLayout: tooltip.onFloatingLayout,
4254
+ children: Children.map(children, (child) => typeof child === "string" || typeof child === "number" ? /* @__PURE__ */ jsx(Text, {
4255
+ style: [styles.text, textStyle],
4256
+ children: child
4257
+ }) : child)
4258
+ });
4259
+ if (!visible) return bubble;
4260
+ return /* @__PURE__ */ jsx(Modal$1, {
4261
+ visible,
4262
+ transparent: true,
4263
+ animationType: "fade",
4264
+ onRequestClose: tooltip.requestClose,
4265
+ children: /* @__PURE__ */ jsx(Pressable, {
4266
+ style: styles.overlay,
4267
+ onPress: tooltip.requestOutsideClose,
4268
+ children: bubble
4269
+ })
4270
+ });
4271
+ };
4272
+ TooltipContent.displayName = "Tooltip.Content";
4273
+ //#endregion
4274
+ //#region src/components/Tooltip/internal/useTooltipDelay.ts
4275
+ const resolveTooltipDelay = (delay) => {
4276
+ if (typeof delay === "number") return {
4277
+ open: delay,
4278
+ close: 2500
4279
+ };
4280
+ return {
4281
+ open: delay?.open ?? 0,
4282
+ close: delay?.close ?? 2500
4283
+ };
4284
+ };
4285
+ //#endregion
4286
+ //#region src/components/Tooltip/Root/TooltipRoot.tsx
4287
+ const TooltipRoot = ({ children, open: openProp, defaultOpen = false, onOpenChange, placement = "top", disabled = false, delay, offset = 8, closeOnOutsidePress = true, style }) => {
4288
+ const contentId = `${useId()}-content`;
3977
4289
  const triggerRef = useRef(null);
3978
4290
  const closeTimerRef = useRef(null);
3979
- const { position, updatePosition, onFloatingLayout } = useNativeFloatingPosition(placement, 8);
3980
- const hideDelay = delay?.close ?? 2500;
3981
- const clearCloseTimer = () => {
4291
+ const resolvedDelay = useMemo(() => resolveTooltipDelay(delay), [delay]);
4292
+ const [open, setOpenState] = useControllableState({
4293
+ value: openProp,
4294
+ defaultValue: defaultOpen,
4295
+ onChange: onOpenChange
4296
+ });
4297
+ const { position, updatePosition, onFloatingLayout } = useNativeFloatingPosition(placement, offset);
4298
+ const clearCloseTimer = useCallback(() => {
3982
4299
  if (closeTimerRef.current) {
3983
4300
  clearTimeout(closeTimerRef.current);
3984
4301
  closeTimerRef.current = null;
3985
4302
  }
3986
- };
3987
- const dismiss = useNativeDismiss({
3988
- id: overlayId,
3989
- visible: visible && !disabled,
3990
- onClose: () => {
3991
- clearCloseTimer();
3992
- setVisible(false);
3993
- }
3994
- });
3995
- const showTooltip = () => {
4303
+ }, []);
4304
+ const setOpen = useCallback((nextOpen) => {
4305
+ if (disabled && nextOpen) return;
4306
+ setOpenState(nextOpen);
4307
+ }, [disabled, setOpenState]);
4308
+ const hide = useCallback(() => {
4309
+ clearCloseTimer();
4310
+ setOpen(false);
4311
+ }, [clearCloseTimer, setOpen]);
4312
+ const show = useCallback(() => {
3996
4313
  if (disabled) return;
3997
4314
  clearCloseTimer();
3998
4315
  updatePosition(triggerRef);
3999
- setVisible(true);
4000
- closeTimerRef.current = setTimeout(() => {
4001
- setVisible(false);
4002
- closeTimerRef.current = null;
4003
- }, hideDelay);
4004
- };
4316
+ const openTooltip = () => {
4317
+ setOpen(true);
4318
+ closeTimerRef.current = setTimeout(() => {
4319
+ setOpen(false);
4320
+ closeTimerRef.current = null;
4321
+ }, resolvedDelay.close);
4322
+ };
4323
+ if (resolvedDelay.open > 0) {
4324
+ closeTimerRef.current = setTimeout(openTooltip, resolvedDelay.open);
4325
+ return;
4326
+ }
4327
+ openTooltip();
4328
+ }, [
4329
+ clearCloseTimer,
4330
+ disabled,
4331
+ resolvedDelay,
4332
+ setOpen,
4333
+ updatePosition
4334
+ ]);
4335
+ const dismiss = useNativeDismiss({
4336
+ id: contentId,
4337
+ visible: open && !disabled,
4338
+ closeOnOutsidePress,
4339
+ onClose: hide
4340
+ });
4341
+ useEffect(() => {
4342
+ if (disabled && open) hide();
4343
+ }, [
4344
+ disabled,
4345
+ hide,
4346
+ open
4347
+ ]);
4005
4348
  useEffect(() => {
4006
4349
  return clearCloseTimer;
4007
- }, []);
4008
- return /* @__PURE__ */ jsxs(View, {
4009
- style: [styles.root, style],
4010
- children: [/* @__PURE__ */ jsx(Pressable, {
4011
- ref: triggerRef,
4012
- onLongPress: showTooltip,
4350
+ }, [clearCloseTimer]);
4351
+ return /* @__PURE__ */ jsx(TooltipProvider, {
4352
+ value: useMemo(() => ({
4353
+ contentId,
4354
+ open,
4355
+ disabled,
4356
+ placement,
4357
+ position,
4358
+ triggerRef,
4359
+ setOpen,
4360
+ show,
4361
+ hide,
4362
+ requestClose: dismiss.requestClose,
4363
+ requestOutsideClose: dismiss.requestOutsideClose,
4364
+ onFloatingLayout
4365
+ }), [
4366
+ contentId,
4367
+ disabled,
4368
+ dismiss.requestClose,
4369
+ dismiss.requestOutsideClose,
4370
+ hide,
4371
+ open,
4372
+ placement,
4373
+ position,
4374
+ setOpen,
4375
+ show,
4376
+ onFloatingLayout
4377
+ ]),
4378
+ children: /* @__PURE__ */ jsx(View, {
4379
+ style,
4013
4380
  children
4014
- }), /* @__PURE__ */ jsx(Modal$1, {
4015
- visible: visible && !disabled,
4016
- transparent: true,
4017
- animationType: "fade",
4018
- onRequestClose: dismiss.requestClose,
4019
- children: /* @__PURE__ */ jsx(Pressable, {
4020
- style: styles.overlay,
4021
- onPress: dismiss.requestOutsideClose,
4022
- children: /* @__PURE__ */ jsx(View, {
4023
- pointerEvents: "none",
4024
- style: [
4025
- styles.bubble,
4026
- {
4027
- maxWidth,
4028
- top: position.top,
4029
- left: position.left
4030
- },
4031
- contentStyle
4032
- ],
4033
- onLayout: onFloatingLayout,
4034
- children: typeof content === "string" ? /* @__PURE__ */ jsx(Text, {
4035
- style: [styles.text, textStyle],
4036
- children: content
4037
- }) : content
4038
- })
4039
- })
4040
- })]
4381
+ })
4041
4382
  });
4042
- }
4383
+ };
4384
+ TooltipRoot.displayName = "Tooltip.Root";
4385
+ //#endregion
4386
+ //#region src/components/Tooltip/Trigger/TooltipTrigger.tsx
4387
+ const TooltipTrigger = ({ children, disabled, onLongPress, style, ...props }) => {
4388
+ const tooltip = useTooltipContext();
4389
+ const isDisabled = tooltip.disabled || disabled;
4390
+ return /* @__PURE__ */ jsx(Pressable, {
4391
+ ...props,
4392
+ ref: tooltip.triggerRef,
4393
+ accessibilityState: {
4394
+ ...props.accessibilityState,
4395
+ disabled: isDisabled || props.accessibilityState?.disabled
4396
+ },
4397
+ disabled: isDisabled,
4398
+ onLongPress: (event) => {
4399
+ onLongPress?.(event);
4400
+ if (event.defaultPrevented) return;
4401
+ tooltip.show();
4402
+ },
4403
+ style,
4404
+ children
4405
+ });
4406
+ };
4407
+ TooltipTrigger.displayName = "Tooltip.Trigger";
4408
+ //#endregion
4409
+ //#region src/components/Tooltip/Tooltip.tsx
4410
+ const Tooltip = Object.assign(TooltipRoot, {
4411
+ Trigger: TooltipTrigger,
4412
+ Content: TooltipContent,
4413
+ Arrow: TooltipArrow
4414
+ });
4043
4415
  Tooltip.displayName = "Tooltip";
4044
4416
  //#endregion
4045
4417
  //#region src/utils/devWarning.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.39.5",
3
+ "version": "2.41.0",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",