jfs-components 0.1.28 → 0.1.30
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 +15 -2
- package/lib/commonjs/components/CompareTable/CompareTable.js +191 -25
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +66 -6
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/commonjs/components/Table/Table.js +27 -7
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CompareTable/CompareTable.js +191 -26
- package/lib/module/components/ContentSheet/ContentSheet.js +69 -9
- package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
- package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/module/components/Table/Table.js +27 -7
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +21 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +14 -1
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
- package/lib/typescript/src/components/Table/Table.d.ts +9 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CompareTable/CompareTable.tsx +224 -30
- package/src/components/ContentSheet/ContentSheet.tsx +88 -8
- package/src/components/MoneyValue/MoneyValue.tsx +216 -65
- package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
- package/src/components/Table/Table.tsx +29 -4
- package/src/icons/registry.ts +1 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import React, { useMemo, useEffect, useRef } from 'react'
|
|
1
|
+
import React, { useMemo, useEffect, useRef, useState, useCallback } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
5
|
+
TextInput,
|
|
5
6
|
Pressable,
|
|
6
7
|
Animated,
|
|
8
|
+
Platform,
|
|
9
|
+
Keyboard,
|
|
10
|
+
type GestureResponderEvent,
|
|
11
|
+
type LayoutChangeEvent,
|
|
7
12
|
type StyleProp,
|
|
8
13
|
type ViewStyle,
|
|
9
14
|
type TextStyle,
|
|
@@ -58,6 +63,15 @@ export type MoneyValueProps = {
|
|
|
58
63
|
hidden?: boolean
|
|
59
64
|
/** When true, a blinking vertical cursor is shown at the end of the value text. */
|
|
60
65
|
focused?: boolean
|
|
66
|
+
/**
|
|
67
|
+
* When true, tapping the component enters an inline edit mode where the value
|
|
68
|
+
* becomes a numeric TextInput. The user can type digits or delete the existing
|
|
69
|
+
* amount. Only numeric characters (including one decimal point) are accepted.
|
|
70
|
+
* On blur or submit the edit is committed and the display restores.
|
|
71
|
+
*/
|
|
72
|
+
editable?: boolean
|
|
73
|
+
/** Called when the value changes during editing (after commit on blur). */
|
|
74
|
+
onValueChange?: (newValue: string | number) => void
|
|
61
75
|
/** Modes configuration mapped to Figma tokens. */
|
|
62
76
|
modes?: Modes
|
|
63
77
|
style?: StyleProp<ViewStyle>
|
|
@@ -85,6 +99,8 @@ function MoneyValue({
|
|
|
85
99
|
negative,
|
|
86
100
|
focused = false,
|
|
87
101
|
hidden = false,
|
|
102
|
+
editable = false,
|
|
103
|
+
onValueChange,
|
|
88
104
|
modes = EMPTY_MODES,
|
|
89
105
|
style,
|
|
90
106
|
valueStyle,
|
|
@@ -96,23 +112,60 @@ function MoneyValue({
|
|
|
96
112
|
...rest
|
|
97
113
|
}: MoneyValueProps) {
|
|
98
114
|
// Auto-detect negative from value and compute display value
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
trimmed.startsWith('-') || (typeof value === 'number' && value < 0)
|
|
115
|
+
const stringValue = String(value)
|
|
116
|
+
const trimmed = stringValue.trim()
|
|
117
|
+
const valueIsNegative =
|
|
118
|
+
trimmed.startsWith('-') || (typeof value === 'number' && value < 0)
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
120
|
+
// Strip leading minus sign for display (we show it separately)
|
|
121
|
+
const absoluteValue = trimmed.startsWith('-') ? trimmed.slice(1) : trimmed
|
|
107
122
|
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
// Use explicit negative prop if provided, otherwise use auto-detected
|
|
124
|
+
const showNegative = negative !== undefined ? negative : valueIsNegative
|
|
110
125
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
126
|
+
const isNegative = hidden ? false : showNegative
|
|
127
|
+
const displayValue = hidden ? '•••' : absoluteValue
|
|
128
|
+
|
|
129
|
+
// Inline editing state
|
|
130
|
+
const [isEditing, setIsEditing] = useState(false)
|
|
131
|
+
const [editBuffer, setEditBuffer] = useState('')
|
|
132
|
+
const inputRef = useRef<TextInput>(null)
|
|
133
|
+
// Always-current snapshot of the display value for callbacks to reference.
|
|
134
|
+
const displayValueRef = useRef(displayValue)
|
|
135
|
+
displayValueRef.current = displayValue
|
|
136
|
+
|
|
137
|
+
const enterEditMode = useCallback(() => {
|
|
138
|
+
setIsEditing(true)
|
|
139
|
+
setEditBuffer(displayValueRef.current)
|
|
140
|
+
// Focus the input on next frame so the keyboard opens
|
|
141
|
+
setTimeout(() => inputRef.current?.focus(), 0)
|
|
142
|
+
}, [])
|
|
143
|
+
|
|
144
|
+
const commitEdit = useCallback(() => {
|
|
145
|
+
setIsEditing(false)
|
|
146
|
+
const resolved = editBuffer || '0'
|
|
147
|
+
if (resolved !== displayValueRef.current) {
|
|
148
|
+
onValueChange?.(resolved)
|
|
149
|
+
}
|
|
150
|
+
}, [editBuffer, onValueChange])
|
|
151
|
+
|
|
152
|
+
const handleChangeText = useCallback(
|
|
153
|
+
(text: string) => {
|
|
154
|
+
// Allow only digits and at most one decimal point
|
|
155
|
+
const filtered = text.replace(/[^0-9.]/g, '')
|
|
156
|
+
const parts = filtered.split('.')
|
|
157
|
+
const clean = parts.length > 2 ? parts[0] + '.' + parts.slice(1).join('') : filtered
|
|
158
|
+
setEditBuffer(clean)
|
|
159
|
+
},
|
|
160
|
+
[],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
const handlePress = useCallback((event: GestureResponderEvent) => {
|
|
164
|
+
if (editable) {
|
|
165
|
+
enterEditMode()
|
|
114
166
|
}
|
|
115
|
-
|
|
167
|
+
onPress?.(event)
|
|
168
|
+
}, [editable, enterEditMode, onPress])
|
|
116
169
|
|
|
117
170
|
// Resolve typography and layout tokens from Figma
|
|
118
171
|
const textColor = getVariableByName('moneyValue/text/color', modes)
|
|
@@ -191,64 +244,162 @@ function MoneyValue({
|
|
|
191
244
|
}
|
|
192
245
|
}, [focused, cursorOpacity])
|
|
193
246
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
>
|
|
218
|
-
{resolvedCurrency}
|
|
219
|
-
</Text>
|
|
220
|
-
|
|
221
|
-
{/* Group value and cursor in their own view to bypass the parent's generic gap constraint */}
|
|
222
|
-
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
223
|
-
<Text
|
|
224
|
-
style={[valueTextStyle, valueStyle]}
|
|
225
|
-
accessibilityElementsHidden={true}
|
|
226
|
-
importantForAccessibility="no"
|
|
227
|
-
>
|
|
228
|
-
{displayValue}
|
|
229
|
-
</Text>
|
|
230
|
-
{focused && (
|
|
231
|
-
<Animated.View
|
|
232
|
-
style={{
|
|
233
|
-
opacity: cursorOpacity,
|
|
234
|
-
width: 2,
|
|
235
|
-
height: (valueFontSize as number) * 1.1,
|
|
236
|
-
backgroundColor: textColor,
|
|
237
|
-
marginLeft: 2,
|
|
238
|
-
}}
|
|
239
|
-
/>
|
|
240
|
-
)}
|
|
241
|
-
</View>
|
|
242
|
-
</Pressable>
|
|
247
|
+
// Keyboard avoidance — lift the component when the keyboard would cover it.
|
|
248
|
+
// Follows the same pattern as ActionFooter (uses Keyboard.addListener).
|
|
249
|
+
// Resets when editing ends or the keyboard hides.
|
|
250
|
+
const rootRef = useRef<View>(null)
|
|
251
|
+
const keyboardLift = useRef(new Animated.Value(0)).current
|
|
252
|
+
const rootWindowYRef = useRef(0)
|
|
253
|
+
const rootMeasuredHeightRef = useRef(0)
|
|
254
|
+
|
|
255
|
+
const measureRoot = useCallback(() => {
|
|
256
|
+
rootRef.current?.measureInWindow((_x, y, _w, h) => {
|
|
257
|
+
rootWindowYRef.current = y
|
|
258
|
+
rootMeasuredHeightRef.current = h
|
|
259
|
+
})
|
|
260
|
+
}, [])
|
|
261
|
+
|
|
262
|
+
const handleRootLayout = useCallback(
|
|
263
|
+
(_e: LayoutChangeEvent) => {
|
|
264
|
+
// measureInWindow gives the component's position relative to the window,
|
|
265
|
+
// which is what we need for keyboard overlap calculation. onLayout fires
|
|
266
|
+
// after the component mounts and whenever its size changes.
|
|
267
|
+
measureRoot()
|
|
268
|
+
},
|
|
269
|
+
[measureRoot],
|
|
243
270
|
)
|
|
244
|
-
}
|
|
245
271
|
|
|
246
|
-
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (!editable) return
|
|
274
|
+
|
|
275
|
+
const showSub = Keyboard.addListener('keyboardDidShow', (e) => {
|
|
276
|
+
measureRoot()
|
|
277
|
+
if (!isEditing) return
|
|
247
278
|
|
|
279
|
+
const kbHeight = e.endCoordinates.height
|
|
280
|
+
const { height: screenH } = require('react-native').Dimensions.get('window')
|
|
281
|
+
const componentBottom = rootWindowYRef.current + rootMeasuredHeightRef.current
|
|
282
|
+
const visibleBottom = screenH - kbHeight
|
|
283
|
+
const overlap = componentBottom - visibleBottom + 12
|
|
248
284
|
|
|
285
|
+
if (overlap > 0) {
|
|
286
|
+
Animated.timing(keyboardLift, {
|
|
287
|
+
toValue: -overlap,
|
|
288
|
+
duration: e.duration ?? 250,
|
|
289
|
+
useNativeDriver: true,
|
|
290
|
+
}).start()
|
|
291
|
+
}
|
|
292
|
+
})
|
|
249
293
|
|
|
294
|
+
const hideSub = Keyboard.addListener('keyboardDidHide', () => {
|
|
295
|
+
Animated.timing(keyboardLift, {
|
|
296
|
+
toValue: 0,
|
|
297
|
+
duration: 150,
|
|
298
|
+
useNativeDriver: true,
|
|
299
|
+
}).start()
|
|
300
|
+
})
|
|
250
301
|
|
|
302
|
+
return () => {
|
|
303
|
+
showSub.remove()
|
|
304
|
+
hideSub.remove()
|
|
305
|
+
}
|
|
306
|
+
}, [editable, isEditing, keyboardLift, measureRoot])
|
|
307
|
+
|
|
308
|
+
// Reset lift when editing ends
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
if (!isEditing) {
|
|
311
|
+
keyboardLift.setValue(0)
|
|
312
|
+
}
|
|
313
|
+
}, [isEditing, keyboardLift])
|
|
251
314
|
|
|
315
|
+
const handleBlur = useCallback(() => {
|
|
316
|
+
commitEdit()
|
|
317
|
+
}, [commitEdit])
|
|
252
318
|
|
|
319
|
+
const handleSubmitEditing = useCallback(() => {
|
|
320
|
+
commitEdit()
|
|
321
|
+
}, [commitEdit])
|
|
253
322
|
|
|
323
|
+
return (
|
|
324
|
+
<Animated.View style={{ transform: [{ translateY: keyboardLift }] }}>
|
|
325
|
+
<View ref={rootRef} onLayout={handleRootLayout}>
|
|
326
|
+
<Pressable
|
|
327
|
+
style={[containerStyle, style]}
|
|
328
|
+
accessibilityRole="text"
|
|
329
|
+
accessibilityLabel={undefined}
|
|
330
|
+
accessibilityHint={accessibilityHint}
|
|
331
|
+
onPress={handlePress}
|
|
332
|
+
disabled={!onPress && !editable}
|
|
333
|
+
{...rest}
|
|
334
|
+
>
|
|
335
|
+
{isNegative && (
|
|
336
|
+
<Text
|
|
337
|
+
style={[currencyTextStyle, negativeSignStyle]}
|
|
338
|
+
accessibilityElementsHidden={true}
|
|
339
|
+
importantForAccessibility="no"
|
|
340
|
+
>
|
|
341
|
+
-
|
|
342
|
+
</Text>
|
|
343
|
+
)}
|
|
344
|
+
<Text
|
|
345
|
+
style={[currencyTextStyle, currencyStyle]}
|
|
346
|
+
accessibilityElementsHidden={true}
|
|
347
|
+
importantForAccessibility="no"
|
|
348
|
+
>
|
|
349
|
+
{resolvedCurrency}
|
|
350
|
+
</Text>
|
|
351
|
+
|
|
352
|
+
{/* Group value and cursor in their own view to bypass the parent's generic gap constraint */}
|
|
353
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
354
|
+
{isEditing ? (
|
|
355
|
+
<TextInput
|
|
356
|
+
ref={inputRef}
|
|
357
|
+
value={editBuffer}
|
|
358
|
+
onChangeText={handleChangeText}
|
|
359
|
+
onBlur={handleBlur}
|
|
360
|
+
onSubmitEditing={handleSubmitEditing}
|
|
361
|
+
keyboardType="decimal-pad"
|
|
362
|
+
returnKeyType="done"
|
|
363
|
+
selectTextOnFocus
|
|
364
|
+
style={[
|
|
365
|
+
valueTextStyle,
|
|
366
|
+
valueStyle,
|
|
367
|
+
{
|
|
368
|
+
padding: 0,
|
|
369
|
+
margin: 0,
|
|
370
|
+
// On web a thin border helps the cursor appear precisely
|
|
371
|
+
...(Platform.OS === 'web' ? { outlineStyle: 'none' as any } : {}),
|
|
372
|
+
},
|
|
373
|
+
]}
|
|
374
|
+
accessibilityLabel={accessibilityLabel || 'Edit value'}
|
|
375
|
+
/>
|
|
376
|
+
) : (
|
|
377
|
+
<>
|
|
378
|
+
<Text
|
|
379
|
+
style={[valueTextStyle, valueStyle]}
|
|
380
|
+
accessibilityElementsHidden={true}
|
|
381
|
+
importantForAccessibility="no"
|
|
382
|
+
>
|
|
383
|
+
{displayValue}
|
|
384
|
+
</Text>
|
|
385
|
+
{focused && (
|
|
386
|
+
<Animated.View
|
|
387
|
+
style={{
|
|
388
|
+
opacity: cursorOpacity,
|
|
389
|
+
width: 2,
|
|
390
|
+
height: (valueFontSize as number) * 1.1,
|
|
391
|
+
backgroundColor: textColor,
|
|
392
|
+
marginLeft: 2,
|
|
393
|
+
}}
|
|
394
|
+
/>
|
|
395
|
+
)}
|
|
396
|
+
</>
|
|
397
|
+
)}
|
|
398
|
+
</View>
|
|
399
|
+
</Pressable>
|
|
400
|
+
</View>
|
|
401
|
+
</Animated.View>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
254
404
|
|
|
405
|
+
export default MoneyValue
|
|
@@ -50,6 +50,30 @@ export interface PdpCcCardProps {
|
|
|
50
50
|
title?: string
|
|
51
51
|
/** Subtitle rendered below the title (14px medium). */
|
|
52
52
|
subtitle?: string
|
|
53
|
+
/**
|
|
54
|
+
* Number of lines to limit the title to. The `Title` component defaults to
|
|
55
|
+
* `1` when this is unset, so the headline is a single line with an ellipsis
|
|
56
|
+
* by default. Pass a larger value (e.g. `2`) to allow the title to wrap onto
|
|
57
|
+
* that many lines before truncating, or set {@link disableTruncation} to
|
|
58
|
+
* remove the clamp entirely. The subtitle is never line-clamped.
|
|
59
|
+
*/
|
|
60
|
+
numberOfLines?: number
|
|
61
|
+
/**
|
|
62
|
+
* When `true`, disables truncation for both the title and subtitle: the
|
|
63
|
+
* trailing ellipsis (`…`) is never shown and the `numberOfLines` clamp is
|
|
64
|
+
* ignored. The text is also never shrunk by a tight parent — it keeps its
|
|
65
|
+
* full intrinsic size and overflows (even breaking the parent layout) rather
|
|
66
|
+
* than ever clipping into an ellipsis. The text still wraps onto multiple
|
|
67
|
+
* lines.
|
|
68
|
+
*/
|
|
69
|
+
disableTruncation?: boolean
|
|
70
|
+
/**
|
|
71
|
+
* When `true`, forces both the title and subtitle onto a single line with no
|
|
72
|
+
* wrapping (implies {@link disableTruncation}). On web the line overflows
|
|
73
|
+
* horizontally — brutally breaking the parent layout if it has to — instead
|
|
74
|
+
* of wrapping or truncating.
|
|
75
|
+
*/
|
|
76
|
+
singleLine?: boolean
|
|
53
77
|
/**
|
|
54
78
|
* The metric columns rendered in the stats row. Vertical dividers are
|
|
55
79
|
* inserted automatically between adjacent metrics. Defaults to two sample
|
|
@@ -105,6 +129,9 @@ function PdpCcCard({
|
|
|
105
129
|
media,
|
|
106
130
|
title = 'Title',
|
|
107
131
|
subtitle = 'Subtitle',
|
|
132
|
+
numberOfLines,
|
|
133
|
+
disableTruncation,
|
|
134
|
+
singleLine,
|
|
108
135
|
metrics = DEFAULT_METRICS,
|
|
109
136
|
buttonLabel = 'button',
|
|
110
137
|
buttonIcon = 'ic_add_circle',
|
|
@@ -165,7 +192,15 @@ function PdpCcCard({
|
|
|
165
192
|
<>
|
|
166
193
|
<View style={styles.mediaSlot}>{mediaNode}</View>
|
|
167
194
|
|
|
168
|
-
<Title
|
|
195
|
+
<Title
|
|
196
|
+
title={title}
|
|
197
|
+
subtitle={subtitle}
|
|
198
|
+
textAlign="Center"
|
|
199
|
+
modes={modes}
|
|
200
|
+
numberOfLines={numberOfLines}
|
|
201
|
+
disableTruncation={disableTruncation}
|
|
202
|
+
singleLine={singleLine}
|
|
203
|
+
/>
|
|
169
204
|
|
|
170
205
|
{metrics.length > 0 ? (
|
|
171
206
|
<View style={styles.metricsRow}>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { createContext, useContext } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -13,6 +13,16 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
|
13
13
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
14
14
|
import type { Modes } from '../../design-tokens'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Carries the root `Table`'s `columnWidth` down to every cell so that, when it
|
|
18
|
+
* is set, all columns (header + body, data-driven or composed) share the same
|
|
19
|
+
* fixed width without each cell having to repeat the prop. Cells still honour
|
|
20
|
+
* an explicit `width` prop over this context value. `undefined` (the default,
|
|
21
|
+
* when no `Table` ancestor is present, or `columnWidth` is unset) leaves cells
|
|
22
|
+
* to flex — preserving the original behaviour.
|
|
23
|
+
*/
|
|
24
|
+
const TableContext = createContext<number | undefined>(undefined)
|
|
25
|
+
|
|
16
26
|
/* -------------------------------------------------------------------------------------------------
|
|
17
27
|
* Shared types & token resolution
|
|
18
28
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -135,6 +145,10 @@ function TableCell({
|
|
|
135
145
|
style,
|
|
136
146
|
}: TableCellProps) {
|
|
137
147
|
const t = resolveTableTokens(modes)
|
|
148
|
+
// A cell's width resolves in priority order: an explicit `width` prop, then
|
|
149
|
+
// the root Table's `columnWidth` (via context), then fall back to flex.
|
|
150
|
+
const ctxColumnWidth = useContext(TableContext)
|
|
151
|
+
const effectiveWidth = width ?? ctxColumnWidth
|
|
138
152
|
return (
|
|
139
153
|
<View
|
|
140
154
|
style={[
|
|
@@ -148,7 +162,7 @@ function TableCell({
|
|
|
148
162
|
borderColor: t.borderColor,
|
|
149
163
|
borderBottomWidth: isLastRow ? 0 : t.borderSize,
|
|
150
164
|
borderRightWidth: isLastColumn ? 0 : t.borderSize,
|
|
151
|
-
...(
|
|
165
|
+
...(effectiveWidth != null ? { width: effectiveWidth } : { flex, minWidth: 0 }),
|
|
152
166
|
},
|
|
153
167
|
style,
|
|
154
168
|
]}
|
|
@@ -188,6 +202,8 @@ function TableHeaderCell({
|
|
|
188
202
|
style,
|
|
189
203
|
}: TableHeaderCellProps) {
|
|
190
204
|
const t = resolveTableTokens(modes)
|
|
205
|
+
const ctxColumnWidth = useContext(TableContext)
|
|
206
|
+
const effectiveWidth = width ?? ctxColumnWidth
|
|
191
207
|
return (
|
|
192
208
|
<View
|
|
193
209
|
style={[
|
|
@@ -198,7 +214,7 @@ function TableHeaderCell({
|
|
|
198
214
|
justifyContent: alignToJustify(align),
|
|
199
215
|
paddingHorizontal: t.headerPaddingH,
|
|
200
216
|
paddingVertical: t.headerPaddingV,
|
|
201
|
-
...(
|
|
217
|
+
...(effectiveWidth != null ? { width: effectiveWidth } : { flex, minWidth: 0 }),
|
|
202
218
|
},
|
|
203
219
|
style,
|
|
204
220
|
]}
|
|
@@ -327,6 +343,14 @@ export type TableProps<T = Record<string, unknown>> = {
|
|
|
327
343
|
* for wide tables that exceed the viewport on mobile. @default false
|
|
328
344
|
*/
|
|
329
345
|
scrollable?: boolean
|
|
346
|
+
/**
|
|
347
|
+
* Fixed width (px) applied to every column — header and body alike — so
|
|
348
|
+
* all columns share the same width. An explicit per-column `width` (on
|
|
349
|
+
* `TableColumn`, or on an individual `Table.Cell`/`Table.HeaderCell`) still
|
|
350
|
+
* takes precedence when set. Omit to let columns flex to share the
|
|
351
|
+
* available space.
|
|
352
|
+
*/
|
|
353
|
+
columnWidth?: number
|
|
330
354
|
/** Accessibility label for the table container. */
|
|
331
355
|
accessibilityLabel?: string
|
|
332
356
|
/** Design token modes for theming (e.g. `{ "Color Mode": "Light" }`). */
|
|
@@ -360,6 +384,7 @@ function Table<T = Record<string, unknown>>({
|
|
|
360
384
|
getRowKey,
|
|
361
385
|
onRowPress,
|
|
362
386
|
scrollable = false,
|
|
387
|
+
columnWidth,
|
|
363
388
|
accessibilityLabel,
|
|
364
389
|
modes = EMPTY_MODES,
|
|
365
390
|
style,
|
|
@@ -424,7 +449,7 @@ function Table<T = Record<string, unknown>>({
|
|
|
424
449
|
accessibilityLabel={accessibilityLabel}
|
|
425
450
|
style={[{ overflow: 'hidden' }, !scrollable && { width: '100%' }, style]}
|
|
426
451
|
>
|
|
427
|
-
{body}
|
|
452
|
+
<TableContext.Provider value={columnWidth}>{body}</TableContext.Provider>
|
|
428
453
|
</View>
|
|
429
454
|
)
|
|
430
455
|
|
package/src/icons/registry.ts
CHANGED