@tamagui/list-item 2.7.7 → 3.0.0-beta.643.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/src/ListItem.tsx CHANGED
@@ -1,12 +1,25 @@
1
- import { getFontSize } from '@tamagui/font-size'
2
1
  import { getFontSized } from '@tamagui/get-font-sized'
3
- import { getSize, getSpace } from '@tamagui/get-token'
2
+ import { oneSizeTokenSmaller } from '@tamagui/get-token'
4
3
  import { withStaticProperties } from '@tamagui/helpers'
5
- import { getIcon, useCurrentColor } from '@tamagui/helpers-tamagui'
4
+ import { getThemedIconSize, useGetThemedIcon } from '@tamagui/helpers-tamagui'
6
5
  import { YStack } from '@tamagui/stacks'
6
+ import { resolveSizeToken } from '@tamagui/size'
7
7
  import { SizableText, wrapChildrenInText } from '@tamagui/text'
8
- import type { ColorTokens, FontSizeTokens, GetProps, SizeTokens } from '@tamagui/web'
9
- import { createStyledContext, styled, View } from '@tamagui/web'
8
+ import type {
9
+ ColorTokens,
10
+ FontSizeTokens,
11
+ GetProps,
12
+ SizeTokens,
13
+ VariantSpreadExtras,
14
+ } from '@tamagui/web'
15
+ import {
16
+ createStyledContext,
17
+ createStyledHOC,
18
+ getVariableValue,
19
+ styled,
20
+ useProps,
21
+ View,
22
+ } from '@tamagui/web'
10
23
  import type { FunctionComponent, JSX, ReactNode } from 'react'
11
24
 
12
25
  type IconProp = JSX.Element | FunctionComponent<{ color?: any; size?: any }> | null
@@ -19,7 +32,8 @@ export type ListItemExtraProps = {
19
32
  scaleIcon?: number
20
33
  title?: ReactNode
21
34
  subTitle?: ReactNode
22
- iconSize?: SizeTokens
35
+ iconSize?: SizeTokens | true
36
+ color?: ColorTokens | string
23
37
  }
24
38
 
25
39
  export type ListItemProps = GetProps<typeof ListItemFrame> & ListItemExtraProps
@@ -27,7 +41,7 @@ export type ListItemProps = GetProps<typeof ListItemFrame> & ListItemExtraProps
27
41
  const NAME = 'ListItem'
28
42
 
29
43
  const context = createStyledContext<{
30
- size?: SizeTokens
44
+ size?: SizeTokens | true
31
45
  variant?: ListItemVariant
32
46
  color?: ColorTokens | string
33
47
  }>({
@@ -36,160 +50,97 @@ const context = createStyledContext<{
36
50
  color: undefined,
37
51
  })
38
52
 
53
+ export const listItemSizeVariant = (
54
+ val: SizeTokens | true,
55
+ { tokens }: VariantSpreadExtras<any>
56
+ ) => {
57
+ const sizeToken = resolveSizeToken(val, 'size')
58
+ const spaceToken = resolveSizeToken(val, 'space')
59
+ const size = typeof sizeToken === 'number' ? sizeToken : tokens.size[sizeToken]
60
+ const sizeVal = getVariableValue(size) as number
61
+ return {
62
+ minHeight: size,
63
+ paddingHorizontal: tokens.space[spaceToken],
64
+ paddingVertical: Math.max(0, Math.round(sizeVal * 0.36 - 9)),
65
+ gap: getThemedIconSize(sizeToken, 0.4),
66
+ }
67
+ }
68
+
69
+ const listItemSubtitleSizeVariant = (
70
+ val: SizeTokens | true,
71
+ extras: VariantSpreadExtras<any>
72
+ ) => {
73
+ const fontSizeToken = resolveSizeToken(val, 'fontSize')
74
+ const oneSmaller = oneSizeTokenSmaller(fontSizeToken)
75
+ return getFontSized(oneSmaller as FontSizeTokens, extras as any)
76
+ }
77
+
78
+ // Unstyled ListItem frame: structural layout + the size mechanism + the
79
+ // disabled pointer-event block only. Theme decoration (palette, border, cursor,
80
+ // hover/press color styling, the outlined/active appearance, disabled dimming)
81
+ // lives in the tamagui skin (code/ui/tamagui/src/components/ListItem.tsx).
39
82
  const ListItemFrame = styled(View, {
40
83
  context,
41
- name: NAME,
84
+ displayName: NAME,
42
85
  render: 'li',
43
86
  role: 'listitem',
87
+ size: true,
88
+ alignItems: 'center',
89
+ justifyContent: 'space-between',
90
+ flexWrap: 'nowrap',
91
+ width: '100%',
92
+ maxWidth: '100%',
93
+ overflow: 'hidden',
94
+ flexDirection: 'row',
44
95
 
45
96
  variants: {
46
- unstyled: {
47
- false: {
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
- },
85
-
86
- pressStyle: {
87
- backgroundColor: 'transparent',
88
- borderColor: '$borderColorPress',
89
- },
90
- },
91
- },
92
-
93
97
  size: {
94
- '...size': (val: SizeTokens, { tokens }) => {
95
- return {
96
- minHeight: tokens.size[val],
97
- paddingHorizontal: tokens.space[val],
98
- paddingVertical: getSpace(tokens.space[val], {
99
- shift: -4,
100
- }),
101
- }
102
- },
103
- },
104
-
105
- active: {
106
- true: {
107
- hoverStyle: {
108
- backgroundColor: '$background',
109
- },
110
- },
98
+ true: listItemSizeVariant,
99
+ Size: listItemSizeVariant,
111
100
  },
112
101
 
113
102
  disabled: {
114
103
  true: {
115
- opacity: 0.5,
116
104
  pointerEvents: 'none',
117
105
  },
118
106
  },
119
107
  } as const,
120
-
121
- defaultVariants: {
122
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
123
- },
124
108
  })
125
109
 
110
+ // text color comes from the styled context (the tamagui skin sets a default
111
+ // `color` on ListItem, which flows through as context.color); unstyled it
112
+ // inherits.
126
113
  const ListItemText = styled(SizableText, {
127
114
  context,
128
- name: 'ListItemText',
129
-
130
- variants: {
131
- unstyled: {
132
- false: {
133
- color: '$color',
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
- },
115
+ displayName: 'ListItemText',
116
+ size: true,
117
+ flexGrow: 1,
118
+ flexShrink: 1,
119
+ ellipsis: true,
120
+ cursor: 'inherit',
146
121
  })
147
122
 
148
123
  const ListItemSubtitle = styled(ListItemText, {
149
- name: 'ListItemSubtitle',
124
+ displayName: 'ListItemSubtitle',
150
125
  context,
126
+ opacity: 0.6,
127
+ maxWidth: '100%',
151
128
  variants: {
152
- unstyled: {
153
- false: {
154
- opacity: 0.6,
155
- maxWidth: '100%',
156
- color: '$color',
157
- },
158
- },
159
-
160
129
  size: {
161
- '...size': (val, extras) => {
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
- },
130
+ true: listItemSubtitleSizeVariant,
131
+ Size: listItemSubtitleSizeVariant,
169
132
  },
170
133
  } as const,
171
-
172
- defaultVariants: {
173
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
174
- },
175
134
  })
176
135
 
177
136
  const ListItemTitle = styled(ListItemText, {
178
- name: 'ListItemTitle',
137
+ displayName: 'ListItemTitle',
179
138
  context,
180
- variants: {
181
- unstyled: {
182
- false: {},
183
- },
184
- } as const,
185
- defaultVariants: {
186
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
187
- },
188
139
  })
189
140
 
190
141
  const ListItemIcon = (props: {
191
142
  children: React.ReactNode
192
- size?: SizeTokens
143
+ size?: SizeTokens | true
193
144
  scaleIcon?: number
194
145
  }) => {
195
146
  const { children, size, scaleIcon = 1 } = props
@@ -198,91 +149,106 @@ const ListItemIcon = (props: {
198
149
  throw new Error('ListItem.Icon must be used within a ListItem')
199
150
  }
200
151
 
201
- const sizeToken = size ?? styledContext.size ?? '$true'
202
- const iconColor = useCurrentColor(styledContext.color)
203
-
204
- const iconSize = getFontSize(sizeToken as any) * scaleIcon
152
+ const sizeToken = size ?? styledContext.size ?? true
153
+ const contextColor = styledContext.color
154
+ const iconColorProp =
155
+ contextColor === 'unset' || typeof contextColor === 'number'
156
+ ? undefined
157
+ : contextColor
205
158
 
206
- return getIcon(children, {
159
+ const iconSize = getThemedIconSize(sizeToken, scaleIcon)
160
+ const getThemedIcon = useGetThemedIcon({
207
161
  size: iconSize,
208
- color: iconColor,
162
+ color: iconColorProp,
209
163
  })
164
+
165
+ return getThemedIcon(children)
210
166
  }
211
167
 
212
- const ListItemComponent = ListItemFrame.styleable<ListItemExtraProps>(
213
- function ListItem(propsIn, ref) {
168
+ const ListItemComponent = createStyledHOC(
169
+ ListItemFrame,
170
+ function ListItem(propsIn: ListItemProps, ref) {
171
+ const processedProps = useProps(propsIn, {
172
+ noNormalize: true,
173
+ noExpand: true,
174
+ })
175
+
214
176
  const {
215
177
  children,
216
178
  icon,
217
179
  iconAfter,
218
180
  scaleIcon = 1,
219
- unstyled = false,
220
181
  subTitle,
221
182
  title,
222
183
  iconSize,
184
+ color,
223
185
  ...rest
224
- } = propsIn
186
+ } = processedProps
225
187
 
226
- const size = propsIn.size || '$true'
227
188
  const styledContext = context.useStyledContext()
228
- const iconColor = useCurrentColor(styledContext?.color)
229
- const iconSizeNumber = getFontSize(iconSize || (size as any)) * scaleIcon
230
-
231
- const iconSpacing = iconSizeNumber * 0.4
232
- const themedIcon = icon ? (
233
- <View marginRight={iconSpacing}>
234
- {getIcon(icon, { size: iconSizeNumber, color: iconColor })}
235
- </View>
236
- ) : null
237
- const themedIconAfter = iconAfter ? (
238
- <View marginLeft={iconSpacing}>
239
- {getIcon(iconAfter, { size: iconSizeNumber, color: iconColor })}
240
- </View>
241
- ) : null
242
-
243
- const wrappedChildren = wrapChildrenInText(
244
- ListItemText,
245
- { children },
246
- propsIn.unstyled !== true
247
- ? {
248
- unstyled: process.env.TAMAGUI_HEADLESS === '1',
249
- fontSize: propsIn.size,
250
- }
251
- : undefined
252
- )
189
+ const size = rest.size ?? propsIn.size ?? styledContext?.size ?? true
190
+ const contextColor = color ?? propsIn.color ?? styledContext?.color
191
+ const iconColorProp =
192
+ contextColor === 'unset' || typeof contextColor === 'number'
193
+ ? undefined
194
+ : contextColor
195
+ const iconSizeNumber = getThemedIconSize(iconSize ?? (size as any), scaleIcon)
196
+ const getThemedIcon = useGetThemedIcon({
197
+ size: iconSizeNumber,
198
+ color: iconColorProp,
199
+ })
200
+
201
+ const themedIcon = icon ? getThemedIcon(icon) : null
202
+ const themedIconAfter = iconAfter ? getThemedIcon(iconAfter) : null
203
+
204
+ const wrappedChildren = wrapChildrenInText(ListItemText, { children }, { size })
205
+
206
+ const listItemContext = {
207
+ ...styledContext,
208
+ color: contextColor,
209
+ size: size as SizeTokens,
210
+ variant: (rest.variant ?? propsIn.variant ?? styledContext?.variant) as
211
+ | ListItemVariant
212
+ | undefined,
213
+ }
214
+ const frameProps = {
215
+ size,
216
+ variant: listItemContext.variant,
217
+ ...rest,
218
+ }
253
219
 
254
220
  return (
255
- <ListItemFrame ref={ref} {...rest}>
256
- {themedIcon}
257
- {title || subTitle ? (
258
- <YStack flex={1}>
259
- {title ? (
260
- typeof title === 'string' ? (
261
- <ListItemTitle unstyled={unstyled} size={size as any}>
262
- {title}
263
- </ListItemTitle>
264
- ) : (
265
- title
266
- )
267
- ) : null}
268
- {subTitle ? (
269
- <>
270
- {typeof subTitle === 'string' ? (
271
- <ListItemSubtitle unstyled={unstyled} size={size}>
272
- {subTitle}
273
- </ListItemSubtitle>
274
- ) : (
275
- subTitle
276
- )}
277
- </>
278
- ) : null}
279
- {wrappedChildren}
280
- </YStack>
281
- ) : (
282
- wrappedChildren
283
- )}
284
- {themedIconAfter}
285
- </ListItemFrame>
221
+ <context.Provider {...listItemContext}>
222
+ <ListItemFrame ref={ref} {...(frameProps as any)}>
223
+ <context.Provider {...listItemContext}>
224
+ {themedIcon}
225
+ {title || subTitle ? (
226
+ <YStack flex={1}>
227
+ {title ? (
228
+ typeof title === 'string' ? (
229
+ <ListItemTitle size={size as any}>{title}</ListItemTitle>
230
+ ) : (
231
+ title
232
+ )
233
+ ) : null}
234
+ {subTitle ? (
235
+ <>
236
+ {typeof subTitle === 'string' ? (
237
+ <ListItemSubtitle size={size}>{subTitle}</ListItemSubtitle>
238
+ ) : (
239
+ subTitle
240
+ )}
241
+ </>
242
+ ) : null}
243
+ {wrappedChildren}
244
+ </YStack>
245
+ ) : (
246
+ wrappedChildren
247
+ )}
248
+ {themedIconAfter}
249
+ </context.Provider>
250
+ </ListItemFrame>
251
+ </context.Provider>
286
252
  )
287
253
  }
288
254
  )