@ankhorage/surface 9.0.14 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @ankhorage/surface
2
2
 
3
+ ## 9.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bea3e43: Add trailing tab content and opt-in equal-width TabList fill distribution for badges and responsive tab bars.
8
+
9
+ ## 9.0.15
10
+
11
+ ### Patch Changes
12
+
13
+ - cad0524: Update Ankhorage dependencies: `@ankhorage/utility`.
14
+
3
15
  ## 9.0.14
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/surface
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v9.0.14](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v9.1.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Cross-platform UI foundation for React Native and React Native Web. Provides theming, layout primitives, and core building blocks for modern apps.
9
9
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { TabProps } from '../../../../types/tabs';
3
3
  /*** Renders one accessible selectable tab inside a Tabs context. */
4
- export declare function Tab({ value, children, disabled, interactionPolicy, testID, }: TabProps): React.JSX.Element;
4
+ export declare function Tab({ value, children, disabled, interactionPolicy, testID, trailing, }: TabProps): React.JSX.Element;
5
5
  //# sourceMappingURL=Tab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tab.d.ts","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/Tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAOvD,oEAAoE;AACpE,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,iBAA6B,EAC7B,MAAM,GACP,EAAE,QAAQ,qBAsCV"}
1
+ {"version":3,"file":"Tab.d.ts","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/Tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAOvD,oEAAoE;AACpE,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,iBAA6B,EAC7B,MAAM,EACN,QAAQ,GACT,EAAE,QAAQ,qBAoCV"}
@@ -6,7 +6,7 @@ import { Text } from '../../../typography/public';
6
6
  import { useTabRegistration } from '../../composition/useTabRegistration';
7
7
  import { useTabsContext } from '../../composition/useTabsContext';
8
8
  /*** Renders one accessible selectable tab inside a Tabs context. */
9
- export function Tab({ value, children, disabled = false, interactionPolicy = 'enabled', testID, }) {
9
+ export function Tab({ value, children, disabled = false, interactionPolicy = 'enabled', testID, trailing, }) {
10
10
  const { theme } = useTheme();
11
11
  const { activeValue, getPanelId, getTabId, setActiveValue, setFocusedValue } = useTabsContext();
12
12
  const pressableRef = useTabRegistration({ disabled, value });
@@ -20,12 +20,22 @@ export function Tab({ value, children, disabled = false, interactionPolicy = 'en
20
20
  : theme.semantics.border.default,
21
21
  disabled,
22
22
  })}>
23
- <Text color={selected ? 'primary' : undefined} variant="label" weight="medium">
24
- {children}
25
- </Text>
23
+ {renderTabContent(children, trailing, selected)}
26
24
  </View>
27
25
  </Pressable>);
28
26
  }
27
+ /*** Renders the canonical tab label with optional trailing content. */
28
+ function renderTabContent(children, trailing, selected) {
29
+ const label = (<Text color={selected ? 'primary' : undefined} variant="label" weight="medium">
30
+ {children}
31
+ </Text>);
32
+ if (trailing === undefined)
33
+ return label;
34
+ return (<View align="center" direction="row" gap="xs">
35
+ {label}
36
+ {trailing}
37
+ </View>);
38
+ }
29
39
  /*** Resolves active and disabled visual state without inline JSX styles. */
30
40
  function resolveTabStyle({ borderColor, disabled, }) {
31
41
  return { borderBottomColor: borderColor, borderBottomWidth: 2, opacity: disabled ? 0.64 : 1 };
@@ -1 +1 @@
1
- {"version":3,"file":"Tab.js","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/Tab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,4BAA4B,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAElE,oEAAoE;AACpE,MAAM,UAAU,GAAG,CAAC,EAClB,KAAK,EACL,QAAQ,EACR,QAAQ,GAAG,KAAK,EAChB,iBAAiB,GAAG,SAAS,EAC7B,MAAM,GACG;IACT,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAC7B,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,GAAG,cAAc,EAAE,CAAC;IAChG,MAAM,YAAY,GAAG,kBAAkB,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,WAAW,KAAK,KAAK,CAAC;IACvC,MAAM,OAAO,GAAG,iBAAiB,KAAK,SAAS,CAAC;IAChD,MAAM,OAAO,GAAG,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAE9E,OAAO,CACL,CAAC,SAAS,CACR,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAC9B,iBAAiB,CAAC,KAAK,CACvB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAC3C,aAAa,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CACjC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CACnB,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAC1B,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CACzC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CACtC,OAAO,CAAC,CAAC,OAAO,CAAC,CACjB,GAAG,CAAC,CAAC,YAAY,CAAC,CAClB,MAAM,CAAC,CAAC,MAAM,CAAC,CAEf;MAAA,CAAC,IAAI,CACH,EAAE,CAAC,GAAG,CACN,EAAE,CAAC,GAAG,CACN,KAAK,CAAC,CAAC,eAAe,CAAC;YACrB,WAAW,EAAE,QAAQ;gBACnB,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI;gBACrC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO;YAClC,QAAQ;SACT,CAAC,CAAC,CAEH;QAAA,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAC5E;UAAA,CAAC,QAAQ,CACX;QAAA,EAAE,IAAI,CACR;MAAA,EAAE,IAAI,CACR;IAAA,EAAE,SAAS,CAAC,CACb,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,SAAS,eAAe,CAAC,EACvB,WAAW,EACX,QAAQ,GAIT;IACC,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAChG,CAAC","sourcesContent":["import React from 'react';\nimport { Pressable, type ViewStyle } from 'react-native';\n\nimport type { TabProps } from '../../../../types/tabs';\nimport { View } from '../../../layout/public';\nimport { useTheme } from '../../../theme/runtime';\nimport { Text } from '../../../typography/public';\nimport { useTabRegistration } from '../../composition/useTabRegistration';\nimport { useTabsContext } from '../../composition/useTabsContext';\n\n/*** Renders one accessible selectable tab inside a Tabs context. */\nexport function Tab({\n value,\n children,\n disabled = false,\n interactionPolicy = 'enabled',\n testID,\n}: TabProps) {\n const { theme } = useTheme();\n const { activeValue, getPanelId, getTabId, setActiveValue, setFocusedValue } = useTabsContext();\n const pressableRef = useTabRegistration({ disabled, value });\n const selected = activeValue === value;\n const passive = interactionPolicy === 'passive';\n const onPress = passive || disabled ? undefined : () => setActiveValue(value);\n\n return (\n <Pressable\n accessibilityLabel={undefined}\n accessibilityRole=\"tab\"\n accessibilityState={{ disabled, selected }}\n aria-controls={getPanelId(value)}\n disabled={disabled}\n nativeID={getTabId(value)}\n onBlur={() => setFocusedValue(undefined)}\n onFocus={() => setFocusedValue(value)}\n onPress={onPress}\n ref={pressableRef}\n testID={testID}\n >\n <View\n px=\"m\"\n py=\"s\"\n style={resolveTabStyle({\n borderColor: selected\n ? theme.semantics.action.primary.base\n : theme.semantics.border.default,\n disabled,\n })}\n >\n <Text color={selected ? 'primary' : undefined} variant=\"label\" weight=\"medium\">\n {children}\n </Text>\n </View>\n </Pressable>\n );\n}\n\n/*** Resolves active and disabled visual state without inline JSX styles. */\nfunction resolveTabStyle({\n borderColor,\n disabled,\n}: {\n borderColor: string;\n disabled: boolean;\n}): ViewStyle {\n return { borderBottomColor: borderColor, borderBottomWidth: 2, opacity: disabled ? 0.64 : 1 };\n}\n"]}
1
+ {"version":3,"file":"Tab.js","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/Tab.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { Pressable, type ViewStyle } from 'react-native';\n\nimport type { TabProps } from '../../../../types/tabs';\nimport { View } from '../../../layout/public';\nimport { useTheme } from '../../../theme/runtime';\nimport { Text } from '../../../typography/public';\nimport { useTabRegistration } from '../../composition/useTabRegistration';\nimport { useTabsContext } from '../../composition/useTabsContext';\n\n/*** Renders one accessible selectable tab inside a Tabs context. */\nexport function Tab({\n value,\n children,\n disabled = false,\n interactionPolicy = 'enabled',\n testID,\n trailing,\n}: TabProps) {\n const { theme } = useTheme();\n const { activeValue, getPanelId, getTabId, setActiveValue, setFocusedValue } = useTabsContext();\n const pressableRef = useTabRegistration({ disabled, value });\n const selected = activeValue === value;\n const passive = interactionPolicy === 'passive';\n const onPress = passive || disabled ? undefined : () => setActiveValue(value);\n\n return (\n <Pressable\n accessibilityLabel={undefined}\n accessibilityRole=\"tab\"\n accessibilityState={{ disabled, selected }}\n aria-controls={getPanelId(value)}\n disabled={disabled}\n nativeID={getTabId(value)}\n onBlur={() => setFocusedValue(undefined)}\n onFocus={() => setFocusedValue(value)}\n onPress={onPress}\n ref={pressableRef}\n testID={testID}\n >\n <View\n px=\"m\"\n py=\"s\"\n style={resolveTabStyle({\n borderColor: selected\n ? theme.semantics.action.primary.base\n : theme.semantics.border.default,\n disabled,\n })}\n >\n {renderTabContent(children, trailing, selected)}\n </View>\n </Pressable>\n );\n}\n\n/*** Renders the canonical tab label with optional trailing content. */\nfunction renderTabContent(\n children: React.ReactNode,\n trailing: React.ReactNode,\n selected: boolean,\n): React.ReactNode {\n const label = (\n <Text color={selected ? 'primary' : undefined} variant=\"label\" weight=\"medium\">\n {children}\n </Text>\n );\n if (trailing === undefined) return label;\n return (\n <View align=\"center\" direction=\"row\" gap=\"xs\">\n {label}\n {trailing}\n </View>\n );\n}\n\n/*** Resolves active and disabled visual state without inline JSX styles. */\nfunction resolveTabStyle({\n borderColor,\n disabled,\n}: {\n borderColor: string;\n disabled: boolean;\n}): ViewStyle {\n return { borderBottomColor: borderColor, borderBottomWidth: 2, opacity: disabled ? 0.64 : 1 };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
2
  import type { TabListProps } from '../../../../types/tabs';
3
3
  /*** Renders the accessible tab list and owns keyboard focus navigation. */
4
- export declare function TabList({ children, testID }: TabListProps): React.JSX.Element;
4
+ export declare function TabList({ children, fill, testID }: TabListProps): React.JSX.Element;
5
5
  //# sourceMappingURL=TabList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TabList.d.ts","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/TabList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,wBAAwB,CAAC;AAc7E,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,YAAY,qBAuBzD"}
1
+ {"version":3,"file":"TabList.d.ts","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/TabList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,wBAAwB,CAAC;AAe7E,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAY,EAAE,MAAM,EAAE,EAAE,YAAY,qBAuBvE"}
@@ -3,6 +3,7 @@ import { useFocusManager } from '../../../../internal/focus/useFocusManager';
3
3
  import { View } from '../../../layout/public';
4
4
  import { useTabsContext } from '../../composition/useTabsContext';
5
5
  import { resolveNextTabValue } from '../../utils/resolveNextTabValue';
6
+ import { wrapTabListChildren } from '../../utils/wrapTabListChildren';
6
7
  const navigationKeys = [
7
8
  'ArrowLeft',
8
9
  'ArrowRight',
@@ -12,7 +13,7 @@ const navigationKeys = [
12
13
  'End',
13
14
  ];
14
15
  /*** Renders the accessible tab list and owns keyboard focus navigation. */
15
- export function TabList({ children, testID }) {
16
+ export function TabList({ children, fill = false, testID }) {
16
17
  const { bindKeydown } = useFocusManager();
17
18
  const { focusedValue, setActiveValue, tabs } = useTabsContext();
18
19
  React.useEffect(() => {
@@ -30,7 +31,7 @@ export function TabList({ children, testID }) {
30
31
  });
31
32
  }, [bindKeydown, focusedValue, setActiveValue, tabs]);
32
33
  return (<View accessibilityRole="tablist" direction="row" testID={testID}>
33
- {children}
34
+ {wrapTabListChildren(children, fill)}
34
35
  </View>);
35
36
  }
36
37
  /*** Narrows keyboard event keys to the tab navigation key contract. */
@@ -1 +1 @@
1
- {"version":3,"file":"TabList.js","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/TabList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAE7E,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,cAAc,GAAsB;IACxC,WAAW;IACX,YAAY;IACZ,SAAS;IACT,WAAW;IACX,MAAM;IACN,KAAK;CACN,CAAC;AAEF,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAgB;IACxD,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAAC;IAC1C,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC;IAEhE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAC;QAEpC,OAAO,WAAW,CAAC,CAAC,KAAK,EAAE,EAAE;YAC3B,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC3C,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,CAAC,SAAS;gBAAE,OAAO;YAEvB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;YACrD,cAAc,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC;IAEtD,OAAO,CACL,CAAC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAC/D;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,SAAS,kBAAkB,CAAC,GAAuB;IACjD,OAAO,GAAG,KAAK,SAAS,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import React from 'react';\n\nimport { useFocusManager } from '../../../../internal/focus/useFocusManager';\nimport type { TabListProps, TabNavigationKey } from '../../../../types/tabs';\nimport { View } from '../../../layout/public';\nimport { useTabsContext } from '../../composition/useTabsContext';\nimport { resolveNextTabValue } from '../../utils/resolveNextTabValue';\n\nconst navigationKeys: readonly string[] = [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n];\n\n/*** Renders the accessible tab list and owns keyboard focus navigation. */\nexport function TabList({ children, testID }: TabListProps) {\n const { bindKeydown } = useFocusManager();\n const { focusedValue, setActiveValue, tabs } = useTabsContext();\n\n React.useEffect(() => {\n if (!focusedValue) return undefined;\n\n return bindKeydown((event) => {\n if (!isTabNavigationKey(event.key)) return;\n const nextValue = resolveNextTabValue(tabs, focusedValue, event.key);\n if (!nextValue) return;\n\n event.preventDefault();\n tabs.find((tab) => tab.value === nextValue)?.focus();\n setActiveValue(nextValue);\n });\n }, [bindKeydown, focusedValue, setActiveValue, tabs]);\n\n return (\n <View accessibilityRole=\"tablist\" direction=\"row\" testID={testID}>\n {children}\n </View>\n );\n}\n\n/*** Narrows keyboard event keys to the tab navigation key contract. */\nfunction isTabNavigationKey(key: string | undefined): key is TabNavigationKey {\n return key !== undefined && navigationKeys.includes(key);\n}\n"]}
1
+ {"version":3,"file":"TabList.js","sourceRoot":"","sources":["../../../../../src/features/tabs/adapters/inbound/TabList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAE7E,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,cAAc,GAAsB;IACxC,WAAW;IACX,YAAY;IACZ,SAAS;IACT,WAAW;IACX,MAAM;IACN,KAAK;CACN,CAAC;AAEF,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAG,KAAK,EAAE,MAAM,EAAgB;IACtE,MAAM,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAAC;IAC1C,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,GAAG,cAAc,EAAE,CAAC;IAEhE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAC;QAEpC,OAAO,WAAW,CAAC,CAAC,KAAK,EAAE,EAAE;YAC3B,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC3C,MAAM,SAAS,GAAG,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YACrE,IAAI,CAAC,SAAS;gBAAE,OAAO;YAEvB,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;YACrD,cAAc,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC;IAEtD,OAAO,CACL,CAAC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAC/D;MAAA,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,CACtC;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,SAAS,kBAAkB,CAAC,GAAuB;IACjD,OAAO,GAAG,KAAK,SAAS,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import React from 'react';\n\nimport { useFocusManager } from '../../../../internal/focus/useFocusManager';\nimport type { TabListProps, TabNavigationKey } from '../../../../types/tabs';\nimport { View } from '../../../layout/public';\nimport { useTabsContext } from '../../composition/useTabsContext';\nimport { resolveNextTabValue } from '../../utils/resolveNextTabValue';\nimport { wrapTabListChildren } from '../../utils/wrapTabListChildren';\n\nconst navigationKeys: readonly string[] = [\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'ArrowDown',\n 'Home',\n 'End',\n];\n\n/*** Renders the accessible tab list and owns keyboard focus navigation. */\nexport function TabList({ children, fill = false, testID }: TabListProps) {\n const { bindKeydown } = useFocusManager();\n const { focusedValue, setActiveValue, tabs } = useTabsContext();\n\n React.useEffect(() => {\n if (!focusedValue) return undefined;\n\n return bindKeydown((event) => {\n if (!isTabNavigationKey(event.key)) return;\n const nextValue = resolveNextTabValue(tabs, focusedValue, event.key);\n if (!nextValue) return;\n\n event.preventDefault();\n tabs.find((tab) => tab.value === nextValue)?.focus();\n setActiveValue(nextValue);\n });\n }, [bindKeydown, focusedValue, setActiveValue, tabs]);\n\n return (\n <View accessibilityRole=\"tablist\" direction=\"row\" testID={testID}>\n {wrapTabListChildren(children, fill)}\n </View>\n );\n}\n\n/*** Narrows keyboard event keys to the tab navigation key contract. */\nfunction isTabNavigationKey(key: string | undefined): key is TabNavigationKey {\n return key !== undefined && navigationKeys.includes(key);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ /*** Wrap direct tab-list children with equal flex only when fill distribution is enabled. */
3
+ export declare function wrapTabListChildren(children: React.ReactNode, fill: boolean): React.ReactNode;
4
+ //# sourceMappingURL=wrapTabListChildren.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapTabListChildren.d.ts","sourceRoot":"","sources":["../../../../src/features/tabs/utils/wrapTabListChildren.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,6FAA6F;AAC7F,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC,SAAS,CAG7F"}
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { View } from '../../layout/public';
3
+ /*** Wrap direct tab-list children with equal flex only when fill distribution is enabled. */
4
+ export function wrapTabListChildren(children, fill) {
5
+ if (!fill)
6
+ return children;
7
+ return React.Children.map(children, (child) => <View flex={1}>{child}</View>);
8
+ }
9
+ //# sourceMappingURL=wrapTabListChildren.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapTabListChildren.js","sourceRoot":"","sources":["../../../../src/features/tabs/utils/wrapTabListChildren.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE3C,6FAA6F;AAC7F,MAAM,UAAU,mBAAmB,CAAC,QAAyB,EAAE,IAAa;IAC1E,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC;IAC3B,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AAChF,CAAC","sourcesContent":["import React from 'react';\n\nimport { View } from '../../layout/public';\n\n/*** Wrap direct tab-list children with equal flex only when fill distribution is enabled. */\nexport function wrapTabListChildren(children: React.ReactNode, fill: boolean): React.ReactNode {\n if (!fill) return children;\n return React.Children.map(children, (child) => <View flex={1}>{child}</View>);\n}\n"]}
@@ -10,6 +10,7 @@ export interface TabsProps extends ViewStyleProps {
10
10
  }
11
11
  export interface TabListProps {
12
12
  children?: React.ReactNode;
13
+ fill?: boolean;
13
14
  testID?: string;
14
15
  }
15
16
  export interface TabProps {
@@ -18,6 +19,7 @@ export interface TabProps {
18
19
  disabled?: boolean;
19
20
  interactionPolicy?: InteractionPolicy;
20
21
  testID?: string;
22
+ trailing?: React.ReactNode;
21
23
  }
22
24
  export interface TabPanelProps extends ViewStyleProps {
23
25
  value: string;
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,SAAU,SAAQ,cAAc;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,WAAW,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,MAAM,gBAAgB,GAC1B,WAAW,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC"}
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,WAAW,SAAU,SAAQ,cAAc;IAC/C,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,WAAW,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,IAAI,EAAE,SAAS,eAAe,EAAE,CAAC;IACjC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,MAAM,gBAAgB,GAC1B,WAAW,GAAG,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from './interactionPolicy';\nimport type { ViewStyleProps } from './layout';\n\nexport interface TabsProps extends ViewStyleProps {\n children?: React.ReactNode;\n value?: string;\n defaultValue?: string;\n onValueChange?: ((value: string) => void) | undefined;\n testID?: string;\n}\n\nexport interface TabListProps {\n children?: React.ReactNode;\n testID?: string;\n}\n\nexport interface TabProps {\n value: string;\n children?: React.ReactNode;\n disabled?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n}\n\nexport interface TabPanelProps extends ViewStyleProps {\n value: string;\n children?: React.ReactNode;\n testID?: string;\n}\n\nexport interface TabRegistration {\n disabled: boolean;\n focus: () => void;\n value: string;\n}\n\nexport interface TabsContextValue {\n activeValue: string | undefined;\n focusedValue: string | undefined;\n getPanelId: (value: string) => string;\n getTabId: (value: string) => string;\n registerTab: (tab: TabRegistration) => void;\n setActiveValue: (value: string) => void;\n setFocusedValue: (value: string | undefined) => void;\n tabs: readonly TabRegistration[];\n unregisterTab: (value: string) => void;\n}\n\nexport type TabNavigationKey =\n 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End';\n"]}
1
+ {"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/types/tabs.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from './interactionPolicy';\nimport type { ViewStyleProps } from './layout';\n\nexport interface TabsProps extends ViewStyleProps {\n children?: React.ReactNode;\n value?: string;\n defaultValue?: string;\n onValueChange?: ((value: string) => void) | undefined;\n testID?: string;\n}\n\nexport interface TabListProps {\n children?: React.ReactNode;\n fill?: boolean;\n testID?: string;\n}\n\nexport interface TabProps {\n value: string;\n children?: React.ReactNode;\n disabled?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n trailing?: React.ReactNode;\n}\n\nexport interface TabPanelProps extends ViewStyleProps {\n value: string;\n children?: React.ReactNode;\n testID?: string;\n}\n\nexport interface TabRegistration {\n disabled: boolean;\n focus: () => void;\n value: string;\n}\n\nexport interface TabsContextValue {\n activeValue: string | undefined;\n focusedValue: string | undefined;\n getPanelId: (value: string) => string;\n getTabId: (value: string) => string;\n registerTab: (tab: TabRegistration) => void;\n setActiveValue: (value: string) => void;\n setFocusedValue: (value: string | undefined) => void;\n tabs: readonly TabRegistration[];\n unregisterTab: (value: string) => void;\n}\n\nexport type TabNavigationKey =\n 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End';\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "9.0.14",
4
+ "version": "9.1.0",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@ankhorage/color-theory": "^0.3.1",
47
47
  "@ankhorage/contracts": "^22.5.0",
48
- "@ankhorage/utility": "^1.4.0",
48
+ "@ankhorage/utility": "^1.5.0",
49
49
  "@gorhom/bottom-sheet": "^5.2.13"
50
50
  },
51
51
  "files": [
@@ -15,6 +15,7 @@ export function Tab({
15
15
  disabled = false,
16
16
  interactionPolicy = 'enabled',
17
17
  testID,
18
+ trailing,
18
19
  }: TabProps) {
19
20
  const { theme } = useTheme();
20
21
  const { activeValue, getPanelId, getTabId, setActiveValue, setFocusedValue } = useTabsContext();
@@ -47,14 +48,32 @@ export function Tab({
47
48
  disabled,
48
49
  })}
49
50
  >
50
- <Text color={selected ? 'primary' : undefined} variant="label" weight="medium">
51
- {children}
52
- </Text>
51
+ {renderTabContent(children, trailing, selected)}
53
52
  </View>
54
53
  </Pressable>
55
54
  );
56
55
  }
57
56
 
57
+ /*** Renders the canonical tab label with optional trailing content. */
58
+ function renderTabContent(
59
+ children: React.ReactNode,
60
+ trailing: React.ReactNode,
61
+ selected: boolean,
62
+ ): React.ReactNode {
63
+ const label = (
64
+ <Text color={selected ? 'primary' : undefined} variant="label" weight="medium">
65
+ {children}
66
+ </Text>
67
+ );
68
+ if (trailing === undefined) return label;
69
+ return (
70
+ <View align="center" direction="row" gap="xs">
71
+ {label}
72
+ {trailing}
73
+ </View>
74
+ );
75
+ }
76
+
58
77
  /*** Resolves active and disabled visual state without inline JSX styles. */
59
78
  function resolveTabStyle({
60
79
  borderColor,
@@ -5,6 +5,7 @@ import type { TabListProps, TabNavigationKey } from '../../../../types/tabs';
5
5
  import { View } from '../../../layout/public';
6
6
  import { useTabsContext } from '../../composition/useTabsContext';
7
7
  import { resolveNextTabValue } from '../../utils/resolveNextTabValue';
8
+ import { wrapTabListChildren } from '../../utils/wrapTabListChildren';
8
9
 
9
10
  const navigationKeys: readonly string[] = [
10
11
  'ArrowLeft',
@@ -16,7 +17,7 @@ const navigationKeys: readonly string[] = [
16
17
  ];
17
18
 
18
19
  /*** Renders the accessible tab list and owns keyboard focus navigation. */
19
- export function TabList({ children, testID }: TabListProps) {
20
+ export function TabList({ children, fill = false, testID }: TabListProps) {
20
21
  const { bindKeydown } = useFocusManager();
21
22
  const { focusedValue, setActiveValue, tabs } = useTabsContext();
22
23
 
@@ -36,7 +37,7 @@ export function TabList({ children, testID }: TabListProps) {
36
37
 
37
38
  return (
38
39
  <View accessibilityRole="tablist" direction="row" testID={testID}>
39
- {children}
40
+ {wrapTabListChildren(children, fill)}
40
41
  </View>
41
42
  );
42
43
  }
@@ -0,0 +1,18 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ import type { TabProps } from '../../../types/tabs';
4
+
5
+ test('accepts trailing tab content without replacing the canonical label', () => {
6
+ const trailing = '2';
7
+ const props: TabProps = {
8
+ value: 'rules',
9
+ children: 'Rules',
10
+ trailing,
11
+ };
12
+
13
+ expect(props).toEqual({
14
+ value: 'rules',
15
+ children: 'Rules',
16
+ trailing,
17
+ });
18
+ });
@@ -0,0 +1,24 @@
1
+ import { expect, test } from 'bun:test';
2
+ import React from 'react';
3
+
4
+ import { wrapTabListChildren } from './wrapTabListChildren';
5
+
6
+ test('preserves direct children when fill is disabled', () => {
7
+ const child = React.createElement('span', { id: 'one' });
8
+ expect(wrapTabListChildren(child, false)).toBe(child);
9
+ });
10
+
11
+ test('wraps every direct child in an equal flex container when fill is enabled', () => {
12
+ const children = [
13
+ React.createElement('span', { id: 'one', key: 'one' }),
14
+ React.createElement('span', { id: 'two', key: 'two' }),
15
+ ];
16
+ const wrapped = React.Children.toArray(wrapTabListChildren(children, true));
17
+
18
+ expect(wrapped).toHaveLength(2);
19
+ for (const item of wrapped) {
20
+ expect(React.isValidElement(item)).toBe(true);
21
+ if (!React.isValidElement(item)) continue;
22
+ expect(item.props).toMatchObject({ flex: 1 });
23
+ }
24
+ });
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+
3
+ import { View } from '../../layout/public';
4
+
5
+ /*** Wrap direct tab-list children with equal flex only when fill distribution is enabled. */
6
+ export function wrapTabListChildren(children: React.ReactNode, fill: boolean): React.ReactNode {
7
+ if (!fill) return children;
8
+ return React.Children.map(children, (child) => <View flex={1}>{child}</View>);
9
+ }
package/src/types/tabs.ts CHANGED
@@ -13,6 +13,7 @@ export interface TabsProps extends ViewStyleProps {
13
13
 
14
14
  export interface TabListProps {
15
15
  children?: React.ReactNode;
16
+ fill?: boolean;
16
17
  testID?: string;
17
18
  }
18
19
 
@@ -22,6 +23,7 @@ export interface TabProps {
22
23
  disabled?: boolean;
23
24
  interactionPolicy?: InteractionPolicy;
24
25
  testID?: string;
26
+ trailing?: React.ReactNode;
25
27
  }
26
28
 
27
29
  export interface TabPanelProps extends ViewStyleProps {