@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.
- package/dist/cjs/Group.js +261 -0
- package/dist/cjs/Group.js.map +7 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/esm/Group.js +234 -0
- package/dist/esm/Group.js.map +7 -0
- package/dist/esm/Group.mjs +234 -0
- package/dist/esm/Group.mjs.map +7 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/index.mjs.map +7 -0
- package/dist/jsx/Group.js +220 -0
- package/dist/jsx/Group.js.map +7 -0
- package/dist/jsx/Group.mjs +220 -0
- package/dist/jsx/Group.mjs.map +7 -0
- package/dist/jsx/index.js +2 -0
- package/dist/jsx/index.js.map +7 -0
- package/dist/jsx/index.mjs +2 -0
- package/dist/jsx/index.mjs.map +7 -0
- package/package.json +43 -0
- package/src/Group.tsx +318 -0
- package/src/index.ts +1 -0
- package/types/Group.d.ts +321 -0
- package/types/index.d.ts +2 -0
|
@@ -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
|
+
}
|
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'
|