@tamagui/list-item 2.7.7 → 3.0.0-beta.1091.1
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/ListItem.cjs +116 -228
- package/dist/cjs/ListItem.native.cjs +149 -0
- package/dist/cjs/ListItem.native.js +120 -230
- package/dist/cjs/ListItem.native.js.map +1 -1
- package/dist/cjs/index.cjs +9 -11
- package/dist/cjs/index.native.cjs +20 -0
- package/dist/cjs/index.native.js +9 -10
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/esm/ListItem.mjs +102 -217
- package/dist/esm/ListItem.mjs.map +1 -1
- package/dist/esm/ListItem.native.js +106 -220
- package/dist/esm/ListItem.native.js.map +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/index.mjs +1 -2
- package/dist/esm/index.native.js +1 -2
- package/dist/jsx/ListItem.mjs +102 -217
- package/dist/jsx/ListItem.mjs.map +1 -1
- package/dist/jsx/ListItem.native.cjs +149 -0
- package/dist/jsx/ListItem.native.js +120 -230
- package/dist/jsx/ListItem.native.js.map +1 -1
- package/dist/jsx/index.js +1 -2
- package/dist/jsx/index.mjs +1 -2
- package/dist/jsx/index.native.cjs +20 -0
- package/dist/jsx/index.native.js +9 -10
- package/dist/jsx/index.native.js.map +1 -1
- package/package.json +13 -11
- package/src/ListItem.tsx +173 -248
- package/types/ListItem.d.ts +105 -78
- package/types/ListItem.d.ts.map +1 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
- package/dist/esm/index.native.js.map +0 -1
- package/dist/jsx/index.js.map +0 -1
- package/dist/jsx/index.mjs.map +0 -1
package/src/ListItem.tsx
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
|
-
import { getFontSize } from '@tamagui/font-size'
|
|
2
1
|
import { getFontSized } from '@tamagui/get-font-sized'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { getIcon, useCurrentColor } from '@tamagui/helpers-tamagui'
|
|
2
|
+
import { getThemedIconSize, useGetThemedIcon } from '@tamagui/helpers-tamagui'
|
|
3
|
+
import { oneSizeSmaller, resolveSize } from '@tamagui/size'
|
|
6
4
|
import { YStack } from '@tamagui/stacks'
|
|
7
|
-
import {
|
|
5
|
+
import type { TextParentStyles } from '@tamagui/text'
|
|
6
|
+
import { SizableText, textParentProps, wrapChildrenInText } from '@tamagui/text'
|
|
8
7
|
import type { ColorTokens, FontSizeTokens, GetProps, SizeTokens } from '@tamagui/web'
|
|
9
|
-
import { createStyledContext, styled, View } from '@tamagui/web'
|
|
8
|
+
import { createStyledContext, splitStyleProps, styled, View } from '@tamagui/web'
|
|
10
9
|
import type { FunctionComponent, JSX, ReactNode } from 'react'
|
|
11
10
|
|
|
12
11
|
type IconProp = JSX.Element | FunctionComponent<{ color?: any; size?: any }> | null
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type ListItemProps = GetProps<typeof ListItemFrame> & ListItemExtraProps
|
|
26
|
-
|
|
27
|
-
const NAME = 'ListItem'
|
|
28
|
-
|
|
29
|
-
const context = createStyledContext<{
|
|
30
|
-
size?: SizeTokens
|
|
31
|
-
variant?: ListItemVariant
|
|
13
|
+
/**
|
|
14
|
+
* The three props every part shares. A styled component that declares this
|
|
15
|
+
* context reads these from an ancestor and, for any it was passed directly,
|
|
16
|
+
* republishes them to its own descendants — which is the whole mechanism for
|
|
17
|
+
* getting size and color from a ListItem down to its text and icons.
|
|
18
|
+
*/
|
|
19
|
+
export const ListItemContext = createStyledContext<{
|
|
20
|
+
size?: SizeTokens | true
|
|
21
|
+
variant?: 'outlined'
|
|
32
22
|
color?: ColorTokens | string
|
|
33
23
|
}>({
|
|
34
24
|
size: undefined,
|
|
@@ -36,262 +26,197 @@ const context = createStyledContext<{
|
|
|
36
26
|
color: undefined,
|
|
37
27
|
})
|
|
38
28
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
size: '$true',
|
|
49
|
-
alignItems: 'center',
|
|
50
|
-
justifyContent: 'space-between',
|
|
51
|
-
flexWrap: 'nowrap',
|
|
52
|
-
borderColor: '$borderColor',
|
|
53
|
-
width: '100%',
|
|
54
|
-
maxWidth: '100%',
|
|
55
|
-
overflow: 'hidden',
|
|
56
|
-
flexDirection: 'row',
|
|
57
|
-
backgroundColor: '$background',
|
|
58
|
-
cursor: 'default',
|
|
59
|
-
|
|
60
|
-
hoverStyle: {
|
|
61
|
-
backgroundColor: '$backgroundHover',
|
|
62
|
-
borderColor: '$borderColorHover',
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
pressStyle: {
|
|
66
|
-
backgroundColor: '$backgroundPress',
|
|
67
|
-
borderColor: '$borderColorPress',
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
|
|
72
|
-
variant: {
|
|
73
|
-
outlined:
|
|
74
|
-
process.env.TAMAGUI_HEADLESS === '1'
|
|
75
|
-
? {}
|
|
76
|
-
: {
|
|
77
|
-
backgroundColor: 'transparent',
|
|
78
|
-
borderWidth: 1,
|
|
79
|
-
borderColor: '$borderColor',
|
|
80
|
-
|
|
81
|
-
hoverStyle: {
|
|
82
|
-
backgroundColor: 'transparent',
|
|
83
|
-
borderColor: '$borderColorHover',
|
|
84
|
-
},
|
|
29
|
+
export const listItemSizeVariant = styled.dynamic<SizeTokens | true>((val, env) => {
|
|
30
|
+
const { frame } = resolveSize(val, env)
|
|
31
|
+
return {
|
|
32
|
+
minHeight: frame.minHeight,
|
|
33
|
+
paddingHorizontal: frame.paddingHorizontal,
|
|
34
|
+
paddingVertical: frame.paddingVertical,
|
|
35
|
+
gap: frame.gap,
|
|
36
|
+
}
|
|
37
|
+
})
|
|
85
38
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
},
|
|
39
|
+
const listItemSubtitleSizeVariant = styled.dynamic<SizeTokens | true>((val, env) => {
|
|
40
|
+
return getFontSized(oneSizeSmaller(val, env.sizes) as FontSizeTokens, env)
|
|
41
|
+
})
|
|
92
42
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
43
|
+
// structural layout, the size mechanism, and the disabled pointer-event block.
|
|
44
|
+
// theme decoration (palette, border, cursor, the outlined/active appearance,
|
|
45
|
+
// disabled dimming) lives in a skin — see tamagui's components/ListItem.tsx
|
|
46
|
+
export const ListItemFrame = styled(View, {
|
|
47
|
+
context: ListItemContext,
|
|
48
|
+
displayName: 'ListItemFrame',
|
|
49
|
+
// role is the cross-platform one; render only lands on web
|
|
50
|
+
role: 'listitem',
|
|
51
|
+
render: 'li',
|
|
52
|
+
size: true,
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
justifyContent: 'space-between',
|
|
55
|
+
flexWrap: 'nowrap',
|
|
56
|
+
width: '100%',
|
|
57
|
+
maxWidth: '100%',
|
|
58
|
+
overflow: 'hidden',
|
|
59
|
+
flexDirection: 'row',
|
|
104
60
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
hoverStyle: {
|
|
108
|
-
backgroundColor: '$background',
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
61
|
+
variants: {
|
|
62
|
+
size: listItemSizeVariant,
|
|
112
63
|
|
|
113
64
|
disabled: {
|
|
114
65
|
true: {
|
|
115
|
-
opacity: 0.5,
|
|
116
66
|
pointerEvents: 'none',
|
|
117
67
|
},
|
|
118
68
|
},
|
|
119
69
|
} as const,
|
|
120
|
-
|
|
121
|
-
defaultVariants: {
|
|
122
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
123
|
-
},
|
|
124
70
|
})
|
|
125
71
|
|
|
126
|
-
const ListItemText = styled(SizableText, {
|
|
127
|
-
context,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
size: '$true',
|
|
135
|
-
flexGrow: 1,
|
|
136
|
-
flexShrink: 1,
|
|
137
|
-
ellipsis: true,
|
|
138
|
-
cursor: 'inherit',
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
} as const,
|
|
142
|
-
|
|
143
|
-
defaultVariants: {
|
|
144
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
145
|
-
},
|
|
72
|
+
export const ListItemText = styled(SizableText, {
|
|
73
|
+
context: ListItemContext,
|
|
74
|
+
displayName: 'ListItemText',
|
|
75
|
+
size: true,
|
|
76
|
+
flexGrow: 1,
|
|
77
|
+
flexShrink: 1,
|
|
78
|
+
ellipsis: true,
|
|
79
|
+
cursor: 'inherit',
|
|
146
80
|
})
|
|
147
81
|
|
|
148
|
-
const ListItemSubtitle = styled(ListItemText, {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
false: {
|
|
154
|
-
opacity: 0.6,
|
|
155
|
-
maxWidth: '100%',
|
|
156
|
-
color: '$color',
|
|
157
|
-
},
|
|
158
|
-
},
|
|
82
|
+
export const ListItemSubtitle = styled(ListItemText, {
|
|
83
|
+
context: ListItemContext,
|
|
84
|
+
displayName: 'ListItemSubtitle',
|
|
85
|
+
opacity: 0.6,
|
|
86
|
+
maxWidth: '100%',
|
|
159
87
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const oneSmaller = getSize(val, {
|
|
163
|
-
shift: -1,
|
|
164
|
-
excludeHalfSteps: true,
|
|
165
|
-
})
|
|
166
|
-
const fontStyle = getFontSized(oneSmaller.key as FontSizeTokens, extras as any)
|
|
167
|
-
return fontStyle
|
|
168
|
-
},
|
|
169
|
-
},
|
|
88
|
+
variants: {
|
|
89
|
+
size: listItemSubtitleSizeVariant,
|
|
170
90
|
} as const,
|
|
171
|
-
|
|
172
|
-
defaultVariants: {
|
|
173
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
174
|
-
},
|
|
175
91
|
})
|
|
176
92
|
|
|
177
|
-
const ListItemTitle = styled(ListItemText, {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
variants: {
|
|
181
|
-
unstyled: {
|
|
182
|
-
false: {},
|
|
183
|
-
},
|
|
184
|
-
} as const,
|
|
185
|
-
defaultVariants: {
|
|
186
|
-
unstyled: process.env.TAMAGUI_HEADLESS === '1',
|
|
187
|
-
},
|
|
93
|
+
export const ListItemTitle = styled(ListItemText, {
|
|
94
|
+
context: ListItemContext,
|
|
95
|
+
displayName: 'ListItemTitle',
|
|
188
96
|
})
|
|
189
97
|
|
|
190
|
-
|
|
191
|
-
children:
|
|
192
|
-
size?: SizeTokens
|
|
98
|
+
export type ListItemIconProps = {
|
|
99
|
+
children: ReactNode
|
|
100
|
+
size?: SizeTokens | true
|
|
193
101
|
scaleIcon?: number
|
|
194
|
-
}
|
|
195
|
-
const { children, size, scaleIcon = 1 } = props
|
|
196
|
-
const styledContext = context.useStyledContext()
|
|
197
|
-
if (!styledContext) {
|
|
198
|
-
throw new Error('ListItem.Icon must be used within a ListItem')
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const sizeToken = size ?? styledContext.size ?? '$true'
|
|
202
|
-
const iconColor = useCurrentColor(styledContext.color)
|
|
203
|
-
|
|
204
|
-
const iconSize = getFontSize(sizeToken as any) * scaleIcon
|
|
102
|
+
}
|
|
205
103
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
104
|
+
export const ListItemIcon = ({ children, size, scaleIcon = 1 }: ListItemIconProps) => {
|
|
105
|
+
const context = ListItemContext.useStyledContext()
|
|
106
|
+
const getThemedIcon = useGetThemedIcon({
|
|
107
|
+
size: getThemedIconSize(size ?? context.size ?? true, scaleIcon),
|
|
108
|
+
color: context.color,
|
|
209
109
|
})
|
|
110
|
+
|
|
111
|
+
return getThemedIcon(children)
|
|
210
112
|
}
|
|
211
113
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
} = propsIn
|
|
114
|
+
/**
|
|
115
|
+
* The props `useListItem` reads and replaces, so exactly what its result omits.
|
|
116
|
+
*/
|
|
117
|
+
type ListItemConsumedProps = {
|
|
118
|
+
children?: ReactNode
|
|
119
|
+
icon?: IconProp
|
|
120
|
+
iconAfter?: IconProp
|
|
121
|
+
iconSize?: SizeTokens | true
|
|
122
|
+
scaleIcon?: number
|
|
123
|
+
subTitle?: ReactNode
|
|
124
|
+
title?: ReactNode
|
|
125
|
+
}
|
|
225
126
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
127
|
+
export type ListItemBehaviorProps = TextParentStyles &
|
|
128
|
+
ListItemConsumedProps & {
|
|
129
|
+
// read to theme an `icon` prop, then passed through untouched: the frame
|
|
130
|
+
// declares the styled context, so it is what publishes these to the parts
|
|
131
|
+
color?: ColorTokens | string
|
|
132
|
+
size?: SizeTokens | true
|
|
133
|
+
}
|
|
230
134
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
135
|
+
/**
|
|
136
|
+
* What `useListItem` returns: the caller's props minus the ones it consumed.
|
|
137
|
+
* Spelled out rather than cast, so a skin that spreads the result onto a frame
|
|
138
|
+
* is type-checked on exactly what it will receive.
|
|
139
|
+
*/
|
|
140
|
+
export type UseListItemProps<Props extends ListItemBehaviorProps> = Omit<
|
|
141
|
+
Omit<Props, keyof TextParentStyles | keyof typeof textParentProps>,
|
|
142
|
+
keyof ListItemConsumedProps
|
|
143
|
+
> & {
|
|
144
|
+
children: ReactNode
|
|
145
|
+
color?: Props['color']
|
|
146
|
+
}
|
|
242
147
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
148
|
+
/**
|
|
149
|
+
* ListItem behavior: theming the icon props and assembling title, subtitle, and
|
|
150
|
+
* children into the frame's single child. Flat text styles are handed directly
|
|
151
|
+
* to generated text, while size and color still reach the parts through the
|
|
152
|
+
* styled context.
|
|
153
|
+
*/
|
|
154
|
+
export function useListItem<Props extends ListItemBehaviorProps>(
|
|
155
|
+
propsIn: Props
|
|
156
|
+
): { props: UseListItemProps<Props> } {
|
|
157
|
+
const [wrappedTextProps, listItemProps] = splitStyleProps(propsIn, {
|
|
158
|
+
expandShorthands: true,
|
|
159
|
+
filter: textParentProps,
|
|
160
|
+
})
|
|
161
|
+
const {
|
|
162
|
+
children,
|
|
163
|
+
icon,
|
|
164
|
+
iconAfter,
|
|
165
|
+
iconSize,
|
|
166
|
+
scaleIcon = 1,
|
|
167
|
+
subTitle,
|
|
168
|
+
title,
|
|
169
|
+
...frameProps
|
|
170
|
+
} = listItemProps
|
|
171
|
+
const { noTextWrap, textProps, ...textStyleProps } = wrappedTextProps
|
|
172
|
+
|
|
173
|
+
// the frame publishes size and color to every part below it, but an `icon`
|
|
174
|
+
// prop is themed here, before the frame renders, so it reads them itself
|
|
175
|
+
const context = ListItemContext.useStyledContext()
|
|
176
|
+
const getThemedIcon = useGetThemedIcon({
|
|
177
|
+
size: getThemedIconSize(iconSize ?? propsIn.size ?? context.size ?? true, scaleIcon),
|
|
178
|
+
color: propsIn.color ?? context.color,
|
|
179
|
+
})
|
|
253
180
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
181
|
+
const wrappedChildren = wrapChildrenInText(
|
|
182
|
+
ListItemText,
|
|
183
|
+
{ children, noTextWrap, textProps },
|
|
184
|
+
textStyleProps
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
props: {
|
|
189
|
+
...frameProps,
|
|
190
|
+
color: propsIn.color,
|
|
191
|
+
children: (
|
|
192
|
+
<>
|
|
193
|
+
{icon ? getThemedIcon(icon) : null}
|
|
194
|
+
{title || subTitle ? (
|
|
195
|
+
<YStack flex={1}>
|
|
196
|
+
{typeof title === 'string' ? (
|
|
197
|
+
<ListItemTitle {...textStyleProps} {...textProps}>
|
|
262
198
|
{title}
|
|
263
199
|
</ListItemTitle>
|
|
264
200
|
) : (
|
|
265
201
|
title
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
)}
|
|
284
|
-
{themedIconAfter}
|
|
285
|
-
</ListItemFrame>
|
|
286
|
-
)
|
|
202
|
+
)}
|
|
203
|
+
{typeof subTitle === 'string' ? (
|
|
204
|
+
<ListItemSubtitle {...textStyleProps} {...textProps}>
|
|
205
|
+
{subTitle}
|
|
206
|
+
</ListItemSubtitle>
|
|
207
|
+
) : (
|
|
208
|
+
subTitle
|
|
209
|
+
)}
|
|
210
|
+
{wrappedChildren}
|
|
211
|
+
</YStack>
|
|
212
|
+
) : (
|
|
213
|
+
wrappedChildren
|
|
214
|
+
)}
|
|
215
|
+
{iconAfter ? getThemedIcon(iconAfter) : null}
|
|
216
|
+
</>
|
|
217
|
+
),
|
|
218
|
+
},
|
|
287
219
|
}
|
|
288
|
-
|
|
220
|
+
}
|
|
289
221
|
|
|
290
|
-
export
|
|
291
|
-
Apply: context.Provider,
|
|
292
|
-
Frame: ListItemFrame,
|
|
293
|
-
Text: ListItemText,
|
|
294
|
-
Subtitle: ListItemSubtitle,
|
|
295
|
-
Icon: ListItemIcon,
|
|
296
|
-
Title: ListItemTitle,
|
|
297
|
-
})
|
|
222
|
+
export type ListItemFrameProps = GetProps<typeof ListItemFrame>
|