jfs-components 0.1.2 → 0.1.8
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 +29 -0
- package/lib/commonjs/components/AmountInput/AmountInput.js +8 -5
- package/lib/commonjs/components/BenefitCard/BenefitCard.js +231 -0
- package/lib/commonjs/components/CcCard/CcCard.js +470 -0
- package/lib/commonjs/components/Checkbox/Checkbox.js +4 -3
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +4 -3
- package/lib/commonjs/components/CompareTable/CompareTable.js +372 -0
- package/lib/commonjs/components/ComparisonBar/ComparisonBar.js +266 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +35 -3
- package/lib/commonjs/components/FormField/FormField.js +4 -3
- package/lib/commonjs/components/InputSearch/InputSearch.js +6 -4
- package/lib/commonjs/components/NoteInput/NoteInput.js +6 -5
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +273 -0
- package/lib/commonjs/components/ProductMerchandisingCard/GlassFill.js +263 -0
- package/lib/commonjs/components/ProductMerchandisingCard/GlassFill.web.js +116 -0
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +353 -0
- package/lib/commonjs/components/ProjectionMarker/ProjectionMarker.js +161 -0
- package/lib/commonjs/components/Radio/Radio.js +5 -5
- package/lib/commonjs/components/Slider/Slider.js +473 -0
- package/lib/commonjs/components/TextInput/TextInput.js +13 -8
- package/lib/commonjs/components/TextSegment/TextSegment.js +118 -0
- package/lib/commonjs/components/index.js +63 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +38 -9
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/react-utils.js +22 -0
- package/lib/module/components/AmountInput/AmountInput.js +6 -4
- package/lib/module/components/BenefitCard/BenefitCard.js +225 -0
- package/lib/module/components/CcCard/CcCard.js +464 -0
- package/lib/module/components/Checkbox/Checkbox.js +5 -4
- package/lib/module/components/CheckboxItem/CheckboxItem.js +5 -4
- package/lib/module/components/CompareTable/CompareTable.js +367 -0
- package/lib/module/components/ComparisonBar/ComparisonBar.js +260 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +36 -4
- package/lib/module/components/FormField/FormField.js +5 -4
- package/lib/module/components/InputSearch/InputSearch.js +6 -4
- package/lib/module/components/NoteInput/NoteInput.js +7 -6
- package/lib/module/components/PdpCcCard/PdpCcCard.js +267 -0
- package/lib/module/components/ProductMerchandisingCard/GlassFill.js +257 -0
- package/lib/module/components/ProductMerchandisingCard/GlassFill.web.js +111 -0
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +347 -0
- package/lib/module/components/ProjectionMarker/ProjectionMarker.js +156 -0
- package/lib/module/components/Radio/Radio.js +5 -4
- package/lib/module/components/Slider/Slider.js +468 -0
- package/lib/module/components/TextInput/TextInput.js +15 -10
- package/lib/module/components/TextSegment/TextSegment.js +113 -0
- package/lib/module/components/index.js +9 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +38 -9
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/react-utils.js +21 -0
- package/lib/typescript/src/components/AmountInput/AmountInput.d.ts +3 -2
- package/lib/typescript/src/components/BenefitCard/BenefitCard.d.ts +93 -0
- package/lib/typescript/src/components/CcCard/CcCard.d.ts +137 -0
- package/lib/typescript/src/components/Checkbox/Checkbox.d.ts +3 -2
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +2 -2
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +88 -0
- package/lib/typescript/src/components/ComparisonBar/ComparisonBar.d.ts +118 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +20 -1
- package/lib/typescript/src/components/FormField/FormField.d.ts +2 -2
- package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +23 -2
- package/lib/typescript/src/components/NoteInput/NoteInput.d.ts +19 -2
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +84 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/GlassFill.d.ts +56 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/GlassFill.web.d.ts +27 -0
- package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +81 -0
- package/lib/typescript/src/components/ProjectionMarker/ProjectionMarker.d.ts +82 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +3 -2
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +2 -2
- package/lib/typescript/src/components/Slider/Slider.d.ts +99 -0
- package/lib/typescript/src/components/TextInput/TextInput.d.ts +9 -29
- package/lib/typescript/src/components/TextSegment/TextSegment.d.ts +100 -0
- package/lib/typescript/src/components/index.d.ts +10 -1
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +22 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/react-utils.d.ts +10 -0
- package/package.json +2 -1
- package/src/components/AmountInput/AmountInput.tsx +7 -5
- package/src/components/BenefitCard/BenefitCard.tsx +309 -0
- package/src/components/CcCard/CcCard.tsx +598 -0
- package/src/components/Checkbox/Checkbox.tsx +5 -4
- package/src/components/CheckboxItem/CheckboxItem.tsx +5 -4
- package/src/components/CompareTable/CompareTable.tsx +477 -0
- package/src/components/ComparisonBar/ComparisonBar.tsx +356 -0
- package/src/components/DropdownInput/DropdownInput.tsx +55 -3
- package/src/components/FormField/FormField.tsx +5 -4
- package/src/components/InputSearch/InputSearch.tsx +8 -5
- package/src/components/NoteInput/NoteInput.tsx +8 -6
- package/src/components/PdpCcCard/PdpCcCard.tsx +356 -0
- package/src/components/ProductMerchandisingCard/GlassFill.tsx +276 -0
- package/src/components/ProductMerchandisingCard/GlassFill.web.tsx +127 -0
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +423 -0
- package/src/components/ProjectionMarker/ProjectionMarker.tsx +277 -0
- package/src/components/Radio/Radio.tsx +5 -4
- package/src/components/Slider/Slider.tsx +628 -0
- package/src/components/TextInput/TextInput.tsx +15 -11
- package/src/components/TextSegment/TextSegment.tsx +166 -0
- package/src/components/index.ts +10 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +38 -9
- package/src/icons/registry.ts +1 -1
- package/src/utils/react-utils.ts +23 -0
- package/lib/typescript/scripts/extract-component-tokens.d.ts +0 -9
- package/lib/typescript/scripts/generate-component-docs.d.ts +0 -9
- package/lib/typescript/scripts/generate-icon-registry.d.ts +0 -3
- package/lib/typescript/scripts/generate-mode-types.d.ts +0 -2
- package/lib/typescript/scripts/retype-modes.d.cts +0 -2
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
Platform,
|
|
7
|
+
type ImageSourcePropType,
|
|
8
|
+
type StyleProp,
|
|
9
|
+
type ViewStyle,
|
|
10
|
+
type TextStyle,
|
|
11
|
+
} from 'react-native'
|
|
12
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
13
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
14
|
+
import Accordion from '../Accordion/Accordion'
|
|
15
|
+
import Image from '../Image/Image'
|
|
16
|
+
import IconButton from '../IconButton/IconButton'
|
|
17
|
+
import Icon from '../../icons/Icon'
|
|
18
|
+
import type { Modes } from '../../design-tokens'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Brand link colour used by the selection-card CTA. The Figma node resolves
|
|
22
|
+
* `button/foreground` to this value, but no reproducible token mode currently
|
|
23
|
+
* produces it (the published token store is out of sync), so it is pinned here
|
|
24
|
+
* to stay faithful to the design.
|
|
25
|
+
*/
|
|
26
|
+
const BRAND_LINK_COLOR = '#5d00b5'
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Selection-card close control (Figma node 5568:3492). It is an `IconButton`
|
|
30
|
+
* pinned to the card's top-right corner. The Figma node resolves its
|
|
31
|
+
* `iconButton/background` and border to fully transparent (`#ebebed00` /
|
|
32
|
+
* `#ffffff00`) — values no token mode reproduces — so they are overridden here.
|
|
33
|
+
* Size (40px) and icon are left to the `IconButton`'s own token resolution
|
|
34
|
+
* (padding 11 · 2 + icon 18).
|
|
35
|
+
*/
|
|
36
|
+
/** Add-a-card icon button fill + icon (Figma node 5568:3494 direct overrides). */
|
|
37
|
+
const ADD_BUTTON_BG = '#545961'
|
|
38
|
+
const ADD_BUTTON_ICON_COLOR = '#ffffff'
|
|
39
|
+
|
|
40
|
+
const closeButtonStyle: ViewStyle = {
|
|
41
|
+
position: 'absolute',
|
|
42
|
+
top: 1,
|
|
43
|
+
right: 0,
|
|
44
|
+
backgroundColor: 'transparent',
|
|
45
|
+
borderColor: 'transparent',
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Keeps every text layer on a single line on web. */
|
|
49
|
+
const NO_WRAP_TEXT: TextStyle = {
|
|
50
|
+
flexShrink: 0,
|
|
51
|
+
...(Platform.OS === 'web' ? { whiteSpace: 'nowrap' as const } : {}),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const toWeight = (w: number | string) =>
|
|
55
|
+
(typeof w === 'number' ? `${w}` : w) as TextStyle['fontWeight']
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A single product/plan column rendered as a selection card at the top of the
|
|
59
|
+
* table. The card order maps 1:1 to each {@link CompareTableRow}'s `values`.
|
|
60
|
+
*/
|
|
61
|
+
export type CompareTableColumn = {
|
|
62
|
+
/** Product / plan name shown under the image. */
|
|
63
|
+
label?: string;
|
|
64
|
+
/** Thumbnail / logo image. Accepts a URL string or an RN image source. */
|
|
65
|
+
imageSource?: ImageSourcePropType | string;
|
|
66
|
+
/** CTA link text (e.g. "View details"). Omit to hide the link. */
|
|
67
|
+
actionLabel?: string;
|
|
68
|
+
/** Handler for the CTA link. Implies the link is shown. */
|
|
69
|
+
onActionPress?: () => void;
|
|
70
|
+
/** When provided, renders a tappable close icon to remove the column. */
|
|
71
|
+
onRemove?: () => void;
|
|
72
|
+
/** Stable key. Falls back to the label / index. */
|
|
73
|
+
key?: React.Key;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Value rendered inside a comparison cell.
|
|
78
|
+
* - `string` / `number` → rendered as value text.
|
|
79
|
+
* - `false` → renders the muted "not available" cross icon.
|
|
80
|
+
* - any React element → rendered as-is (e.g. a `Badge`, `MoneyValue`, icon…).
|
|
81
|
+
* - `null` / `undefined` / `true` → empty cell.
|
|
82
|
+
*/
|
|
83
|
+
export type CompareTableCellValue =
|
|
84
|
+
| string
|
|
85
|
+
| number
|
|
86
|
+
| boolean
|
|
87
|
+
| null
|
|
88
|
+
| undefined
|
|
89
|
+
| React.ReactElement;
|
|
90
|
+
|
|
91
|
+
export type CompareTableRow = {
|
|
92
|
+
/** One value per column, in the same order as `columns`. */
|
|
93
|
+
values: CompareTableCellValue[];
|
|
94
|
+
/** Stable key. Falls back to the index. */
|
|
95
|
+
key?: React.Key;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A collapsible section. Each section renders as an `Accordion` whose body is a
|
|
100
|
+
* comparison table that spans the same columns as the selection cards.
|
|
101
|
+
*/
|
|
102
|
+
export type CompareTableSection = {
|
|
103
|
+
/** Accordion header label, e.g. "Fees & charges". */
|
|
104
|
+
title: string;
|
|
105
|
+
/** Optional full-width header row shown at the top of the table body. */
|
|
106
|
+
header?: string;
|
|
107
|
+
/** Comparison rows; each row's `values` map 1:1 to `columns`. */
|
|
108
|
+
rows: CompareTableRow[];
|
|
109
|
+
/** Initial expanded state. Defaults to `true` for the first section. */
|
|
110
|
+
defaultExpanded?: boolean;
|
|
111
|
+
/** Stable key. Falls back to the title / index. */
|
|
112
|
+
key?: React.Key;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type CompareTableProps = {
|
|
116
|
+
/**
|
|
117
|
+
* Product / plan columns rendered as selection cards. Their order maps 1:1
|
|
118
|
+
* to each row's `values` array. Supports any number of columns.
|
|
119
|
+
*/
|
|
120
|
+
columns?: CompareTableColumn[];
|
|
121
|
+
/** Collapsible comparison sections. */
|
|
122
|
+
sections?: CompareTableSection[];
|
|
123
|
+
/**
|
|
124
|
+
* When provided, renders an "Add a card" tile after the columns. Hidden once
|
|
125
|
+
* `columns.length` reaches `maxColumns`.
|
|
126
|
+
*/
|
|
127
|
+
onAddColumn?: () => void;
|
|
128
|
+
/** Label for the add-a-card tile. @default 'Add a card' */
|
|
129
|
+
addColumnLabel?: string;
|
|
130
|
+
/** Maximum number of columns before the add tile is hidden. @default 4 */
|
|
131
|
+
maxColumns?: number;
|
|
132
|
+
/** Design token modes for theming (e.g. `{ "Color Mode": "Light" }`). */
|
|
133
|
+
modes?: Modes;
|
|
134
|
+
/** Override the outer container style. */
|
|
135
|
+
style?: StyleProp<ViewStyle>;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const DEFAULT_COLUMNS: CompareTableColumn[] = [
|
|
139
|
+
{ label: 'Plan A', actionLabel: 'View details', onRemove: () => {} },
|
|
140
|
+
{ label: 'Plan B', actionLabel: 'View details', onRemove: () => {} },
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
const DEFAULT_SECTIONS: CompareTableSection[] = [
|
|
144
|
+
{
|
|
145
|
+
title: 'Fees & charges',
|
|
146
|
+
header: 'Annual fees',
|
|
147
|
+
defaultExpanded: true,
|
|
148
|
+
rows: [
|
|
149
|
+
{ values: ['₹0', '₹499'] },
|
|
150
|
+
{ values: ['Free', 'Up to ₹1,300'] },
|
|
151
|
+
{ values: ['1x', '1.25x'] },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* CompareTable renders a product comparison surface: a row of selection cards
|
|
158
|
+
* (one per column) followed by one or more collapsible sections, each holding a
|
|
159
|
+
* comparison table whose cells line up beneath their column.
|
|
160
|
+
*
|
|
161
|
+
* The selection card is an internal sub-component and is intentionally not
|
|
162
|
+
* exported — it should only be used through `CompareTable`. Implementation of
|
|
163
|
+
* Figma node `5568:3496` (`Compare table`) with sub-component `5568:3487`
|
|
164
|
+
* (`selectioncard`).
|
|
165
|
+
*
|
|
166
|
+
* @component
|
|
167
|
+
*/
|
|
168
|
+
function CompareTable({
|
|
169
|
+
columns = DEFAULT_COLUMNS,
|
|
170
|
+
sections = DEFAULT_SECTIONS,
|
|
171
|
+
onAddColumn,
|
|
172
|
+
addColumnLabel = 'Add a card',
|
|
173
|
+
maxColumns = 4,
|
|
174
|
+
modes = EMPTY_MODES,
|
|
175
|
+
style,
|
|
176
|
+
}: CompareTableProps) {
|
|
177
|
+
// --- selection card tokens ------------------------------------------------
|
|
178
|
+
const cardBg = (getVariableByName('selectionCard/background/color', modes) as string) ?? '#ffffff'
|
|
179
|
+
const cardBorderColor = (getVariableByName('selectionCard/border/color', modes) as string) ?? '#eeeeee'
|
|
180
|
+
const cardBorderSize = (getVariableByName('selectionCard/border/size', modes) as number) ?? 1
|
|
181
|
+
const cardGap = (getVariableByName('selectionCard/gap', modes) as number) ?? 12
|
|
182
|
+
const cardPaddingH = (getVariableByName('selectionCard/padding/horizontal', modes) as number) ?? 20
|
|
183
|
+
const cardPaddingV = (getVariableByName('selectionCard/padding/vertical', modes) as number) ?? 20
|
|
184
|
+
const cardLabelColor = (getVariableByName('selectionCard/label/color', modes) as string) ?? '#000000'
|
|
185
|
+
const cardLabelFontSize = (getVariableByName('selectionCard/label/fontsize', modes) as number) ?? 14
|
|
186
|
+
const cardLabelFontFamily = (getVariableByName('selectionCard/label/fontfamily', modes) as string) ?? 'JioType Var'
|
|
187
|
+
const cardLabelFontWeight = (getVariableByName('selectionCard/label/fontweight', modes) as number | string) ?? 700
|
|
188
|
+
const imageRadius = (getVariableByName('image/radius', modes) as number) ?? 8
|
|
189
|
+
|
|
190
|
+
// --- add-a-card icon button tokens ---------------------------------------
|
|
191
|
+
// padding/size/border come from the shared iconButton tokens; the filled
|
|
192
|
+
// background (#545961) and white icon are the Figma node's direct overrides
|
|
193
|
+
// (no token mode reproduces them), so they are pinned to match the design.
|
|
194
|
+
const addBorderColor = (getVariableByName('iconButton/border/color', modes) as string) ?? 'rgba(255,255,255,0)'
|
|
195
|
+
const addBorderSize = (getVariableByName('iconButton/border/size', modes) as number) ?? 1
|
|
196
|
+
const addPadding = (getVariableByName('iconButton/padding', modes) as number) ?? 11
|
|
197
|
+
const addIconSize = (getVariableByName('iconButton/icon/size', modes) as number) ?? 18
|
|
198
|
+
const addBg = ADD_BUTTON_BG
|
|
199
|
+
const addIconColor = ADD_BUTTON_ICON_COLOR
|
|
200
|
+
|
|
201
|
+
// --- table header tokens --------------------------------------------------
|
|
202
|
+
const headerBg = (getVariableByName('comparetableHeader/background/color', modes) as string) ?? '#f5f5f5'
|
|
203
|
+
const headerLabelColor = (getVariableByName('comparetableHeader/label/color', modes) as string) ?? '#000000'
|
|
204
|
+
const headerFontSize = (getVariableByName('comparetableHeader/label/fontsize', modes) as number) ?? 14
|
|
205
|
+
const headerFontFamily = (getVariableByName('comparetableHeader/label/fontfamily', modes) as string) ?? 'JioType Var'
|
|
206
|
+
const headerFontWeight = (getVariableByName('comparetableHeader/label/fontweight', modes) as number | string) ?? 600
|
|
207
|
+
const headerPaddingH = (getVariableByName('comparetableHeader/padding/horizontal', modes) as number) ?? 12
|
|
208
|
+
const headerPaddingV = (getVariableByName('comparetableHeader/padding/vertical', modes) as number) ?? 12
|
|
209
|
+
|
|
210
|
+
// --- table cell tokens ----------------------------------------------------
|
|
211
|
+
const cellBorderColor = (getVariableByName('compareTableCell/border/color', modes) as string) ?? '#eeeeee'
|
|
212
|
+
const cellBorderSize = (getVariableByName('compareTableCell/border/size', modes) as number) ?? 1
|
|
213
|
+
const cellBg = (getVariableByName('compareTableCell/background/color', modes) as string) ?? '#ffffff'
|
|
214
|
+
const cellLabelColor = (getVariableByName('compareTableCell/label/color', modes) as string) ?? '#000000'
|
|
215
|
+
const cellFontSize = (getVariableByName('compareTableCell/label/fontsize', modes) as number) ?? 14
|
|
216
|
+
const cellFontFamily = (getVariableByName('compareTableCell/label/fontfamily', modes) as string) ?? 'JioType Var'
|
|
217
|
+
const cellFontWeight = (getVariableByName('compareTableCell/label/fontweight', modes) as number | string) ?? 400
|
|
218
|
+
const cellPaddingH = (getVariableByName('compareTableCell/padding/horizontal', modes) as number) ?? 12
|
|
219
|
+
const cellPaddingV = (getVariableByName('compareTableCell/padding/vertical', modes) as number) ?? 12
|
|
220
|
+
|
|
221
|
+
const columnCount = columns.length
|
|
222
|
+
const showAddCard = onAddColumn != null && columnCount < maxColumns
|
|
223
|
+
|
|
224
|
+
const cardLabelStyle: TextStyle = {
|
|
225
|
+
...NO_WRAP_TEXT,
|
|
226
|
+
color: cardLabelColor,
|
|
227
|
+
fontSize: cardLabelFontSize,
|
|
228
|
+
fontFamily: cardLabelFontFamily,
|
|
229
|
+
fontWeight: toWeight(cardLabelFontWeight),
|
|
230
|
+
lineHeight: cardLabelFontSize * 1.1,
|
|
231
|
+
textAlign: 'center',
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const linkStyle: TextStyle = {
|
|
235
|
+
...NO_WRAP_TEXT,
|
|
236
|
+
color: BRAND_LINK_COLOR,
|
|
237
|
+
fontFamily: cardLabelFontFamily,
|
|
238
|
+
fontSize: 14,
|
|
239
|
+
fontWeight: '700',
|
|
240
|
+
lineHeight: 22,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const headerTextStyle: TextStyle = {
|
|
244
|
+
color: headerLabelColor,
|
|
245
|
+
fontSize: headerFontSize,
|
|
246
|
+
fontFamily: headerFontFamily,
|
|
247
|
+
fontWeight: toWeight(headerFontWeight),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const cellTextStyle: TextStyle = {
|
|
251
|
+
color: cellLabelColor,
|
|
252
|
+
fontSize: cellFontSize,
|
|
253
|
+
fontFamily: cellFontFamily,
|
|
254
|
+
fontWeight: toWeight(cellFontWeight),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const cardStyle: ViewStyle = {
|
|
258
|
+
flex: 1,
|
|
259
|
+
minWidth: 0,
|
|
260
|
+
minHeight: 147,
|
|
261
|
+
backgroundColor: cardBg,
|
|
262
|
+
alignItems: 'center',
|
|
263
|
+
justifyContent: 'center',
|
|
264
|
+
gap: cardGap,
|
|
265
|
+
paddingHorizontal: cardPaddingH,
|
|
266
|
+
paddingVertical: cardPaddingV,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const renderCard = (column: CompareTableColumn, index: number) => {
|
|
270
|
+
const isLast = index === columnCount - 1 && !showAddCard
|
|
271
|
+
return (
|
|
272
|
+
<View
|
|
273
|
+
key={column.key ?? column.label ?? index}
|
|
274
|
+
style={[
|
|
275
|
+
cardStyle,
|
|
276
|
+
{
|
|
277
|
+
borderBottomWidth: cardBorderSize,
|
|
278
|
+
borderBottomColor: cardBorderColor,
|
|
279
|
+
borderRightWidth: isLast ? 0 : cardBorderSize,
|
|
280
|
+
borderRightColor: cardBorderColor,
|
|
281
|
+
},
|
|
282
|
+
]}
|
|
283
|
+
>
|
|
284
|
+
{column.imageSource != null && (
|
|
285
|
+
<Image
|
|
286
|
+
imageSource={column.imageSource}
|
|
287
|
+
width={66}
|
|
288
|
+
height={44}
|
|
289
|
+
borderRadius={imageRadius}
|
|
290
|
+
resizeMode="cover"
|
|
291
|
+
accessibilityLabel={column.label}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
{column.label != null && (
|
|
295
|
+
<Text style={cardLabelStyle} numberOfLines={2}>
|
|
296
|
+
{column.label}
|
|
297
|
+
</Text>
|
|
298
|
+
)}
|
|
299
|
+
{column.actionLabel != null && (
|
|
300
|
+
<Pressable
|
|
301
|
+
onPress={column.onActionPress}
|
|
302
|
+
accessibilityRole="button"
|
|
303
|
+
accessibilityLabel={column.actionLabel}
|
|
304
|
+
hitSlop={6}
|
|
305
|
+
>
|
|
306
|
+
<Text style={linkStyle}>{column.actionLabel}</Text>
|
|
307
|
+
</Pressable>
|
|
308
|
+
)}
|
|
309
|
+
{column.onRemove != null && (
|
|
310
|
+
// Figma node 5568:3492 — an Icon Button (not a capsule):
|
|
311
|
+
// padding 11 + icon 18 → 40px circle, transparent background,
|
|
312
|
+
// absolutely pinned to the card's top-right (right:0, top:1).
|
|
313
|
+
<IconButton
|
|
314
|
+
iconName="ic_close"
|
|
315
|
+
onPress={column.onRemove}
|
|
316
|
+
modes={modes}
|
|
317
|
+
accessibilityLabel={`Remove ${column.label ?? 'column'}`}
|
|
318
|
+
style={closeButtonStyle}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
321
|
+
</View>
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const renderAddCard = () => (
|
|
326
|
+
<Pressable
|
|
327
|
+
key="__add_card__"
|
|
328
|
+
onPress={onAddColumn}
|
|
329
|
+
accessibilityRole="button"
|
|
330
|
+
accessibilityLabel={addColumnLabel}
|
|
331
|
+
style={[cardStyle, { borderBottomWidth: cardBorderSize, borderBottomColor: cardBorderColor }]}
|
|
332
|
+
>
|
|
333
|
+
<View
|
|
334
|
+
style={{
|
|
335
|
+
width: addPadding * 2 + addIconSize,
|
|
336
|
+
height: addPadding * 2 + addIconSize,
|
|
337
|
+
borderRadius: 9999,
|
|
338
|
+
borderWidth: addBorderSize,
|
|
339
|
+
borderColor: addBorderColor,
|
|
340
|
+
backgroundColor: addBg,
|
|
341
|
+
alignItems: 'center',
|
|
342
|
+
justifyContent: 'center',
|
|
343
|
+
}}
|
|
344
|
+
>
|
|
345
|
+
<Icon
|
|
346
|
+
name="ic_add"
|
|
347
|
+
size={addIconSize}
|
|
348
|
+
color={addIconColor}
|
|
349
|
+
accessibilityElementsHidden
|
|
350
|
+
importantForAccessibility="no"
|
|
351
|
+
/>
|
|
352
|
+
</View>
|
|
353
|
+
<Text style={cardLabelStyle} numberOfLines={1}>
|
|
354
|
+
{addColumnLabel}
|
|
355
|
+
</Text>
|
|
356
|
+
</Pressable>
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
const renderCellContent = (value: CompareTableCellValue, cellKey: React.Key) => {
|
|
360
|
+
if (value === false) {
|
|
361
|
+
return (
|
|
362
|
+
<Icon
|
|
363
|
+
key={cellKey}
|
|
364
|
+
name="ic_close"
|
|
365
|
+
size={cellFontSize + 2}
|
|
366
|
+
color={cellBorderColor}
|
|
367
|
+
/>
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
if (value === null || value === undefined || value === true) {
|
|
371
|
+
return null
|
|
372
|
+
}
|
|
373
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
374
|
+
return (
|
|
375
|
+
<Text key={cellKey} style={cellTextStyle}>
|
|
376
|
+
{value}
|
|
377
|
+
</Text>
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
return cloneChildrenWithModes(value, modes)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const renderTable = (section: CompareTableSection) => (
|
|
384
|
+
<View
|
|
385
|
+
style={{
|
|
386
|
+
width: '100%',
|
|
387
|
+
borderWidth: cellBorderSize,
|
|
388
|
+
borderColor: cellBorderColor,
|
|
389
|
+
overflow: 'hidden',
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{section.header != null && (
|
|
393
|
+
<View
|
|
394
|
+
style={{
|
|
395
|
+
backgroundColor: headerBg,
|
|
396
|
+
paddingHorizontal: headerPaddingH,
|
|
397
|
+
paddingVertical: headerPaddingV,
|
|
398
|
+
borderBottomWidth: cellBorderSize,
|
|
399
|
+
borderBottomColor: cellBorderColor,
|
|
400
|
+
}}
|
|
401
|
+
>
|
|
402
|
+
<Text style={headerTextStyle}>{section.header}</Text>
|
|
403
|
+
</View>
|
|
404
|
+
)}
|
|
405
|
+
{section.rows.map((row, rowIndex) => {
|
|
406
|
+
const isLastRow = rowIndex === section.rows.length - 1
|
|
407
|
+
return (
|
|
408
|
+
<View
|
|
409
|
+
key={row.key ?? rowIndex}
|
|
410
|
+
style={{ flexDirection: 'row', width: '100%' }}
|
|
411
|
+
>
|
|
412
|
+
{Array.from({ length: columnCount }).map((_, colIndex) => {
|
|
413
|
+
const isLastCol = colIndex === columnCount - 1
|
|
414
|
+
return (
|
|
415
|
+
<View
|
|
416
|
+
key={colIndex}
|
|
417
|
+
style={{
|
|
418
|
+
flex: 1,
|
|
419
|
+
minWidth: 0,
|
|
420
|
+
backgroundColor: cellBg,
|
|
421
|
+
justifyContent: 'center',
|
|
422
|
+
paddingHorizontal: cellPaddingH,
|
|
423
|
+
paddingVertical: cellPaddingV,
|
|
424
|
+
borderRightWidth: isLastCol ? 0 : cellBorderSize,
|
|
425
|
+
borderRightColor: cellBorderColor,
|
|
426
|
+
borderBottomWidth: isLastRow ? 0 : cellBorderSize,
|
|
427
|
+
borderBottomColor: cellBorderColor,
|
|
428
|
+
}}
|
|
429
|
+
>
|
|
430
|
+
{renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`)}
|
|
431
|
+
</View>
|
|
432
|
+
)
|
|
433
|
+
})}
|
|
434
|
+
</View>
|
|
435
|
+
)
|
|
436
|
+
})}
|
|
437
|
+
</View>
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
<View
|
|
442
|
+
style={[
|
|
443
|
+
{
|
|
444
|
+
width: '100%',
|
|
445
|
+
backgroundColor: cardBg,
|
|
446
|
+
borderWidth: 1,
|
|
447
|
+
borderColor: '#e8e8e8',
|
|
448
|
+
overflow: 'hidden',
|
|
449
|
+
},
|
|
450
|
+
style,
|
|
451
|
+
]}
|
|
452
|
+
>
|
|
453
|
+
{(columnCount > 0 || showAddCard) && (
|
|
454
|
+
<View style={{ flexDirection: 'row', width: '100%' }}>
|
|
455
|
+
{columns.map(renderCard)}
|
|
456
|
+
{showAddCard && renderAddCard()}
|
|
457
|
+
</View>
|
|
458
|
+
)}
|
|
459
|
+
|
|
460
|
+
<View style={{ width: '100%' }}>
|
|
461
|
+
{sections.map((section, index) => (
|
|
462
|
+
<Accordion
|
|
463
|
+
key={section.key ?? section.title ?? index}
|
|
464
|
+
title={section.title}
|
|
465
|
+
defaultExpanded={section.defaultExpanded ?? index === 0}
|
|
466
|
+
modes={modes}
|
|
467
|
+
style={{ paddingHorizontal: headerPaddingH }}
|
|
468
|
+
>
|
|
469
|
+
{renderTable(section)}
|
|
470
|
+
</Accordion>
|
|
471
|
+
))}
|
|
472
|
+
</View>
|
|
473
|
+
</View>
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export default CompareTable
|