jfs-components 0.0.79 → 0.0.84
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/lib/commonjs/components/AppBar/AppBar.js +56 -6
- package/lib/commonjs/components/Attached/Attached.js +46 -7
- package/lib/commonjs/components/Checkbox/Checkbox.js +18 -2
- package/lib/commonjs/components/Drawer/Drawer.js +6 -1
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/commonjs/components/FormField/FormField.js +1 -14
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +5 -1
- package/lib/commonjs/components/ListItem/ListItem.js +6 -11
- package/lib/commonjs/components/MessageField/MessageField.js +1 -13
- package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +12 -9
- package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +69 -160
- package/lib/commonjs/components/Spinner/Spinner.js +217 -0
- package/lib/commonjs/components/TextInput/TextInput.js +33 -18
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/icons/components/IconArrowdown.js +19 -0
- package/lib/commonjs/icons/components/IconArrowup.js +19 -0
- package/lib/commonjs/icons/components/IconChevrondowncircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronleftcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronrightcircle.js +19 -0
- package/lib/commonjs/icons/components/IconChevronupcircle.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavback.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavcenter.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavhome.js +19 -0
- package/lib/commonjs/icons/components/IconOsnavtask.js +19 -0
- package/lib/commonjs/icons/components/IconSignin.js +19 -0
- package/lib/commonjs/icons/components/IconSignout.js +19 -0
- package/lib/commonjs/icons/components/index.js +132 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AppBar/AppBar.js +56 -6
- package/lib/module/components/Attached/Attached.js +46 -7
- package/lib/module/components/Checkbox/Checkbox.js +18 -2
- package/lib/module/components/Drawer/Drawer.js +6 -1
- package/lib/module/components/DropdownInput/DropdownInput.js +30 -6
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +17 -11
- package/lib/module/components/FormField/FormField.js +3 -16
- package/lib/module/components/FullscreenModal/FullscreenModal.js +5 -1
- package/lib/module/components/ListItem/ListItem.js +6 -11
- package/lib/module/components/MessageField/MessageField.js +3 -15
- package/lib/module/components/PaymentFeedback/PaymentFeedback.js +13 -9
- package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +72 -160
- package/lib/module/components/Spinner/Spinner.js +212 -0
- package/lib/module/components/TextInput/TextInput.js +34 -19
- package/lib/module/components/index.js +1 -0
- package/lib/module/icons/components/IconArrowdown.js +12 -0
- package/lib/module/icons/components/IconArrowup.js +12 -0
- package/lib/module/icons/components/IconChevrondowncircle.js +12 -0
- package/lib/module/icons/components/IconChevronleftcircle.js +12 -0
- package/lib/module/icons/components/IconChevronrightcircle.js +12 -0
- package/lib/module/icons/components/IconChevronupcircle.js +12 -0
- package/lib/module/icons/components/IconOsnavback.js +12 -0
- package/lib/module/icons/components/IconOsnavcenter.js +12 -0
- package/lib/module/icons/components/IconOsnavhome.js +12 -0
- package/lib/module/icons/components/IconOsnavtask.js +12 -0
- package/lib/module/icons/components/IconSignin.js +12 -0
- package/lib/module/icons/components/IconSignout.js +12 -0
- package/lib/module/icons/components/index.js +12 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AppBar/AppBar.d.ts +12 -1
- package/lib/typescript/src/components/Attached/Attached.d.ts +19 -16
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +3 -2
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +3 -3
- package/lib/typescript/src/components/PaymentFeedback/PaymentFeedback.d.ts +5 -1
- package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +10 -8
- package/lib/typescript/src/components/Spinner/Spinner.d.ts +45 -0
- package/lib/typescript/src/components/index.d.ts +1 -0
- package/lib/typescript/src/icons/components/IconArrowdown.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconArrowup.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevrondowncircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronleftcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronrightcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconChevronupcircle.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavback.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavcenter.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavhome.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconOsnavtask.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignin.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconSignout.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +12 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/AppBar/AppBar.tsx +79 -12
- package/src/components/Attached/Attached.tsx +63 -7
- package/src/components/Checkbox/Checkbox.tsx +14 -2
- package/src/components/Drawer/Drawer.tsx +4 -0
- package/src/components/DropdownInput/DropdownInput.tsx +54 -20
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +13 -9
- package/src/components/FormField/FormField.tsx +3 -19
- package/src/components/FullscreenModal/FullscreenModal.tsx +3 -0
- package/src/components/ListItem/ListItem.tsx +14 -16
- package/src/components/MessageField/MessageField.tsx +3 -18
- package/src/components/PaymentFeedback/PaymentFeedback.tsx +15 -8
- package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +82 -192
- package/src/components/Spinner/Spinner.tsx +273 -0
- package/src/components/TextInput/TextInput.tsx +37 -19
- package/src/components/index.ts +1 -0
- package/src/icons/components/IconArrowdown.tsx +11 -0
- package/src/icons/components/IconArrowup.tsx +11 -0
- package/src/icons/components/IconChevrondowncircle.tsx +11 -0
- package/src/icons/components/IconChevronleftcircle.tsx +11 -0
- package/src/icons/components/IconChevronrightcircle.tsx +11 -0
- package/src/icons/components/IconChevronupcircle.tsx +11 -0
- package/src/icons/components/IconOsnavback.tsx +11 -0
- package/src/icons/components/IconOsnavcenter.tsx +11 -0
- package/src/icons/components/IconOsnavhome.tsx +11 -0
- package/src/icons/components/IconOsnavtask.tsx +11 -0
- package/src/icons/components/IconSignin.tsx +11 -0
- package/src/icons/components/IconSignout.tsx +11 -0
- package/src/icons/components/index.ts +12 -0
- package/src/icons/registry.ts +49 -1
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import React
|
|
3
|
+
import React from 'react';
|
|
4
4
|
import { View, Text, Pressable, Platform } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
7
|
import Icon from '../../icons/Icon';
|
|
8
8
|
|
|
9
|
+
/** Figma grid: label column 1.8fr, each plan column 1fr. */
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
const LABEL_COLUMN_FR = 1.8;
|
|
12
|
+
const PLAN_COLUMN_FR = 1;
|
|
13
|
+
|
|
9
14
|
/**
|
|
10
15
|
* A single plan column header (the label column has no header of its own).
|
|
11
16
|
*/
|
|
@@ -17,7 +22,7 @@ import Icon from '../../icons/Icon';
|
|
|
17
22
|
* - any React node → rendered as-is (e.g. a `Badge`, `MoneyValue`, icon…).
|
|
18
23
|
* - `null` / `undefined` / `true` → empty cell.
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
|
|
21
26
|
const DEFAULT_COLUMNS = [{
|
|
22
27
|
label: 'Your plan'
|
|
23
28
|
}, {
|
|
@@ -37,121 +42,65 @@ const DEFAULT_ROWS = [{
|
|
|
37
42
|
values: [false, '1%']
|
|
38
43
|
}];
|
|
39
44
|
|
|
45
|
+
/** Keeps every text layer on a single line. */
|
|
46
|
+
const NO_WRAP_TEXT = {
|
|
47
|
+
flexShrink: 0,
|
|
48
|
+
...(Platform.OS === 'web' ? {
|
|
49
|
+
whiteSpace: 'nowrap'
|
|
50
|
+
} : {})
|
|
51
|
+
};
|
|
52
|
+
const labelColumnStyle = {
|
|
53
|
+
flex: LABEL_COLUMN_FR,
|
|
54
|
+
minWidth: 0
|
|
55
|
+
};
|
|
56
|
+
const planColumnStyle = {
|
|
57
|
+
flex: PLAN_COLUMN_FR,
|
|
58
|
+
minWidth: 0,
|
|
59
|
+
alignItems: 'center'
|
|
60
|
+
};
|
|
61
|
+
|
|
40
62
|
/**
|
|
41
63
|
* PlanComparisonCard renders a compact comparison table that pits the user's
|
|
42
64
|
* current plan against one or more alternative plans across a set of feature
|
|
43
65
|
* rows. Implementation of Figma node `4498:2968` (`PlanComparisonCard`).
|
|
44
66
|
*
|
|
45
|
-
*
|
|
46
|
-
* other column maps to a plan in `columns`. Each cell value can be plain text,
|
|
47
|
-
* a "not available" cross (`false`), or any custom React node.
|
|
67
|
+
* Columns use a 1.8fr / 1fr flex ratio (label vs plan), matching the Figma grid.
|
|
48
68
|
*
|
|
49
69
|
* @component
|
|
50
|
-
* @example
|
|
51
|
-
* ```tsx
|
|
52
|
-
* <PlanComparisonCard
|
|
53
|
-
* columns={[{ label: 'Your plan' }, { label: 'JioFinance+', brand: true }]}
|
|
54
|
-
* rows={[
|
|
55
|
-
* { label: 'JioPoints multiplier', values: ['1x', '1.25x'] },
|
|
56
|
-
* { label: 'Cashback', showInfo: true, values: [false, 'Upto ₹5000'] },
|
|
57
|
-
* ]}
|
|
58
|
-
* />
|
|
59
|
-
* ```
|
|
60
70
|
*/
|
|
61
|
-
/** Keeps every text layer on a single line; columns grow to fit content. */
|
|
62
|
-
const NO_WRAP_TEXT = {
|
|
63
|
-
flexShrink: 0,
|
|
64
|
-
...(Platform.OS === 'web' ? {
|
|
65
|
-
whiteSpace: 'nowrap'
|
|
66
|
-
} : {})
|
|
67
|
-
};
|
|
68
71
|
function PlanComparisonCard({
|
|
69
72
|
columns = DEFAULT_COLUMNS,
|
|
70
73
|
rows = DEFAULT_ROWS,
|
|
71
|
-
labelColumnFlex = 0,
|
|
72
74
|
modes = EMPTY_MODES,
|
|
73
75
|
style
|
|
74
76
|
}) {
|
|
75
|
-
/** Natural widths from header labels (plan columns only). */
|
|
76
|
-
const [headerWidths, setHeaderWidths] = useState([]);
|
|
77
|
-
/** Natural widths from table body columns. */
|
|
78
|
-
const [bodyWidths, setBodyWidths] = useState([]);
|
|
79
|
-
const setMeasuredWidth = useCallback((setter, index, width) => {
|
|
80
|
-
setter(prev => {
|
|
81
|
-
if (prev[index] === width) return prev;
|
|
82
|
-
const next = [...prev];
|
|
83
|
-
next[index] = width;
|
|
84
|
-
return next;
|
|
85
|
-
});
|
|
86
|
-
}, []);
|
|
87
|
-
const onHeaderColumnLayout = useCallback((index, event) => {
|
|
88
|
-
setMeasuredWidth(setHeaderWidths, index, event.nativeEvent.layout.width);
|
|
89
|
-
}, [setMeasuredWidth]);
|
|
90
|
-
const onBodyColumnLayout = useCallback((index, event) => {
|
|
91
|
-
setMeasuredWidth(setBodyWidths, index, event.nativeEvent.layout.width);
|
|
92
|
-
}, [setMeasuredWidth]);
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Shared width for header + body cells in a column (max of natural header
|
|
96
|
-
* label vs body content). No columnGap between columns — gaps would shift
|
|
97
|
-
* headers relative to the flush table grid below.
|
|
98
|
-
*/
|
|
99
|
-
const columnWidthStyle = index => {
|
|
100
|
-
const width = Math.max(headerWidths[index] ?? 0, bodyWidths[index] ?? 0);
|
|
101
|
-
if (width > 0) {
|
|
102
|
-
return {
|
|
103
|
-
width,
|
|
104
|
-
minWidth: width,
|
|
105
|
-
flexShrink: 0,
|
|
106
|
-
flexGrow: 0
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
flexShrink: 0,
|
|
111
|
-
flexGrow: 0
|
|
112
|
-
};
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Container
|
|
116
77
|
const gap = getVariableByName('planComparisonCard/gap', modes) ?? 16;
|
|
117
|
-
|
|
118
|
-
// Header
|
|
119
78
|
const headerFg = getVariableByName('planComparisonCard/header/fg', modes) ?? '#ffffff';
|
|
120
79
|
const headerBrandFg = getVariableByName('planComparisonCard/header/brand/fg', modes) ?? '#cea15a';
|
|
121
80
|
const headerFontSize = getVariableByName('planComparisonCard/header/fontSize', modes) ?? 14;
|
|
122
81
|
const headerFontFamily = getVariableByName('planComparisonCard/header/fontFamily', modes) ?? 'JioType Var';
|
|
123
82
|
const headerLineHeight = getVariableByName('planComparisonCard/header/lineHeight', modes) ?? 18;
|
|
124
83
|
const headerFontWeight = getVariableByName('planComparisonCard/header/fontWeight', modes) ?? '500';
|
|
125
|
-
|
|
126
|
-
// Table
|
|
127
84
|
const tableBackground = getVariableByName('planComparisonCard/tableRow/background', modes) ?? '#141414';
|
|
128
85
|
const tableRadius = getVariableByName('planComparisonCard/tableRow/radius', modes) ?? 16;
|
|
129
86
|
const tableBorderSize = getVariableByName('planComparisonCard/tableRow/border/size', modes) ?? 1;
|
|
130
87
|
const tableBorderColor = getVariableByName('planComparisonCard/tableRow/border/color', modes) ?? '#1e1a14';
|
|
131
|
-
|
|
132
|
-
// Cell
|
|
133
88
|
const cellPadding = getVariableByName('planComparisonCard/tableCell/padding', modes) ?? 12;
|
|
134
89
|
const cellGap = getVariableByName('planComparisonCard/tableCell/gap', modes) ?? 2;
|
|
135
90
|
const cellMinHeight = getVariableByName('planComparisonCard/tableCell/height', modes) ?? 46;
|
|
136
91
|
const cellBorderSize = getVariableByName('planComparisonCard/tableCell/border/size', modes) ?? 1;
|
|
137
92
|
const cellBorderColor = getVariableByName('planComparisonCard/tableCell/border/color', modes) ?? '#1e1a14';
|
|
138
|
-
|
|
139
|
-
// Cell label
|
|
140
93
|
const labelColor = getVariableByName('planComparisonCard/tableCell/label/color', modes) ?? '#ffffff';
|
|
141
94
|
const labelDisabledColor = getVariableByName('planComparisonCard/tableCell/label/disabled/color', modes) ?? '#91949c';
|
|
142
95
|
const labelFontSize = getVariableByName('planComparisonCard/tableCell/label/fontSize', modes) ?? 12;
|
|
143
96
|
const labelFontFamily = getVariableByName('planComparisonCard/tableCell/label/fontFamily', modes) ?? 'JioType Var';
|
|
144
97
|
const labelLineHeight = getVariableByName('planComparisonCard/tableCell/label/lineHeight', modes) ?? 16;
|
|
145
98
|
const labelFontWeight = getVariableByName('planComparisonCard/tableCell/label/fontWeight', modes) ?? '400';
|
|
146
|
-
|
|
147
|
-
// Cell value
|
|
148
99
|
const valueColor = getVariableByName('planComparisonCard/tableCell/value/color', modes) ?? '#ffffff';
|
|
149
100
|
const valueFontSize = getVariableByName('planComparisonCard/tableCell/value/fontSize', modes) ?? 12;
|
|
150
101
|
const valueFontFamily = getVariableByName('planComparisonCard/tableCell/value/fontFamily', modes) ?? 'JioType Var';
|
|
151
102
|
const valueLineHeight = getVariableByName('planComparisonCard/tableCell/value/lineHeight', modes) ?? 16;
|
|
152
103
|
const valueFontWeight = getVariableByName('planComparisonCard/tableCell/value/fontWeight', modes) ?? '500';
|
|
153
|
-
|
|
154
|
-
// Icon
|
|
155
104
|
const iconColor = getVariableByName('planComparisonCard/icon/color', modes) ?? '#ffffff';
|
|
156
105
|
const iconSize = getVariableByName('planComparisonCard/icon/size', modes) ?? 16;
|
|
157
106
|
const toWeight = w => typeof w === 'number' ? `${w}` : w;
|
|
@@ -180,12 +129,26 @@ function PlanComparisonCard({
|
|
|
180
129
|
fontWeight: toWeight(valueFontWeight),
|
|
181
130
|
textAlign: 'center'
|
|
182
131
|
};
|
|
183
|
-
const
|
|
132
|
+
const rowStyle = {
|
|
133
|
+
flexDirection: 'row',
|
|
134
|
+
width: '100%'
|
|
135
|
+
};
|
|
136
|
+
const labelCellStyle = {
|
|
137
|
+
flexDirection: 'row',
|
|
138
|
+
alignItems: 'center',
|
|
139
|
+
gap: cellGap,
|
|
140
|
+
padding: cellPadding,
|
|
141
|
+
minHeight: cellMinHeight
|
|
142
|
+
};
|
|
143
|
+
const valueCellStyle = {
|
|
144
|
+
flexDirection: 'row',
|
|
184
145
|
alignItems: 'center',
|
|
185
|
-
justifyContent: 'center'
|
|
146
|
+
justifyContent: 'center',
|
|
147
|
+
padding: cellPadding,
|
|
148
|
+
minHeight: cellMinHeight,
|
|
149
|
+
width: '100%'
|
|
186
150
|
};
|
|
187
151
|
const renderValue = (value, cellKey) => {
|
|
188
|
-
// "Not available" → muted cross icon.
|
|
189
152
|
if (value === false) {
|
|
190
153
|
return /*#__PURE__*/_jsx(Icon, {
|
|
191
154
|
name: "ic_close",
|
|
@@ -193,87 +156,54 @@ function PlanComparisonCard({
|
|
|
193
156
|
color: labelDisabledColor
|
|
194
157
|
}, cellKey);
|
|
195
158
|
}
|
|
196
|
-
// Empty cell.
|
|
197
159
|
if (value === null || value === undefined || value === true) {
|
|
198
160
|
return null;
|
|
199
161
|
}
|
|
200
|
-
// Text content.
|
|
201
162
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
202
163
|
return /*#__PURE__*/_jsx(Text, {
|
|
203
164
|
style: valueTextStyle,
|
|
204
165
|
children: value
|
|
205
166
|
}, cellKey);
|
|
206
167
|
}
|
|
207
|
-
// Custom node — forward modes so themed children stay in sync.
|
|
208
168
|
return cloneChildrenWithModes(value, modes);
|
|
209
169
|
};
|
|
210
|
-
const labelCellStyle = {
|
|
211
|
-
flexDirection: 'row',
|
|
212
|
-
alignItems: 'center',
|
|
213
|
-
gap: cellGap,
|
|
214
|
-
padding: cellPadding,
|
|
215
|
-
minHeight: cellMinHeight,
|
|
216
|
-
flexShrink: 0
|
|
217
|
-
};
|
|
218
|
-
const valueCellStyle = {
|
|
219
|
-
flexDirection: 'row',
|
|
220
|
-
alignItems: 'center',
|
|
221
|
-
justifyContent: 'center',
|
|
222
|
-
padding: cellPadding,
|
|
223
|
-
minHeight: cellMinHeight,
|
|
224
|
-
flexShrink: 0
|
|
225
|
-
};
|
|
226
170
|
return /*#__PURE__*/_jsxs(View, {
|
|
227
171
|
style: [{
|
|
228
172
|
gap,
|
|
229
|
-
|
|
173
|
+
width: '100%'
|
|
230
174
|
}, style],
|
|
231
175
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
232
|
-
style:
|
|
233
|
-
flexDirection: 'row',
|
|
234
|
-
alignItems: 'flex-end'
|
|
235
|
-
},
|
|
176
|
+
style: rowStyle,
|
|
236
177
|
children: [/*#__PURE__*/_jsx(View, {
|
|
237
|
-
style:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
alignSelf: 'center'
|
|
249
|
-
}],
|
|
250
|
-
children: column.label
|
|
251
|
-
})
|
|
252
|
-
}, column.label ?? index);
|
|
253
|
-
})]
|
|
254
|
-
}), /*#__PURE__*/_jsxs(View, {
|
|
178
|
+
style: labelColumnStyle
|
|
179
|
+
}), columns.map((column, index) => /*#__PURE__*/_jsx(View, {
|
|
180
|
+
style: planColumnStyle,
|
|
181
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
182
|
+
style: [headerTextStyle, {
|
|
183
|
+
color: column.brand ? headerBrandFg : headerFg
|
|
184
|
+
}],
|
|
185
|
+
children: column.label
|
|
186
|
+
})
|
|
187
|
+
}, column.label ?? index))]
|
|
188
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
255
189
|
style: {
|
|
256
|
-
|
|
257
|
-
alignSelf: 'flex-start',
|
|
190
|
+
width: '100%',
|
|
258
191
|
backgroundColor: tableBackground,
|
|
259
192
|
borderWidth: tableBorderSize,
|
|
260
193
|
borderColor: tableBorderColor,
|
|
261
194
|
borderRadius: tableRadius,
|
|
262
195
|
overflow: 'hidden'
|
|
263
196
|
},
|
|
264
|
-
children:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
style: [labelCellStyle,
|
|
274
|
-
borderBottomWidth: isLast ? 0 : cellBorderSize,
|
|
275
|
-
borderBottomColor: cellBorderColor
|
|
276
|
-
}],
|
|
197
|
+
children: rows.map((row, rowIndex) => {
|
|
198
|
+
const isLast = rowIndex === rows.length - 1;
|
|
199
|
+
const showInfo = row.showInfo || row.onInfoPress != null;
|
|
200
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
201
|
+
style: [rowStyle, {
|
|
202
|
+
borderBottomWidth: isLast ? 0 : cellBorderSize,
|
|
203
|
+
borderBottomColor: cellBorderColor
|
|
204
|
+
}],
|
|
205
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
206
|
+
style: [labelColumnStyle, labelCellStyle],
|
|
277
207
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
278
208
|
style: labelTextStyle,
|
|
279
209
|
children: row.label
|
|
@@ -292,30 +222,12 @@ function PlanComparisonCard({
|
|
|
292
222
|
size: iconSize,
|
|
293
223
|
color: iconColor
|
|
294
224
|
}))]
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
style: [columnWidthStyle(colIndexWidth), planHeaderColumnStyle],
|
|
302
|
-
children: rows.map((row, rowIndex) => {
|
|
303
|
-
const isLast = rowIndex === rows.length - 1;
|
|
304
|
-
return /*#__PURE__*/_jsx(View, {
|
|
305
|
-
style: [valueCellStyle, {
|
|
306
|
-
borderBottomWidth: isLast ? 0 : cellBorderSize,
|
|
307
|
-
borderBottomColor: cellBorderColor
|
|
308
|
-
}],
|
|
309
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
310
|
-
style: {
|
|
311
|
-
flexShrink: 0
|
|
312
|
-
},
|
|
313
|
-
children: renderValue(row.values?.[colIndex], `${rowIndex}-${colIndex}`)
|
|
314
|
-
})
|
|
315
|
-
}, row.key ?? `${row.label}-${rowIndex}`);
|
|
316
|
-
})
|
|
317
|
-
}, column.label ?? colIndex);
|
|
318
|
-
})]
|
|
225
|
+
}), columns.map((column, colIndex) => /*#__PURE__*/_jsx(View, {
|
|
226
|
+
style: [planColumnStyle, valueCellStyle],
|
|
227
|
+
children: renderValue(row.values?.[colIndex], `${rowIndex}-${colIndex}`)
|
|
228
|
+
}, column.label ?? colIndex))]
|
|
229
|
+
}, row.key ?? `${row.label}-${rowIndex}`);
|
|
230
|
+
})
|
|
319
231
|
})]
|
|
320
232
|
});
|
|
321
233
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect } from 'react';
|
|
4
|
+
import { StyleSheet, View } from 'react-native';
|
|
5
|
+
import Animated, { Easing, cancelAnimation, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
|
|
6
|
+
import Svg, { Path } from 'react-native-svg';
|
|
7
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
8
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
9
|
+
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
10
|
+
import { useReducedMotion } from '../../skeleton/useReducedMotion';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Per-segment colours, resolved from the Figma `spiner/*` tokens. Consumers can
|
|
14
|
+
* override any subset via the `colors` prop.
|
|
15
|
+
*/
|
|
16
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
17
|
+
const SEGMENT_COUNT = 3;
|
|
18
|
+
const DEFAULT_SIZE = 72;
|
|
19
|
+
const DEFAULT_DURATION_MS = 1500;
|
|
20
|
+
const DEFAULT_GRAVITY = 0.45;
|
|
21
|
+
|
|
22
|
+
// Stroke thickness as a fraction of the diameter (matches the Figma ring weight).
|
|
23
|
+
const STROKE_RATIO = 0.11;
|
|
24
|
+
// Angular length of each individual segment.
|
|
25
|
+
const ARC_LENGTH_DEG = 100;
|
|
26
|
+
// Spacing between consecutive heads when fully bunched at the top. Small but
|
|
27
|
+
// non-zero so all three colours stay faintly visible as they crest the top.
|
|
28
|
+
const SPREAD_MIN_DEG = 10;
|
|
29
|
+
// Spacing between consecutive heads at full spread. At this extent each segment's
|
|
30
|
+
// tail only overlaps the next head by `ARC_LENGTH_DEG - SPREAD_MAX_DEG` (16°) —
|
|
31
|
+
// the maximum extension the chain reaches while staying connected (never a gap).
|
|
32
|
+
const SPREAD_MAX_DEG = 84;
|
|
33
|
+
// Fraction of each revolution spent gradually fanning *out* (the rest is spent
|
|
34
|
+
// snapping back together over the top).
|
|
35
|
+
//
|
|
36
|
+
// This is the knob that balances "reaches full extension" against "never stalls
|
|
37
|
+
// and never recoils". The tail segment's velocity while spreading is
|
|
38
|
+
// `vLead * (1 - spreadRange / (SPREAD_OUT_FRAC * π))`. Spreading the fan-out over
|
|
39
|
+
// ~3/4 of the turn keeps that factor around ~0.45 (so the tail always carries
|
|
40
|
+
// clear forward momentum — it never crawls to a stall, and never reverses),
|
|
41
|
+
// while still letting the breath reach a full 1.0. The remaining ~1/4 is an
|
|
42
|
+
// energetic gather over the top where the trailing segments whip forward to
|
|
43
|
+
// rejoin the lead. A symmetric (sinusoidal/triangle) breath cannot do all three:
|
|
44
|
+
// reach full extension, avoid recoil, and avoid a sustained stall.
|
|
45
|
+
const SPREAD_OUT_FRAC = 0.75;
|
|
46
|
+
const DEG_TO_RAD = Math.PI / 180;
|
|
47
|
+
const TWO_PI = Math.PI * 2;
|
|
48
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
49
|
+
const toNumber = (value, fallback) => {
|
|
50
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
const parsed = Number(value);
|
|
55
|
+
if (Number.isFinite(parsed)) {
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return fallback;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds the SVG path for a single fixed-length arc whose *head* sits at the
|
|
64
|
+
* top (12 o'clock) and whose body trails counter-clockwise behind it. Rotating
|
|
65
|
+
* the containing view clockwise then places the head at the desired angle.
|
|
66
|
+
*/
|
|
67
|
+
const buildArcPath = (center, radius, arcLengthDeg) => {
|
|
68
|
+
const arc = arcLengthDeg * DEG_TO_RAD;
|
|
69
|
+
// Head at the top: phi = 0 -> (center, center - radius).
|
|
70
|
+
const headX = center;
|
|
71
|
+
const headY = center - radius;
|
|
72
|
+
// Tail trails counter-clockwise by `arc`: phi = -arc.
|
|
73
|
+
const tailX = center + radius * Math.sin(-arc);
|
|
74
|
+
const tailY = center - radius * Math.cos(-arc);
|
|
75
|
+
const largeArc = arcLengthDeg > 180 ? 1 : 0;
|
|
76
|
+
// Sweep from tail -> head is clockwise (sweep flag = 1 in SVG y-down space).
|
|
77
|
+
return `M ${tailX} ${tailY} A ${radius} ${radius} 0 ${largeArc} 1 ${headX} ${headY}`;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Animated rotation for one segment.
|
|
82
|
+
*
|
|
83
|
+
* A single linear clock drives a gravity-warped lead angle: it advances faster
|
|
84
|
+
* over the top and slower through the bottom, giving the fall its weight. Each
|
|
85
|
+
* segment trails the lead by `index * offset`, where `offset` breathes between
|
|
86
|
+
* its bunched (top) and spread (bottom) extents in lock-step with the lead's
|
|
87
|
+
* vertical position. Because the offset is bounded by `SPREAD_MAX_DEG`, the
|
|
88
|
+
* three segments form a continuously-overlapping chain that gathers at the top
|
|
89
|
+
* and fans out — fully connected — through the free fall.
|
|
90
|
+
*/
|
|
91
|
+
const useSegmentRotation = (clock, index, gravity, spreadMinRad, spreadMaxRad, spreadOutFrac) => useAnimatedStyle(() => {
|
|
92
|
+
'worklet';
|
|
93
|
+
|
|
94
|
+
const tau = clock.value * TWO_PI;
|
|
95
|
+
// Lead angle (clockwise from top). d(lead)/dtau = 1 + gravity*cos(tau) is
|
|
96
|
+
// maximal at the top (tau = 0) and minimal at the bottom (tau = PI), giving
|
|
97
|
+
// the fall its weight.
|
|
98
|
+
const lead = tau + gravity * Math.sin(tau);
|
|
99
|
+
// Breathing is an asymmetric saw in the lead angle: it ramps *gradually* from
|
|
100
|
+
// 0 (bunched, top) up to 1 (fully spread) over `spreadOutFrac` of the turn,
|
|
101
|
+
// then drops back to 0 over the remaining arc (the quick gather over the top).
|
|
102
|
+
// The gentle fan-out slope keeps the trailing segment moving forward at a
|
|
103
|
+
// healthy fraction of the lead's speed — it never stalls and never recoils —
|
|
104
|
+
// while still reaching full extension; the steeper gather is a forward whip,
|
|
105
|
+
// so momentum only ever increases there.
|
|
106
|
+
const leadMod = lead - TWO_PI * Math.floor(lead / TWO_PI);
|
|
107
|
+
const splitLead = spreadOutFrac * TWO_PI;
|
|
108
|
+
const breath = leadMod < splitLead ? leadMod / splitLead : (TWO_PI - leadMod) / (TWO_PI - splitLead);
|
|
109
|
+
const offset = spreadMinRad + breath * (spreadMaxRad - spreadMinRad);
|
|
110
|
+
const head = lead - index * offset;
|
|
111
|
+
return {
|
|
112
|
+
transform: [{
|
|
113
|
+
rotate: `${head * 180 / Math.PI}deg`
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}, [gravity, index, spreadMinRad, spreadMaxRad, spreadOutFrac]);
|
|
117
|
+
const fullSize = {
|
|
118
|
+
...StyleSheet.absoluteFillObject
|
|
119
|
+
};
|
|
120
|
+
function Spinner({
|
|
121
|
+
size = DEFAULT_SIZE,
|
|
122
|
+
durationMs = DEFAULT_DURATION_MS,
|
|
123
|
+
gravity = DEFAULT_GRAVITY,
|
|
124
|
+
colors,
|
|
125
|
+
animating = true,
|
|
126
|
+
modes: propModes = EMPTY_MODES,
|
|
127
|
+
style,
|
|
128
|
+
accessibilityLabel = 'Loading',
|
|
129
|
+
...rest
|
|
130
|
+
}) {
|
|
131
|
+
const {
|
|
132
|
+
modes: globalModes
|
|
133
|
+
} = useTokens();
|
|
134
|
+
const modes = {
|
|
135
|
+
...globalModes,
|
|
136
|
+
...propModes
|
|
137
|
+
};
|
|
138
|
+
const systemReducedMotion = useReducedMotion();
|
|
139
|
+
const isAnimated = animating && !systemReducedMotion;
|
|
140
|
+
const resolvedSize = toNumber(size, DEFAULT_SIZE);
|
|
141
|
+
const safeGravity = clamp(toNumber(gravity, DEFAULT_GRAVITY), 0, 0.9);
|
|
142
|
+
const strokeWidth = Math.max(1, resolvedSize * STROKE_RATIO);
|
|
143
|
+
const radius = Math.max(0, (resolvedSize - strokeWidth) / 2);
|
|
144
|
+
const center = resolvedSize / 2;
|
|
145
|
+
const arcPath = buildArcPath(center, radius, ARC_LENGTH_DEG);
|
|
146
|
+
const segmentColors = [colors?.primary ?? getVariableByName('spiner/primary/bg', modes) ?? '#d0a259', colors?.secondary ?? getVariableByName('spiner/secondary/bg', modes) ?? '#5b00b5', colors?.tertiary ?? getVariableByName('spiner/tertiary/bg', modes) ?? '#066b99'];
|
|
147
|
+
const clock = useSharedValue(0);
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!isAnimated) {
|
|
150
|
+
cancelAnimation(clock);
|
|
151
|
+
clock.value = 0;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
clock.value = 0;
|
|
155
|
+
clock.value = withRepeat(withTiming(1, {
|
|
156
|
+
duration: Math.max(1, durationMs),
|
|
157
|
+
easing: Easing.linear
|
|
158
|
+
}), -1, false);
|
|
159
|
+
return () => {
|
|
160
|
+
cancelAnimation(clock);
|
|
161
|
+
};
|
|
162
|
+
}, [isAnimated, durationMs, clock]);
|
|
163
|
+
|
|
164
|
+
// Hooks must run unconditionally and in a stable order, so all three segment
|
|
165
|
+
// styles are always computed even when the spinner renders statically.
|
|
166
|
+
const spreadMinRad = SPREAD_MIN_DEG * DEG_TO_RAD;
|
|
167
|
+
const spreadMaxRad = SPREAD_MAX_DEG * DEG_TO_RAD;
|
|
168
|
+
const style0 = useSegmentRotation(clock, 0, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
|
|
169
|
+
const style1 = useSegmentRotation(clock, 1, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
|
|
170
|
+
const style2 = useSegmentRotation(clock, 2, safeGravity, spreadMinRad, spreadMaxRad, SPREAD_OUT_FRAC);
|
|
171
|
+
const animatedStyles = [style0, style1, style2];
|
|
172
|
+
|
|
173
|
+
// Static resting fan (evenly spaced) used when animation is disabled.
|
|
174
|
+
const restingRotations = [0, -120, -240];
|
|
175
|
+
const containerStyle = {
|
|
176
|
+
height: resolvedSize,
|
|
177
|
+
width: resolvedSize,
|
|
178
|
+
position: 'relative'
|
|
179
|
+
};
|
|
180
|
+
return /*#__PURE__*/_jsx(View, {
|
|
181
|
+
accessibilityRole: "progressbar",
|
|
182
|
+
accessibilityLabel: accessibilityLabel,
|
|
183
|
+
style: [containerStyle, style],
|
|
184
|
+
...rest,
|
|
185
|
+
children: Array.from({
|
|
186
|
+
length: SEGMENT_COUNT
|
|
187
|
+
}, (_, i) => SEGMENT_COUNT - 1 - i).map(segmentIndex => {
|
|
188
|
+
const segmentStyle = isAnimated ? animatedStyles[segmentIndex] : {
|
|
189
|
+
transform: [{
|
|
190
|
+
rotate: `${restingRotations[segmentIndex]}deg`
|
|
191
|
+
}]
|
|
192
|
+
};
|
|
193
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
194
|
+
style: [fullSize, segmentStyle],
|
|
195
|
+
pointerEvents: "none",
|
|
196
|
+
children: /*#__PURE__*/_jsx(Svg, {
|
|
197
|
+
width: resolvedSize,
|
|
198
|
+
height: resolvedSize,
|
|
199
|
+
viewBox: `0 0 ${resolvedSize} ${resolvedSize}`,
|
|
200
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
201
|
+
d: arcPath,
|
|
202
|
+
stroke: segmentColors[segmentIndex],
|
|
203
|
+
strokeWidth: strokeWidth,
|
|
204
|
+
strokeLinecap: "round",
|
|
205
|
+
fill: "none"
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
}, segmentIndex);
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
export default Spinner;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useRef, useState } from 'react';
|
|
4
|
-
import { Pressable, View, TextInput as RNTextInput } from 'react-native';
|
|
4
|
+
import { Platform, Pressable, View, TextInput as RNTextInput } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import Icon from '../../icons/Icon';
|
|
7
7
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
@@ -38,7 +38,8 @@ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
|
38
38
|
* Helper function to convert a color to a more transparent version for placeholder text.
|
|
39
39
|
* Takes a color string (hex, rgb, rgba) and returns it with reduced opacity.
|
|
40
40
|
*/
|
|
41
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
41
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
42
|
+
const IS_WEB = Platform.OS === 'web';
|
|
42
43
|
function makePlaceholderColor(color, opacity = 0.5) {
|
|
43
44
|
if (!color || typeof color !== 'string') {
|
|
44
45
|
return color || '';
|
|
@@ -114,10 +115,9 @@ function TextInput({
|
|
|
114
115
|
// Track focus state to hide placeholder when focused
|
|
115
116
|
const [isFocused, setIsFocused] = useState(false);
|
|
116
117
|
const [isHovered, setIsHovered] = useState(false);
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// native input only gains focus on the *second* tap.
|
|
118
|
+
// On web we keep a ref so a click anywhere inside the (Pressable) wrapper can
|
|
119
|
+
// focus the input. On native the wrapper is a plain View and the native
|
|
120
|
+
// input focuses itself on the first tap (see container note below).
|
|
121
121
|
const inputRef = useRef(null);
|
|
122
122
|
|
|
123
123
|
// Resolve container tokens
|
|
@@ -209,19 +209,18 @@ function TextInput({
|
|
|
209
209
|
|
|
210
210
|
// Generate default accessibility label from placeholder if not provided
|
|
211
211
|
const defaultAccessibilityLabel = accessibilityLabel || placeholder || 'Text input';
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
accessible: false,
|
|
212
|
+
|
|
213
|
+
// IMPORTANT (Android focus reliability):
|
|
214
|
+
// Do NOT wrap the native <TextInput> in a Pressable/Touchable on native.
|
|
215
|
+
// A touch-responder-claiming wrapper steals the first tap, which is the
|
|
216
|
+
// classic cause of the "needs 2–3 taps to focus" Android bug — and forwarding
|
|
217
|
+
// focus from `onPress` is unreliable because the press is cancelled by the
|
|
218
|
+
// tiniest finger movement. A plain <View> does not claim the responder, so
|
|
219
|
+
// the native input receives the tap and focuses on the FIRST tap.
|
|
220
|
+
// On web there is no such issue, so we keep the Pressable for the hover
|
|
221
|
+
// affordance plus click-anywhere-to-focus.
|
|
222
|
+
const containerStyleArray = [containerStyle, focusContainerStyle, hoverStyle, style];
|
|
223
|
+
const inner = /*#__PURE__*/_jsxs(_Fragment, {
|
|
225
224
|
children: [processedLeading && /*#__PURE__*/_jsx(View, {
|
|
226
225
|
accessibilityElementsHidden: true,
|
|
227
226
|
importantForAccessibility: "no",
|
|
@@ -244,6 +243,22 @@ function TextInput({
|
|
|
244
243
|
children: processedTrailing
|
|
245
244
|
})]
|
|
246
245
|
});
|
|
246
|
+
if (IS_WEB) {
|
|
247
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
248
|
+
style: containerStyleArray,
|
|
249
|
+
onHoverIn: () => setIsHovered(true),
|
|
250
|
+
onHoverOut: () => setIsHovered(false)
|
|
251
|
+
// Web: clicking the padding / icon gutter focuses the input too.
|
|
252
|
+
,
|
|
253
|
+
onPress: () => inputRef.current?.focus(),
|
|
254
|
+
accessible: false,
|
|
255
|
+
children: inner
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return /*#__PURE__*/_jsx(View, {
|
|
259
|
+
style: containerStyleArray,
|
|
260
|
+
children: inner
|
|
261
|
+
});
|
|
247
262
|
}
|
|
248
263
|
|
|
249
264
|
/**
|
|
@@ -66,6 +66,7 @@ export { default as Title } from './Title/Title';
|
|
|
66
66
|
export { default as Screen } from './Screen/Screen';
|
|
67
67
|
export { default as Section } from './Section/Section';
|
|
68
68
|
export { default as Slot } from './Slot/Slot';
|
|
69
|
+
export { default as Spinner } from './Spinner/Spinner';
|
|
69
70
|
export { default as Stepper } from './Stepper/Stepper';
|
|
70
71
|
export { Step } from './Stepper/Step';
|
|
71
72
|
export { StepLabel } from './Stepper/StepLabel';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import Svg, { Path } from 'react-native-svg';
|
|
5
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
6
|
+
export const IconArrowdown = props => /*#__PURE__*/_jsx(Svg, {
|
|
7
|
+
viewBox: "0 0 24 24",
|
|
8
|
+
...props,
|
|
9
|
+
children: /*#__PURE__*/_jsx(Path, {
|
|
10
|
+
d: "M18.7099 14.29C18.617 14.1963 18.5064 14.1219 18.3845 14.0711C18.2627 14.0203 18.132 13.9942 17.9999 13.9942C17.8679 13.9942 17.7372 14.0203 17.6154 14.0711C17.4935 14.1219 17.3829 14.1963 17.2899 14.29L12.9999 18.59V3C12.9999 2.73478 12.8946 2.48043 12.707 2.29289C12.5195 2.10536 12.2652 2 11.9999 2C11.7347 2 11.4804 2.10536 11.2928 2.29289C11.1053 2.48043 10.9999 2.73478 10.9999 3V18.59L6.70994 14.29C6.52164 14.1017 6.26624 13.9959 5.99994 13.9959C5.73364 13.9959 5.47825 14.1017 5.28994 14.29C5.10164 14.4783 4.99585 14.7337 4.99585 15C4.99585 15.1319 5.02182 15.2624 5.07228 15.3842C5.12274 15.5061 5.1967 15.6168 5.28994 15.71L11.2899 21.71C11.3829 21.8037 11.4935 21.8781 11.6154 21.9289C11.7372 21.9797 11.8679 22.0058 11.9999 22.0058C12.132 22.0058 12.2627 21.9797 12.3845 21.9289C12.5064 21.8781 12.617 21.8037 12.7099 21.71L18.7099 15.71C18.8037 15.617 18.8781 15.5064 18.9288 15.3846C18.9796 15.2627 19.0057 15.132 19.0057 15C19.0057 14.868 18.9796 14.7373 18.9288 14.6154C18.8781 14.4936 18.8037 14.383 18.7099 14.29Z"
|
|
11
|
+
})
|
|
12
|
+
});
|