@saltcorn/builder 1.1.3-beta.9 → 1.1.3-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder_bundle.js +1 -1
- package/package.json +2 -2
- package/src/components/elements/Action.js +12 -12
- package/src/components/elements/Aggregation.js +7 -5
- package/src/components/elements/Container.js +7 -0
- package/src/components/elements/Image.js +29 -3
- package/src/components/elements/JoinField.js +1 -0
- package/src/components/elements/Link.js +4 -2
- package/src/components/elements/Tabs.js +5 -2
- package/src/components/elements/Text.js +17 -1
- package/src/components/elements/View.js +1 -0
- package/src/components/elements/ViewLink.js +2 -1
- package/src/components/elements/utils.js +11 -8
- package/src/components/storage.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/builder",
|
|
3
|
-
"version": "1.1.3-
|
|
3
|
+
"version": "1.1.3-rc.0",
|
|
4
4
|
"description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"homepage": "https://saltcorn.com",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@babel/preset-react": "7.24.7",
|
|
21
21
|
"@craftjs/core": "0.1.0-beta.20",
|
|
22
22
|
"@craftjs/utils": "0.1.0-beta.20",
|
|
23
|
-
"@saltcorn/common-code": "1.1.3-
|
|
23
|
+
"@saltcorn/common-code": "1.1.3-rc.0",
|
|
24
24
|
"saltcorn-craft-layers-noeye": "0.1.0-beta.22",
|
|
25
25
|
"@fonticonpicker/react-fonticonpicker": "1.2.0",
|
|
26
26
|
"@fortawesome/fontawesome-svg-core": "1.2.34",
|
|
@@ -72,10 +72,10 @@ const Action = ({
|
|
|
72
72
|
color: action_textcol || "#000000",
|
|
73
73
|
}
|
|
74
74
|
: action_style === "on_page_load"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
? {
|
|
76
|
+
border: "1px red dashed",
|
|
77
|
+
}
|
|
78
|
+
: {}
|
|
79
79
|
}
|
|
80
80
|
>
|
|
81
81
|
<DynamicFontAwesomeIcon icon={action_icon} className="me-1" />
|
|
@@ -134,7 +134,6 @@ const ActionSettings = () => {
|
|
|
134
134
|
step_action_names,
|
|
135
135
|
spinner,
|
|
136
136
|
is_submit_action,
|
|
137
|
-
|
|
138
137
|
} = node;
|
|
139
138
|
const options = useContext(optionsCtx);
|
|
140
139
|
const getCfgFields = (fv) => (options.actionConfigForms || {})[fv];
|
|
@@ -149,13 +148,13 @@ const ActionSettings = () => {
|
|
|
149
148
|
const cfg_link = (options.triggerActions || []).includes(name)
|
|
150
149
|
? `/actions/configure/${encodeURIComponent(name)}`
|
|
151
150
|
: name === "Multi-step action" &&
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
(options.triggerActions || []).includes(
|
|
152
|
+
step_action_names?.[use_setting_action_n]
|
|
153
|
+
)
|
|
154
|
+
? `/actions/configure/${encodeURIComponent(
|
|
155
|
+
step_action_names?.[use_setting_action_n]
|
|
156
|
+
)}`
|
|
157
|
+
: "";
|
|
159
158
|
return (
|
|
160
159
|
<div>
|
|
161
160
|
<table className="w-100">
|
|
@@ -393,6 +392,7 @@ const ActionSettings = () => {
|
|
|
393
392
|
type="text"
|
|
394
393
|
className="form-control text-to-display"
|
|
395
394
|
value={step_only_ifs?.[use_setting_action_n] || ""}
|
|
395
|
+
spellCheck={false}
|
|
396
396
|
onChange={(e) => {
|
|
397
397
|
if (!e.target) return;
|
|
398
398
|
const value = e.target.value;
|
|
@@ -83,10 +83,10 @@ const AggregationSettings = () => {
|
|
|
83
83
|
stat === "Percent true" || stat === "Percent false"
|
|
84
84
|
? "Float"
|
|
85
85
|
: stat === "Count" || stat === "CountUnique"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
? "Integer"
|
|
87
|
+
: stat === "Array_Agg"
|
|
88
|
+
? "Array"
|
|
89
|
+
: targetFieldType;
|
|
90
90
|
const fvs = options.agg_fieldview_options[outcomeType];
|
|
91
91
|
|
|
92
92
|
const [fetchedCfgFields, setFetchedCfgFields] = useState([]);
|
|
@@ -107,7 +107,7 @@ const AggregationSettings = () => {
|
|
|
107
107
|
agg_outcome_type: outcomeType,
|
|
108
108
|
agg_fieldview,
|
|
109
109
|
agg_field: targetField?.name,
|
|
110
|
-
mode: options?.mode
|
|
110
|
+
mode: options?.mode,
|
|
111
111
|
}),
|
|
112
112
|
}
|
|
113
113
|
)
|
|
@@ -233,7 +233,9 @@ const AggregationSettings = () => {
|
|
|
233
233
|
type="text"
|
|
234
234
|
className="form-control"
|
|
235
235
|
value={aggwhere}
|
|
236
|
+
spellCheck={false}
|
|
236
237
|
onChange={setAProp("aggwhere")}
|
|
238
|
+
onInput={(e) => validate_expression_elem($(e.target))}
|
|
237
239
|
/>
|
|
238
240
|
</td>
|
|
239
241
|
</tr>
|
|
@@ -629,6 +629,7 @@ const ContainerSettings = () => {
|
|
|
629
629
|
value={imgResponsiveWidths}
|
|
630
630
|
className="form-control"
|
|
631
631
|
onChange={setAProp("imgResponsiveWidths")}
|
|
632
|
+
spellCheck={false}
|
|
632
633
|
/>
|
|
633
634
|
<small>
|
|
634
635
|
<i>
|
|
@@ -951,7 +952,9 @@ const ContainerSettings = () => {
|
|
|
951
952
|
type="text"
|
|
952
953
|
className="form-control text-to-display"
|
|
953
954
|
value={showIfFormula}
|
|
955
|
+
spellCheck={false}
|
|
954
956
|
onChange={setAProp("showIfFormula")}
|
|
957
|
+
onInput={(e) => validate_expression_elem($(e.target))}
|
|
955
958
|
/>
|
|
956
959
|
<div style={{ marginTop: "-5px" }}>
|
|
957
960
|
<small className="text-muted font-monospace">
|
|
@@ -1048,6 +1051,7 @@ const ContainerSettings = () => {
|
|
|
1048
1051
|
<input
|
|
1049
1052
|
type="text"
|
|
1050
1053
|
className="form-control"
|
|
1054
|
+
spellCheck={false}
|
|
1051
1055
|
value={url}
|
|
1052
1056
|
onChange={setAProp("url")}
|
|
1053
1057
|
/>
|
|
@@ -1097,6 +1101,7 @@ const ContainerSettings = () => {
|
|
|
1097
1101
|
type="text"
|
|
1098
1102
|
className="form-control text-to-display"
|
|
1099
1103
|
value={customId}
|
|
1104
|
+
spellCheck={false}
|
|
1100
1105
|
onChange={setAProp("customId")}
|
|
1101
1106
|
/>
|
|
1102
1107
|
</OrFormula>
|
|
@@ -1108,6 +1113,7 @@ const ContainerSettings = () => {
|
|
|
1108
1113
|
type="text"
|
|
1109
1114
|
className="form-control text-to-display"
|
|
1110
1115
|
value={customClass}
|
|
1116
|
+
spellCheck={false}
|
|
1111
1117
|
onChange={setAProp("customClass")}
|
|
1112
1118
|
/>
|
|
1113
1119
|
</OrFormula>
|
|
@@ -1120,6 +1126,7 @@ const ContainerSettings = () => {
|
|
|
1120
1126
|
className="text-to-display form-control"
|
|
1121
1127
|
value={customCSS}
|
|
1122
1128
|
onChange={setAProp("customCSS")}
|
|
1129
|
+
spellCheck={false}
|
|
1123
1130
|
></textarea>
|
|
1124
1131
|
</div>
|
|
1125
1132
|
</Accordion>
|
|
@@ -33,19 +33,28 @@ export /**
|
|
|
33
33
|
* @category saltcorn-builder
|
|
34
34
|
* @subcategory components
|
|
35
35
|
*/
|
|
36
|
-
const Image = ({ fileid, block, srctype, url, alt, style }) => {
|
|
36
|
+
const Image = ({ fileid, block, srctype, url, alt, style, customClass }) => {
|
|
37
37
|
const {
|
|
38
38
|
selected,
|
|
39
39
|
connectors: { connect, drag },
|
|
40
40
|
} = useNode((node) => ({ selected: node.events.selected }));
|
|
41
41
|
const theurl = srctype === "File" ? `/files/serve/${fileid}` : url;
|
|
42
42
|
return fileid === 0 ? (
|
|
43
|
-
<span
|
|
43
|
+
<span
|
|
44
|
+
{...blockProps(block)}
|
|
45
|
+
className={`${style && style.width ? "" : "w-100"} ${customClass || ""} image-widget ${
|
|
46
|
+
selected ? "selected-node" : ""
|
|
47
|
+
}`}
|
|
48
|
+
ref={(dom) => connect(drag(dom))}
|
|
49
|
+
style={reactifyStyles(style || {})}
|
|
50
|
+
>
|
|
51
|
+
No images Available
|
|
52
|
+
</span>
|
|
44
53
|
) : (
|
|
45
54
|
<img
|
|
46
55
|
{...blockProps(block)}
|
|
47
56
|
ref={(dom) => connect(drag(dom))}
|
|
48
|
-
className={`${style && style.width ? "" : "w-100"} image-widget ${
|
|
57
|
+
className={`${style && style.width ? "" : "w-100"} ${customClass || ""} image-widget ${
|
|
49
58
|
selected ? "selected-node" : ""
|
|
50
59
|
}`}
|
|
51
60
|
style={reactifyStyles(style || {})}
|
|
@@ -72,6 +81,7 @@ const ImageSettings = () => {
|
|
|
72
81
|
block: node.data.props.block,
|
|
73
82
|
style: node.data.props.style,
|
|
74
83
|
isFormula: node.data.props.isFormula,
|
|
84
|
+
customClass: node.data.props.customClass,
|
|
75
85
|
imgResponsiveWidths: node.data.props.imgResponsiveWidths,
|
|
76
86
|
}));
|
|
77
87
|
const {
|
|
@@ -85,6 +95,7 @@ const ImageSettings = () => {
|
|
|
85
95
|
isFormula,
|
|
86
96
|
filepath,
|
|
87
97
|
imgResponsiveWidths,
|
|
98
|
+
customClass,
|
|
88
99
|
style,
|
|
89
100
|
} = node;
|
|
90
101
|
const options = useContext(optionsCtx);
|
|
@@ -185,6 +196,7 @@ const ImageSettings = () => {
|
|
|
185
196
|
<input
|
|
186
197
|
type="text"
|
|
187
198
|
className="form-control"
|
|
199
|
+
spellCheck={false}
|
|
188
200
|
value={url}
|
|
189
201
|
onChange={setAProp("url")}
|
|
190
202
|
/>
|
|
@@ -262,6 +274,7 @@ const ImageSettings = () => {
|
|
|
262
274
|
<input
|
|
263
275
|
type="text"
|
|
264
276
|
value={imgResponsiveWidths}
|
|
277
|
+
spellCheck={false}
|
|
265
278
|
className="form-control"
|
|
266
279
|
onChange={setAProp("imgResponsiveWidths")}
|
|
267
280
|
/>
|
|
@@ -284,6 +297,18 @@ const ImageSettings = () => {
|
|
|
284
297
|
setProp={setProp}
|
|
285
298
|
isStyle={true}
|
|
286
299
|
/>
|
|
300
|
+
<tr>
|
|
301
|
+
<td>Class</td>
|
|
302
|
+
<td>
|
|
303
|
+
<input
|
|
304
|
+
type="text"
|
|
305
|
+
value={customClass}
|
|
306
|
+
className="form-control"
|
|
307
|
+
onChange={setAProp("customClass")}
|
|
308
|
+
spellCheck={false}
|
|
309
|
+
/>
|
|
310
|
+
</td>
|
|
311
|
+
</tr>
|
|
287
312
|
{srctype !== "Upload" && (
|
|
288
313
|
<tr>
|
|
289
314
|
<td colSpan="2">
|
|
@@ -321,6 +346,7 @@ Image.craft = {
|
|
|
321
346
|
{ name: "srctype", default: "File" },
|
|
322
347
|
{ name: "fileid", default: 0 },
|
|
323
348
|
"field",
|
|
349
|
+
"customClass",
|
|
324
350
|
"block",
|
|
325
351
|
"imgResponsiveWidths",
|
|
326
352
|
{ name: "style", default: {} },
|
|
@@ -190,6 +190,7 @@ const LinkSettings = () => {
|
|
|
190
190
|
<OrFormula nodekey="url" {...{ setProp, isFormula, node }}>
|
|
191
191
|
<input
|
|
192
192
|
type="text"
|
|
193
|
+
spellCheck={false}
|
|
193
194
|
className="form-control "
|
|
194
195
|
value={url}
|
|
195
196
|
onChange={setAProp("url")}
|
|
@@ -269,6 +270,7 @@ const LinkSettings = () => {
|
|
|
269
270
|
<td>
|
|
270
271
|
<input
|
|
271
272
|
type="text"
|
|
273
|
+
spellCheck={false}
|
|
272
274
|
className="form-control"
|
|
273
275
|
value={view_state_fml}
|
|
274
276
|
onChange={setAProp("view_state_fml")}
|
|
@@ -348,8 +350,8 @@ const LinkSettings = () => {
|
|
|
348
350
|
link_src === "Page"
|
|
349
351
|
? url.replace("/page/", `/pageedit/edit/`)
|
|
350
352
|
: link_src === "View"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
+
? url.replace("/view/", `/viewedit/config/`)
|
|
354
|
+
: ""
|
|
353
355
|
}
|
|
354
356
|
>
|
|
355
357
|
Configure this {link_src}
|
|
@@ -121,8 +121,8 @@ const Tabs = ({
|
|
|
121
121
|
(typeof titles[ix].label === "undefined"
|
|
122
122
|
? titles[ix]
|
|
123
123
|
: titles[ix].label === ""
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
? "(empty)"
|
|
125
|
+
: titles[ix].label)}
|
|
126
126
|
</a>
|
|
127
127
|
</li>
|
|
128
128
|
);
|
|
@@ -331,6 +331,7 @@ const TabsSettings = () => {
|
|
|
331
331
|
<td>
|
|
332
332
|
<input
|
|
333
333
|
type="text"
|
|
334
|
+
spellCheck={false}
|
|
334
335
|
className="form-control"
|
|
335
336
|
value={tabId}
|
|
336
337
|
onChange={setAProp("tabId")}
|
|
@@ -398,6 +399,7 @@ const TabsSettings = () => {
|
|
|
398
399
|
type="text"
|
|
399
400
|
className="form-control text-to-display"
|
|
400
401
|
value={titles[use_setting_tab_n] || ""}
|
|
402
|
+
spellCheck={false}
|
|
401
403
|
onChange={(e) => {
|
|
402
404
|
if (!e.target) return;
|
|
403
405
|
const value = e.target.value;
|
|
@@ -417,6 +419,7 @@ const TabsSettings = () => {
|
|
|
417
419
|
<td colSpan={2}>
|
|
418
420
|
<input
|
|
419
421
|
type="text"
|
|
422
|
+
spellCheck={false}
|
|
420
423
|
className="form-control text-to-display"
|
|
421
424
|
value={showif?.[use_setting_tab_n] || ""}
|
|
422
425
|
onChange={(e) => {
|
|
@@ -85,6 +85,7 @@ const Text = ({
|
|
|
85
85
|
icon,
|
|
86
86
|
font,
|
|
87
87
|
style,
|
|
88
|
+
customClass,
|
|
88
89
|
}) => {
|
|
89
90
|
const {
|
|
90
91
|
connectors: { connect, drag },
|
|
@@ -103,7 +104,7 @@ const Text = ({
|
|
|
103
104
|
<div
|
|
104
105
|
className={`${
|
|
105
106
|
isBlock(block, inline, textStyle) ? "d-block" : "d-inline-block"
|
|
106
|
-
} ${Array.isArray(textStyle) ? textStyle.join(" ") : textStyle} is-text ${
|
|
107
|
+
} ${customClass || ""} ${Array.isArray(textStyle) ? textStyle.join(" ") : textStyle} is-text ${
|
|
107
108
|
isFormula.text ? "font-monospace" : ""
|
|
108
109
|
} ${selected ? "selected-node" : ""}`}
|
|
109
110
|
ref={(dom) => connect(drag(dom))}
|
|
@@ -160,6 +161,7 @@ const TextSettings = () => {
|
|
|
160
161
|
isFormula: node.data.props.isFormula,
|
|
161
162
|
textStyle: node.data.props.textStyle,
|
|
162
163
|
labelFor: node.data.props.labelFor,
|
|
164
|
+
customClass: node.data.props.customClass,
|
|
163
165
|
icon: node.data.props.icon,
|
|
164
166
|
font: node.data.props.font,
|
|
165
167
|
style: node.data.props.style,
|
|
@@ -175,6 +177,7 @@ const TextSettings = () => {
|
|
|
175
177
|
icon,
|
|
176
178
|
font,
|
|
177
179
|
style,
|
|
180
|
+
customClass,
|
|
178
181
|
} = node;
|
|
179
182
|
const { mode, fields, icons } = useContext(optionsCtx);
|
|
180
183
|
const setAProp = setAPropGen(setProp);
|
|
@@ -204,6 +207,7 @@ const TextSettings = () => {
|
|
|
204
207
|
className="text-to-display form-control"
|
|
205
208
|
value={text}
|
|
206
209
|
onChange={setAProp("text")}
|
|
210
|
+
spellCheck={false}
|
|
207
211
|
/>
|
|
208
212
|
) : (
|
|
209
213
|
<ErrorBoundary>
|
|
@@ -298,6 +302,18 @@ const TextSettings = () => {
|
|
|
298
302
|
setProp={setProp}
|
|
299
303
|
isStyle={true}
|
|
300
304
|
/>
|
|
305
|
+
<tr>
|
|
306
|
+
<td>Class</td>
|
|
307
|
+
<td>
|
|
308
|
+
<input
|
|
309
|
+
type="text"
|
|
310
|
+
value={customClass}
|
|
311
|
+
className="form-control"
|
|
312
|
+
onChange={setAProp("customClass")}
|
|
313
|
+
spellCheck={false}
|
|
314
|
+
/>
|
|
315
|
+
</td>
|
|
316
|
+
</tr>
|
|
301
317
|
<SettingsRow
|
|
302
318
|
field={{
|
|
303
319
|
name: "color",
|
|
@@ -184,7 +184,7 @@ const ViewLinkSettings = () => {
|
|
|
184
184
|
relationsCache[options.tableName][safeViewName]
|
|
185
185
|
);
|
|
186
186
|
let safeRelation = null;
|
|
187
|
-
if (relation) {
|
|
187
|
+
if (relation && subView) {
|
|
188
188
|
const subView = views.find((view) => view.name === safeViewName);
|
|
189
189
|
const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
|
|
190
190
|
safeRelation = new Relation(
|
|
@@ -315,6 +315,7 @@ const ViewLinkSettings = () => {
|
|
|
315
315
|
className="viewlink-label form-control"
|
|
316
316
|
value={extra_state_fml}
|
|
317
317
|
onChange={setAProp("extra_state_fml")}
|
|
318
|
+
spellCheck={false}
|
|
318
319
|
/>
|
|
319
320
|
{errorString ? (
|
|
320
321
|
<small className="text-danger font-monospace d-block">
|
|
@@ -195,6 +195,7 @@ const OrFormula = ({ setProp, isFormula, node, nodekey, children }) => {
|
|
|
195
195
|
type="text"
|
|
196
196
|
className="form-control text-to-display"
|
|
197
197
|
value={node[nodekey] || ""}
|
|
198
|
+
spellCheck={false}
|
|
198
199
|
onChange={(e) => {
|
|
199
200
|
if (e.target) {
|
|
200
201
|
const target_value = e.target.value;
|
|
@@ -882,10 +883,10 @@ const ConfigField = ({
|
|
|
882
883
|
let stored_value = configuration
|
|
883
884
|
? configuration[field.name]
|
|
884
885
|
: isStyle
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
886
|
+
? props.style[field.name]
|
|
887
|
+
: subProp
|
|
888
|
+
? props[subProp]?.[field.name]
|
|
889
|
+
: props[field.name];
|
|
889
890
|
|
|
890
891
|
let value = or_if_undef(stored_value, field.default);
|
|
891
892
|
if (valuePostfix)
|
|
@@ -955,6 +956,7 @@ const ConfigField = ({
|
|
|
955
956
|
type="text"
|
|
956
957
|
className={`field-${field?.name} form-control`}
|
|
957
958
|
value={value || ""}
|
|
959
|
+
spellCheck={false}
|
|
958
960
|
onChange={(e) => e.target && myOnChange(e.target.value)}
|
|
959
961
|
/>
|
|
960
962
|
);
|
|
@@ -1016,6 +1018,7 @@ const ConfigField = ({
|
|
|
1016
1018
|
type="text"
|
|
1017
1019
|
className={`field-${field?.name} form-control`}
|
|
1018
1020
|
value={value}
|
|
1021
|
+
spellCheck={false}
|
|
1019
1022
|
onChange={(e) => e.target && myOnChange(e.target.value)}
|
|
1020
1023
|
/>
|
|
1021
1024
|
),
|
|
@@ -1035,8 +1038,8 @@ const ConfigField = ({
|
|
|
1035
1038
|
o.name && o.label
|
|
1036
1039
|
? { value: o.name, label: o.label }
|
|
1037
1040
|
: o.value && o.label
|
|
1038
|
-
|
|
1039
|
-
|
|
1041
|
+
? { value: o.value, label: o.label }
|
|
1042
|
+
: { value: o, label: o }
|
|
1040
1043
|
);
|
|
1041
1044
|
return (
|
|
1042
1045
|
<Select
|
|
@@ -1133,8 +1136,8 @@ const ConfigField = ({
|
|
|
1133
1136
|
configuration
|
|
1134
1137
|
? configuration[field.name + "Unit"]
|
|
1135
1138
|
: isStyle || subProp
|
|
1136
|
-
|
|
1137
|
-
|
|
1139
|
+
? styleDim
|
|
1140
|
+
: props[field.name + "Unit"],
|
|
1138
1141
|
"px"
|
|
1139
1142
|
)}
|
|
1140
1143
|
autoable={field.autoable}
|
|
@@ -160,6 +160,7 @@ const layoutToNodes = (
|
|
|
160
160
|
inline={segment.inline || false}
|
|
161
161
|
textStyle={segment.textStyle || ""}
|
|
162
162
|
labelFor={segment.labelFor || ""}
|
|
163
|
+
customClass={segment.customClass || ""}
|
|
163
164
|
style={segment.style || {}}
|
|
164
165
|
icon={segment.icon}
|
|
165
166
|
font={segment.font || ""}
|
|
@@ -580,6 +581,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
|
|
|
580
581
|
textStyle: node.props.textStyle,
|
|
581
582
|
isFormula: node.props.isFormula,
|
|
582
583
|
labelFor: node.props.labelFor,
|
|
584
|
+
customClass: node.props.customClass,
|
|
583
585
|
style: node.props.style,
|
|
584
586
|
icon: node.props.icon,
|
|
585
587
|
font: node.props.font,
|