@tamagui/group 1.7.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.
@@ -0,0 +1,220 @@
1
+ import {
2
+ getConfig,
3
+ getExpandedShorthands,
4
+ getTokens,
5
+ getVariableValue,
6
+ isTamaguiElement,
7
+ mergeProps,
8
+ spacedChildren,
9
+ styled,
10
+ useMediaPropsActive,
11
+ withStaticProperties
12
+ } from "@tamagui/core";
13
+ import { createContextScope } from "@tamagui/create-context";
14
+ import { ThemeableStack } from "@tamagui/stacks";
15
+ import { useControllableState } from "@tamagui/use-controllable-state";
16
+ import React, { Children, forwardRef, isValidElement } from "react";
17
+ import { ScrollView } from "react-native";
18
+ import { useIndex, useIndexedChildren } from "reforest";
19
+ const GROUP_NAME = "Group";
20
+ const [createGroupContext, createGroupScope] = createContextScope(GROUP_NAME);
21
+ const [GroupProvider, useGroupContext] = createGroupContext(GROUP_NAME);
22
+ const GroupFrame = styled(ThemeableStack, {
23
+ name: "GroupFrame",
24
+ variants: {
25
+ unstyled: {
26
+ false: {
27
+ size: "$true",
28
+ y: 0,
29
+ backgroundColor: "$background"
30
+ }
31
+ },
32
+ size: (val, { tokens }) => {
33
+ const borderRadius = tokens.radius[val] ?? val ?? tokens.radius["$true"];
34
+ return {
35
+ borderRadius
36
+ };
37
+ }
38
+ },
39
+ defaultVariants: {
40
+ unstyled: false
41
+ }
42
+ });
43
+ function createGroup(verticalDefault) {
44
+ return withStaticProperties(
45
+ forwardRef((props, ref) => {
46
+ const activeProps = useMediaPropsActive(props);
47
+ const {
48
+ __scopeGroup,
49
+ children: childrenProp,
50
+ space,
51
+ size = "$true",
52
+ spaceDirection,
53
+ separator,
54
+ scrollable,
55
+ axis = verticalDefault ? "vertical" : "horizontal",
56
+ disabled: disabledProp,
57
+ disablePassBorderRadius: disablePassBorderRadiusProp,
58
+ borderRadius,
59
+ forceUseItem,
60
+ ...restProps
61
+ } = getExpandedShorthands(activeProps);
62
+ const vertical = axis === "vertical";
63
+ const [itemChildrenCount, setItemChildrenCount] = useControllableState({
64
+ defaultProp: forceUseItem ? 1 : 0
65
+ });
66
+ const isUsingItems = true;
67
+ const radius = borderRadius ?? (size ? getVariableValue(getTokens().radius[size]) - 1 : void 0);
68
+ const hasRadius = radius !== void 0;
69
+ const disablePassBorderRadius = disablePassBorderRadiusProp ?? !hasRadius;
70
+ if (!isUsingItems)
71
+ console.log("screw up!");
72
+ const childrenArray = Children.toArray(childrenProp);
73
+ const children = isUsingItems ? childrenProp : childrenArray.map((child, i) => {
74
+ if (!isValidElement(child)) {
75
+ return child;
76
+ }
77
+ const disabled = child.props.disabled ?? disabledProp;
78
+ const isFirst = i === 0;
79
+ const isLast = i === childrenArray.length - 1;
80
+ const radiusStyles = disablePassBorderRadius === true ? null : getBorderRadius({
81
+ isFirst,
82
+ isLast,
83
+ radius,
84
+ vertical,
85
+ disable: disablePassBorderRadius
86
+ });
87
+ const props2 = {
88
+ disabled,
89
+ ...isTamaguiElement(child) ? radiusStyles : { style: radiusStyles }
90
+ };
91
+ return cloneElementWithPropOrder(child, props2);
92
+ });
93
+ const indexedChildren = useIndexedChildren(
94
+ spacedChildren({
95
+ direction: spaceDirection,
96
+ separator,
97
+ space,
98
+ children
99
+ })
100
+ );
101
+ const onItemMount = React.useCallback(
102
+ () => setItemChildrenCount((prev) => prev + 1),
103
+ []
104
+ );
105
+ const onItemUnmount = React.useCallback(
106
+ () => setItemChildrenCount((prev) => prev - 1),
107
+ []
108
+ );
109
+ return <GroupProvider
110
+ disablePassBorderRadius={disablePassBorderRadius}
111
+ vertical={axis === "vertical"}
112
+ radius={radius}
113
+ disabled={disabledProp}
114
+ onItemMount={onItemMount}
115
+ onItemUnmount={onItemUnmount}
116
+ scope={__scopeGroup}
117
+ ><GroupFrame
118
+ ref={ref}
119
+ size={size}
120
+ flexDirection={axis === "horizontal" ? "row" : "column"}
121
+ borderRadius={borderRadius}
122
+ {...restProps}
123
+ >{wrapScroll({ ...activeProps, axis }, indexedChildren)}</GroupFrame></GroupProvider>;
124
+ }),
125
+ {
126
+ Item: GroupItem
127
+ }
128
+ );
129
+ }
130
+ const GroupItem = (props) => {
131
+ const { __scopeGroup, children } = props;
132
+ const groupItemProps = useGroupItem(
133
+ { disabled: isValidElement(children) ? children.props.disabled : void 0 },
134
+ __scopeGroup
135
+ );
136
+ if (!isValidElement(children)) {
137
+ return children;
138
+ }
139
+ if (isTamaguiElement(children)) {
140
+ return React.cloneElement(children, groupItemProps);
141
+ }
142
+ return React.cloneElement(children, {
143
+ style: {
144
+ ...children.props?.["style"],
145
+ ...groupItemProps
146
+ }
147
+ });
148
+ };
149
+ const useGroupItem = (childrenProps, __scopeGroup) => {
150
+ const treeIndex = useIndex();
151
+ const context = useGroupContext("GroupItem", __scopeGroup);
152
+ React.useEffect(() => {
153
+ context.onItemMount();
154
+ return () => {
155
+ context.onItemUnmount();
156
+ };
157
+ }, []);
158
+ if (!treeIndex) {
159
+ throw Error("<Group.Item/> should only be used within a <Group/>");
160
+ }
161
+ const isFirst = treeIndex.index === 0;
162
+ const isLast = treeIndex.index === treeIndex.maxIndex;
163
+ const disabled = childrenProps.disabled ?? context.disabled;
164
+ let propsToPass = {
165
+ disabled
166
+ };
167
+ if (context.disablePassBorderRadius !== true) {
168
+ const borderRadius = getBorderRadius({
169
+ radius: context.radius,
170
+ isFirst,
171
+ isLast,
172
+ vertical: context.vertical,
173
+ disable: context.disablePassBorderRadius
174
+ });
175
+ return { ...propsToPass, ...borderRadius };
176
+ }
177
+ return propsToPass;
178
+ };
179
+ const Group = createGroup(true);
180
+ const YGroup = Group;
181
+ const XGroup = createGroup(false);
182
+ const wrapScroll = ({ scrollable, axis, showScrollIndicator = false }, children) => {
183
+ if (scrollable)
184
+ return <ScrollView
185
+ {...axis === "vertical" && {
186
+ showsVerticalScrollIndicator: showScrollIndicator
187
+ }}
188
+ {...axis === "horizontal" && {
189
+ horizontal: true,
190
+ showsHorizontalScrollIndicator: showScrollIndicator
191
+ }}
192
+ >{children}</ScrollView>;
193
+ return children;
194
+ };
195
+ const getBorderRadius = ({
196
+ isFirst,
197
+ isLast,
198
+ radius,
199
+ vertical,
200
+ disable
201
+ }) => {
202
+ return {
203
+ borderTopLeftRadius: isFirst && disable !== "top" && disable !== "start" ? radius : 0,
204
+ borderTopRightRadius: disable !== "top" && disable !== "end" && (vertical && isFirst || !vertical && isLast) ? radius : 0,
205
+ borderBottomLeftRadius: disable !== "bottom" && disable !== "start" && (vertical && isLast || !vertical && isFirst) ? radius : 0,
206
+ borderBottomRightRadius: isLast && disable !== "bottom" && disable !== "end" ? radius : 0
207
+ };
208
+ };
209
+ const cloneElementWithPropOrder = (child, props) => {
210
+ const next = mergeProps(child.props, props, false, getConfig().shorthands)[0];
211
+ return React.cloneElement({ ...child, props: null }, next);
212
+ };
213
+ export {
214
+ Group,
215
+ GroupFrame,
216
+ XGroup,
217
+ YGroup,
218
+ useGroupItem
219
+ };
220
+ //# sourceMappingURL=Group.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/Group.tsx"],
4
+ "sourcesContent": ["import {\n GetProps,\n TamaguiElement,\n UnionableString,\n Variable,\n getConfig,\n getExpandedShorthands,\n getTokens,\n getVariableValue,\n isTamaguiElement,\n mergeProps,\n spacedChildren,\n styled,\n useMediaPropsActive,\n withStaticProperties,\n} from '@tamagui/core'\nimport { Scope, createContextScope } from '@tamagui/create-context'\nimport { ThemeableStack } from '@tamagui/stacks'\nimport { useControllableState } from '@tamagui/use-controllable-state'\nimport React, { Children, forwardRef, isValidElement } from 'react'\nimport { ScrollView } from 'react-native'\nimport { useIndex, useIndexedChildren } from 'reforest'\n\ntype DisablePassBorderRadius = boolean | 'bottom' | 'top' | 'start' | 'end'\n\ninterface GroupContextValue {\n vertical: boolean\n disablePassBorderRadius: DisablePassBorderRadius\n onItemMount: () => void\n onItemUnmount: () => void\n radius?: number | UnionableString | Variable<any>\n disabled?: boolean\n}\n\nconst GROUP_NAME = 'Group'\n\ntype ScopedProps<P> = P & { __scopeGroup?: Scope }\nconst [createGroupContext, createGroupScope] = createContextScope(GROUP_NAME)\nconst [GroupProvider, useGroupContext] = createGroupContext<GroupContextValue>(GROUP_NAME)\n\nexport const GroupFrame = styled(ThemeableStack, {\n name: 'GroupFrame',\n\n variants: {\n unstyled: {\n false: {\n size: '$true',\n y: 0,\n backgroundColor: '$background',\n },\n },\n\n size: (val, { tokens }) => {\n const borderRadius = tokens.radius[val] ?? val ?? tokens.radius['$true']\n return {\n borderRadius,\n }\n },\n } as const,\n\n defaultVariants: {\n unstyled: false,\n },\n})\n\nexport type GroupProps = GetProps<typeof GroupFrame> & {\n axis?: 'horizontal' | 'vertical'\n\n scrollable?: boolean\n /**\n * @default false\n */\n showScrollIndicator?: boolean\n disabled?: boolean\n disablePassBorderRadius?: DisablePassBorderRadius\n /**\n * forces the group to use the Group.Item API\n */\n forceUseItem?: boolean\n}\n\nfunction createGroup(verticalDefault: boolean) {\n return withStaticProperties(\n forwardRef<TamaguiElement, ScopedProps<GroupProps>>((props, ref) => {\n const activeProps = useMediaPropsActive(props)\n\n const {\n __scopeGroup,\n children: childrenProp,\n space,\n size = '$true',\n spaceDirection,\n separator,\n scrollable,\n axis = verticalDefault ? 'vertical' : 'horizontal',\n disabled: disabledProp,\n disablePassBorderRadius: disablePassBorderRadiusProp,\n borderRadius,\n forceUseItem,\n ...restProps\n } = getExpandedShorthands(activeProps)\n\n const vertical = axis === 'vertical'\n const [itemChildrenCount, setItemChildrenCount] = useControllableState({\n defaultProp: forceUseItem ? 1 : 0,\n })\n const isUsingItems = true // itemChildrenCount > 0\n\n // 1 off given border to adjust for border radius? This should be user controllable\n const radius =\n borderRadius ??\n (size ? getVariableValue(getTokens().radius[size]) - 1 : undefined)\n\n const hasRadius = radius !== undefined\n const disablePassBorderRadius = disablePassBorderRadiusProp ?? !hasRadius\n\n if (!isUsingItems) console.log('screw up!')\n const childrenArray = Children.toArray(childrenProp)\n const children = isUsingItems\n ? childrenProp\n : childrenArray.map((child, i) => {\n if (!isValidElement(child)) {\n return child\n }\n const disabled = child.props.disabled ?? disabledProp\n\n const isFirst = i === 0\n const isLast = i === childrenArray.length - 1\n\n const radiusStyles =\n disablePassBorderRadius === true\n ? null\n : getBorderRadius({\n isFirst,\n isLast,\n radius,\n vertical,\n disable: disablePassBorderRadius,\n })\n const props = {\n disabled,\n ...(isTamaguiElement(child) ? radiusStyles : { style: radiusStyles }),\n }\n\n return cloneElementWithPropOrder(child, props)\n })\n\n const indexedChildren = useIndexedChildren(\n spacedChildren({\n direction: spaceDirection,\n separator,\n space,\n children,\n })\n )\n\n const onItemMount = React.useCallback(\n () => setItemChildrenCount((prev) => prev + 1),\n []\n )\n const onItemUnmount = React.useCallback(\n () => setItemChildrenCount((prev) => prev - 1),\n []\n )\n\n return (\n <GroupProvider\n disablePassBorderRadius={disablePassBorderRadius}\n vertical={axis === 'vertical'}\n radius={radius}\n disabled={disabledProp}\n onItemMount={onItemMount}\n onItemUnmount={onItemUnmount}\n scope={__scopeGroup}\n >\n <GroupFrame\n ref={ref}\n size={size}\n flexDirection={axis === 'horizontal' ? 'row' : 'column'}\n borderRadius={borderRadius}\n {...restProps}\n >\n {wrapScroll({ ...activeProps, axis }, indexedChildren)}\n </GroupFrame>\n </GroupProvider>\n )\n }),\n {\n Item: GroupItem,\n }\n )\n}\n\nconst GroupItem = (props: ScopedProps<{ children: React.ReactNode }>) => {\n const { __scopeGroup, children } = props\n const groupItemProps = useGroupItem(\n { disabled: isValidElement(children) ? children.props.disabled : undefined },\n __scopeGroup\n )\n\n if (!isValidElement(children)) {\n return children as any\n }\n\n if (isTamaguiElement(children)) {\n return React.cloneElement(children, groupItemProps)\n }\n\n return React.cloneElement(children, {\n style: {\n ...children.props?.['style'],\n ...groupItemProps,\n },\n } as any)\n}\n\nexport const useGroupItem = (\n childrenProps: { disabled: boolean },\n __scopeGroup?: Scope\n) => {\n const treeIndex = useIndex()\n const context = useGroupContext('GroupItem', __scopeGroup)\n\n React.useEffect(() => {\n context.onItemMount()\n return () => {\n context.onItemUnmount()\n }\n }, [])\n\n if (!treeIndex) {\n throw Error('<Group.Item/> should only be used within a <Group/>')\n }\n\n const isFirst = treeIndex.index === 0\n const isLast = treeIndex.index === treeIndex.maxIndex\n\n const disabled = childrenProps.disabled ?? context.disabled\n\n let propsToPass: Record<string, any> = {\n disabled,\n }\n\n if (context.disablePassBorderRadius !== true) {\n const borderRadius = getBorderRadius({\n radius: context.radius,\n isFirst,\n isLast,\n vertical: context.vertical,\n disable: context.disablePassBorderRadius,\n })\n return { ...propsToPass, ...borderRadius }\n }\n return propsToPass\n}\n\nexport const Group = createGroup(true)\nexport const YGroup = Group\nexport const XGroup = createGroup(false)\n\nconst wrapScroll = (\n { scrollable, axis, showScrollIndicator = false }: GroupProps,\n children: any\n) => {\n if (scrollable)\n return (\n <ScrollView\n {...(axis === 'vertical' && {\n showsVerticalScrollIndicator: showScrollIndicator,\n })}\n {...(axis === 'horizontal' && {\n horizontal: true,\n showsHorizontalScrollIndicator: showScrollIndicator,\n })}\n >\n {children}\n </ScrollView>\n )\n return children\n}\n\nconst getBorderRadius = ({\n isFirst,\n isLast,\n radius,\n vertical,\n disable,\n}: {\n radius: any\n vertical: boolean\n isFirst: boolean\n isLast: boolean\n disable: DisablePassBorderRadius\n}) => {\n // TODO: RTL support would be nice here\n return {\n borderTopLeftRadius: isFirst && disable !== 'top' && disable !== 'start' ? radius : 0,\n borderTopRightRadius:\n disable !== 'top' &&\n disable !== 'end' &&\n ((vertical && isFirst) || (!vertical && isLast))\n ? radius\n : 0,\n borderBottomLeftRadius:\n disable !== 'bottom' &&\n disable !== 'start' &&\n ((vertical && isLast) || (!vertical && isFirst))\n ? radius\n : 0,\n borderBottomRightRadius:\n isLast && disable !== 'bottom' && disable !== 'end' ? radius : 0,\n }\n}\n\nconst cloneElementWithPropOrder = (child: any, props: Object) => {\n const next = mergeProps(child.props, props, false, getConfig().shorthands)[0]\n return React.cloneElement({ ...child, props: null }, next)\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["props"]
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Group";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": ["export * from './Group'\n"],
5
+ "mappings": "AAAA,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./Group";
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts"],
4
+ "sourcesContent": ["export * from './Group'\n"],
5
+ "mappings": "AAAA,cAAc;",
6
+ "names": []
7
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@tamagui/group",
3
+ "version": "1.7.0",
4
+ "sideEffects": [
5
+ "*.css"
6
+ ],
7
+ "source": "src/index.ts",
8
+ "types": "./types/index.d.ts",
9
+ "main": "dist/cjs",
10
+ "module": "dist/esm",
11
+ "module:jsx": "dist/jsx",
12
+ "files": [
13
+ "src",
14
+ "types",
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tamagui-build",
19
+ "watch": "tamagui-build --watch",
20
+ "lint": "../../node_modules/.bin/rome check src",
21
+ "lint:fix": "../../node_modules/.bin/rome check --apply-suggested src",
22
+ "clean": "tamagui-build clean",
23
+ "clean:build": "tamagui-build clean:build"
24
+ },
25
+ "dependencies": {
26
+ "@tamagui/core": "1.7.0",
27
+ "@tamagui/create-context": "1.7.0",
28
+ "@tamagui/stacks": "1.7.0",
29
+ "@tamagui/use-controllable-state": "1.7.0",
30
+ "reforest": "^0.12.1"
31
+ },
32
+ "peerDependencies": {
33
+ "react": "*"
34
+ },
35
+ "devDependencies": {
36
+ "@tamagui/build": "1.7.0",
37
+ "react": "^18.2.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14"
43
+ }
package/src/Group.tsx ADDED
@@ -0,0 +1,318 @@
1
+ import {
2
+ GetProps,
3
+ TamaguiElement,
4
+ UnionableString,
5
+ Variable,
6
+ getConfig,
7
+ getExpandedShorthands,
8
+ getTokens,
9
+ getVariableValue,
10
+ isTamaguiElement,
11
+ mergeProps,
12
+ spacedChildren,
13
+ styled,
14
+ useMediaPropsActive,
15
+ withStaticProperties,
16
+ } from '@tamagui/core'
17
+ import { Scope, createContextScope } from '@tamagui/create-context'
18
+ import { ThemeableStack } from '@tamagui/stacks'
19
+ import { useControllableState } from '@tamagui/use-controllable-state'
20
+ import React, { Children, forwardRef, isValidElement } from 'react'
21
+ import { ScrollView } from 'react-native'
22
+ import { useIndex, useIndexedChildren } from 'reforest'
23
+
24
+ type DisablePassBorderRadius = boolean | 'bottom' | 'top' | 'start' | 'end'
25
+
26
+ interface GroupContextValue {
27
+ vertical: boolean
28
+ disablePassBorderRadius: DisablePassBorderRadius
29
+ onItemMount: () => void
30
+ onItemUnmount: () => void
31
+ radius?: number | UnionableString | Variable<any>
32
+ disabled?: boolean
33
+ }
34
+
35
+ const GROUP_NAME = 'Group'
36
+
37
+ type ScopedProps<P> = P & { __scopeGroup?: Scope }
38
+ const [createGroupContext, createGroupScope] = createContextScope(GROUP_NAME)
39
+ const [GroupProvider, useGroupContext] = createGroupContext<GroupContextValue>(GROUP_NAME)
40
+
41
+ export const GroupFrame = styled(ThemeableStack, {
42
+ name: 'GroupFrame',
43
+
44
+ variants: {
45
+ unstyled: {
46
+ false: {
47
+ size: '$true',
48
+ y: 0,
49
+ backgroundColor: '$background',
50
+ },
51
+ },
52
+
53
+ size: (val, { tokens }) => {
54
+ const borderRadius = tokens.radius[val] ?? val ?? tokens.radius['$true']
55
+ return {
56
+ borderRadius,
57
+ }
58
+ },
59
+ } as const,
60
+
61
+ defaultVariants: {
62
+ unstyled: false,
63
+ },
64
+ })
65
+
66
+ export type GroupProps = GetProps<typeof GroupFrame> & {
67
+ axis?: 'horizontal' | 'vertical'
68
+
69
+ scrollable?: boolean
70
+ /**
71
+ * @default false
72
+ */
73
+ showScrollIndicator?: boolean
74
+ disabled?: boolean
75
+ disablePassBorderRadius?: DisablePassBorderRadius
76
+ /**
77
+ * forces the group to use the Group.Item API
78
+ */
79
+ forceUseItem?: boolean
80
+ }
81
+
82
+ function createGroup(verticalDefault: boolean) {
83
+ return withStaticProperties(
84
+ forwardRef<TamaguiElement, ScopedProps<GroupProps>>((props, ref) => {
85
+ const activeProps = useMediaPropsActive(props)
86
+
87
+ const {
88
+ __scopeGroup,
89
+ children: childrenProp,
90
+ space,
91
+ size = '$true',
92
+ spaceDirection,
93
+ separator,
94
+ scrollable,
95
+ axis = verticalDefault ? 'vertical' : 'horizontal',
96
+ disabled: disabledProp,
97
+ disablePassBorderRadius: disablePassBorderRadiusProp,
98
+ borderRadius,
99
+ forceUseItem,
100
+ ...restProps
101
+ } = getExpandedShorthands(activeProps)
102
+
103
+ const vertical = axis === 'vertical'
104
+ const [itemChildrenCount, setItemChildrenCount] = useControllableState({
105
+ defaultProp: forceUseItem ? 1 : 0,
106
+ })
107
+ const isUsingItems = true // itemChildrenCount > 0
108
+
109
+ // 1 off given border to adjust for border radius? This should be user controllable
110
+ const radius =
111
+ borderRadius ??
112
+ (size ? getVariableValue(getTokens().radius[size]) - 1 : undefined)
113
+
114
+ const hasRadius = radius !== undefined
115
+ const disablePassBorderRadius = disablePassBorderRadiusProp ?? !hasRadius
116
+
117
+ if (!isUsingItems) console.log('screw up!')
118
+ const childrenArray = Children.toArray(childrenProp)
119
+ const children = isUsingItems
120
+ ? childrenProp
121
+ : childrenArray.map((child, i) => {
122
+ if (!isValidElement(child)) {
123
+ return child
124
+ }
125
+ const disabled = child.props.disabled ?? disabledProp
126
+
127
+ const isFirst = i === 0
128
+ const isLast = i === childrenArray.length - 1
129
+
130
+ const radiusStyles =
131
+ disablePassBorderRadius === true
132
+ ? null
133
+ : getBorderRadius({
134
+ isFirst,
135
+ isLast,
136
+ radius,
137
+ vertical,
138
+ disable: disablePassBorderRadius,
139
+ })
140
+ const props = {
141
+ disabled,
142
+ ...(isTamaguiElement(child) ? radiusStyles : { style: radiusStyles }),
143
+ }
144
+
145
+ return cloneElementWithPropOrder(child, props)
146
+ })
147
+
148
+ const indexedChildren = useIndexedChildren(
149
+ spacedChildren({
150
+ direction: spaceDirection,
151
+ separator,
152
+ space,
153
+ children,
154
+ })
155
+ )
156
+
157
+ const onItemMount = React.useCallback(
158
+ () => setItemChildrenCount((prev) => prev + 1),
159
+ []
160
+ )
161
+ const onItemUnmount = React.useCallback(
162
+ () => setItemChildrenCount((prev) => prev - 1),
163
+ []
164
+ )
165
+
166
+ return (
167
+ <GroupProvider
168
+ disablePassBorderRadius={disablePassBorderRadius}
169
+ vertical={axis === 'vertical'}
170
+ radius={radius}
171
+ disabled={disabledProp}
172
+ onItemMount={onItemMount}
173
+ onItemUnmount={onItemUnmount}
174
+ scope={__scopeGroup}
175
+ >
176
+ <GroupFrame
177
+ ref={ref}
178
+ size={size}
179
+ flexDirection={axis === 'horizontal' ? 'row' : 'column'}
180
+ borderRadius={borderRadius}
181
+ {...restProps}
182
+ >
183
+ {wrapScroll({ ...activeProps, axis }, indexedChildren)}
184
+ </GroupFrame>
185
+ </GroupProvider>
186
+ )
187
+ }),
188
+ {
189
+ Item: GroupItem,
190
+ }
191
+ )
192
+ }
193
+
194
+ const GroupItem = (props: ScopedProps<{ children: React.ReactNode }>) => {
195
+ const { __scopeGroup, children } = props
196
+ const groupItemProps = useGroupItem(
197
+ { disabled: isValidElement(children) ? children.props.disabled : undefined },
198
+ __scopeGroup
199
+ )
200
+
201
+ if (!isValidElement(children)) {
202
+ return children as any
203
+ }
204
+
205
+ if (isTamaguiElement(children)) {
206
+ return React.cloneElement(children, groupItemProps)
207
+ }
208
+
209
+ return React.cloneElement(children, {
210
+ style: {
211
+ ...children.props?.['style'],
212
+ ...groupItemProps,
213
+ },
214
+ } as any)
215
+ }
216
+
217
+ export const useGroupItem = (
218
+ childrenProps: { disabled: boolean },
219
+ __scopeGroup?: Scope
220
+ ) => {
221
+ const treeIndex = useIndex()
222
+ const context = useGroupContext('GroupItem', __scopeGroup)
223
+
224
+ React.useEffect(() => {
225
+ context.onItemMount()
226
+ return () => {
227
+ context.onItemUnmount()
228
+ }
229
+ }, [])
230
+
231
+ if (!treeIndex) {
232
+ throw Error('<Group.Item/> should only be used within a <Group/>')
233
+ }
234
+
235
+ const isFirst = treeIndex.index === 0
236
+ const isLast = treeIndex.index === treeIndex.maxIndex
237
+
238
+ const disabled = childrenProps.disabled ?? context.disabled
239
+
240
+ let propsToPass: Record<string, any> = {
241
+ disabled,
242
+ }
243
+
244
+ if (context.disablePassBorderRadius !== true) {
245
+ const borderRadius = getBorderRadius({
246
+ radius: context.radius,
247
+ isFirst,
248
+ isLast,
249
+ vertical: context.vertical,
250
+ disable: context.disablePassBorderRadius,
251
+ })
252
+ return { ...propsToPass, ...borderRadius }
253
+ }
254
+ return propsToPass
255
+ }
256
+
257
+ export const Group = createGroup(true)
258
+ export const YGroup = Group
259
+ export const XGroup = createGroup(false)
260
+
261
+ const wrapScroll = (
262
+ { scrollable, axis, showScrollIndicator = false }: GroupProps,
263
+ children: any
264
+ ) => {
265
+ if (scrollable)
266
+ return (
267
+ <ScrollView
268
+ {...(axis === 'vertical' && {
269
+ showsVerticalScrollIndicator: showScrollIndicator,
270
+ })}
271
+ {...(axis === 'horizontal' && {
272
+ horizontal: true,
273
+ showsHorizontalScrollIndicator: showScrollIndicator,
274
+ })}
275
+ >
276
+ {children}
277
+ </ScrollView>
278
+ )
279
+ return children
280
+ }
281
+
282
+ const getBorderRadius = ({
283
+ isFirst,
284
+ isLast,
285
+ radius,
286
+ vertical,
287
+ disable,
288
+ }: {
289
+ radius: any
290
+ vertical: boolean
291
+ isFirst: boolean
292
+ isLast: boolean
293
+ disable: DisablePassBorderRadius
294
+ }) => {
295
+ // TODO: RTL support would be nice here
296
+ return {
297
+ borderTopLeftRadius: isFirst && disable !== 'top' && disable !== 'start' ? radius : 0,
298
+ borderTopRightRadius:
299
+ disable !== 'top' &&
300
+ disable !== 'end' &&
301
+ ((vertical && isFirst) || (!vertical && isLast))
302
+ ? radius
303
+ : 0,
304
+ borderBottomLeftRadius:
305
+ disable !== 'bottom' &&
306
+ disable !== 'start' &&
307
+ ((vertical && isLast) || (!vertical && isFirst))
308
+ ? radius
309
+ : 0,
310
+ borderBottomRightRadius:
311
+ isLast && disable !== 'bottom' && disable !== 'end' ? radius : 0,
312
+ }
313
+ }
314
+
315
+ const cloneElementWithPropOrder = (child: any, props: Object) => {
316
+ const next = mergeProps(child.props, props, false, getConfig().shorthands)[0]
317
+ return React.cloneElement({ ...child, props: null }, next)
318
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './Group'