Skip to content

Commit 25088a1

Browse files
authored
enhance Search widget to accept an object as default value (#1092)
1 parent 26b150e commit 25088a1

File tree

5 files changed

+139
-25
lines changed

5 files changed

+139
-25
lines changed

.changeset/thirty-kings-prove.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ensembleui/react-kitchen-sink": patch
3+
"@ensembleui/react-runtime": patch
4+
---
5+
6+
enhance Search widget to accept an object as default value

apps/kitchen-sink/src/ensemble/screens/layouts.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ View:
2020
header:
2121
title:
2222
Header:
23-
23+
2424
menu:
2525
Drawer:
2626
id: testDrawer
@@ -36,25 +36,25 @@ View:
3636
label: Navigate Home
3737
onTap:
3838
executeCode: |
39-
testDrawer.setCollapsed(true);
39+
testDrawer.setIsCollapsed(true);
4040
ensemble.navigateScreen("Home");
4141
- Button:
4242
label: Navigate to Widgets
4343
onTap:
4444
executeCode: |
45-
testDrawer.setCollapsed(true);
45+
testDrawer.setIsCollapsed(true);
4646
ensemble.navigateScreen("Widgets");
4747
- Button:
4848
label: Navigate to Layouts
4949
onTap:
5050
executeCode: |
51-
testDrawer.setCollapsed(true);
51+
testDrawer.setIsCollapsed(true);
5252
ensemble.navigateScreen("Layouts");
5353
- Button:
5454
label: Close Drawer
5555
onTap:
5656
executeCode: |
57-
testDrawer.setCollapsed(true);
57+
testDrawer.setIsCollapsed(true);
5858
5959
body:
6060
Column:
@@ -151,18 +151,18 @@ View:
151151
label: 2
152152
- Button:
153153
label: FittedColumn
154-
154+
155155
- Markdown:
156156
text: |
157157
## Drawer
158158
159-
Click the button below to open the drawer in the current view. You can place any widgets you would like in the drawer. The drawer can be opened and closed dynamically using the `setCollapsed` method.
159+
Click the button below to open the drawer in the current view. You can place any widgets you would like in the drawer. The drawer can be opened and closed dynamically using the `setIsCollapsed` method.
160160
161161
- Button:
162162
label: open drawer
163163
onTap:
164164
executeCode: |
165-
testDrawer.setCollapsed(false);
165+
testDrawer.setIsCollapsed(false);
166166
167167
- Text:
168168
styles:

apps/kitchen-sink/src/ensemble/screens/menu.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,4 @@ ViewGroup:
114114
name: ArrowBack
115115
onTap:
116116
executeCode: |
117-
sidebar.setCollapsed(!sidebar.isCollapsed)
117+
sidebar.setIsCollapsed(!sidebar.isCollapsed)

apps/kitchen-sink/src/ensemble/screens/widgets.yaml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,60 @@ View:
12951295
onRefresh:
12961296
executeCode: |
12971297
console.log('refreshed')
1298+
- Row:
1299+
children:
1300+
- Search:
1301+
id: searchDefaultObject
1302+
value: # assuming this data is coming from server
1303+
id: 105
1304+
firstName: Emma
1305+
lastName: Wilson
1306+
styles:
1307+
width: 320px
1308+
height: 40px
1309+
borderRadius: 12
1310+
borderWidth: 2
1311+
borderStyle: solid
1312+
borderColor: "#B8BED6"
1313+
placeholder: Search
1314+
item-template:
1315+
data: |-
1316+
${findUsers.body.users.map(user => ({id: user.id, firstName: user.firstName, lastName: user.lastName}))}
1317+
name: user
1318+
template:
1319+
SearchText:
1320+
inputs:
1321+
user: ${user}
1322+
selectedLabel:
1323+
SearchText:
1324+
inputs:
1325+
user: ${value.user}
1326+
searchKey: id
1327+
onSearch:
1328+
invokeAPI:
1329+
name: findUsers
1330+
inputs:
1331+
search: ${search}
1332+
onSelect:
1333+
executeCode: |
1334+
console.log(value)
1335+
onClear:
1336+
executeCode: |
1337+
console.log("Search cleared")
1338+
- Text:
1339+
text: ${JSON.stringify(searchDefaultObject.value)}
1340+
- Text:
1341+
text: ${JSON.stringify(findUsers.body.users.map((user) => user.id))}
1342+
1343+
SearchText:
1344+
inputs:
1345+
- user
1346+
body:
1347+
Text:
1348+
text: ${user.firstName} ${user.lastName}
1349+
styles:
1350+
color: red
1351+
12981352
API:
12991353
getDummyProducts:
13001354
method: GET

packages/runtime/src/widgets/Search.tsx

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { Icon } from "./Icon";
2626
const widgetName = "Search";
2727

2828
export type SearchProps = {
29-
value?: string;
29+
value?: string | { [key: string]: unknown };
3030
placeholder?: string;
3131
searchKey?: string;
3232
selectedLabel?: { [key: string]: unknown };
@@ -58,6 +58,7 @@ export const Search: React.FC<SearchProps> = ({
5858
}) => {
5959
const [searchValue, setSearchValue] = useState<string | null>(null);
6060
const [value, setValue] = useState<unknown>();
61+
const [selectedObject, setSelectedObject] = useState<unknown>(null);
6162

6263
const { namedData } = useTemplateData({
6364
data: itemTemplate?.data,
@@ -143,8 +144,14 @@ export const Search: React.FC<SearchProps> = ({
143144
(option) => extractValue(option) === selectedValue,
144145
);
145146

147+
const fullSelectedObject = get(selectedOption, [
148+
itemTemplate.name,
149+
]) as unknown;
150+
// store full object separately only for selectedLabel rendering
151+
setSelectedObject(fullSelectedObject);
152+
146153
onSelectAction?.callback({
147-
value: get(selectedOption, [itemTemplate.name]) as unknown,
154+
value: fullSelectedObject,
148155
});
149156
}
150157
},
@@ -153,6 +160,8 @@ export const Search: React.FC<SearchProps> = ({
153160

154161
const handleClear = useCallback(() => {
155162
setSearchValue(null);
163+
setValue(null);
164+
setSelectedObject(null);
156165
onClearAction?.callback();
157166
}, [onClearAction?.callback]);
158167

@@ -169,27 +178,72 @@ export const Search: React.FC<SearchProps> = ({
169178
: EnsembleRuntime.render([unwrapWidget(notFoundContent)]);
170179
}, [values?.notFoundContent]);
171180

181+
const selectDefaultValue = useMemo(() => {
182+
if (!values?.initialValue) return undefined;
183+
184+
if (isObject(values.initialValue)) {
185+
const extractedValue = extractValue({
186+
[itemTemplate?.name ?? ""]: values.initialValue,
187+
});
188+
return String(extractedValue);
189+
}
190+
191+
return String(values.initialValue);
192+
}, [values?.initialValue, extractValue, itemTemplate?.name]);
193+
172194
const renderLabel = useCallback(
173195
(label: React.ReactNode, labelValue: string | number): React.ReactNode => {
174-
if (isNil(rest.selectedLabel) || isEmpty(namedData)) {
175-
return label;
196+
// if we have selectedLabel and a selected object (from selection or initial object value)
197+
if (!isNil(rest.selectedLabel) && selectedObject) {
198+
return (
199+
<CustomScopeProvider
200+
value={{ value: { [itemTemplate?.name ?? ""]: selectedObject } }}
201+
>
202+
{EnsembleRuntime.render([unwrapWidget(rest.selectedLabel)])}
203+
</CustomScopeProvider>
204+
);
176205
}
177206

178-
const option = namedData.find(
179-
(item) => extractValue(item) === labelValue,
180-
);
181-
return (
182-
<CustomScopeProvider value={{ value: option }}>
183-
{EnsembleRuntime.render([unwrapWidget(rest.selectedLabel)])}
184-
</CustomScopeProvider>
185-
);
207+
// if we have selectedLabel but no selectedObject, try to find from namedData
208+
if (!isNil(rest.selectedLabel) && !isEmpty(namedData)) {
209+
const option = namedData.find(
210+
(item) => extractValue(item) === labelValue,
211+
);
212+
if (option) {
213+
return (
214+
<CustomScopeProvider value={{ value: option }}>
215+
{EnsembleRuntime.render([unwrapWidget(rest.selectedLabel)])}
216+
</CustomScopeProvider>
217+
);
218+
}
219+
}
220+
221+
return label;
186222
},
187-
[extractValue, namedData, rest.selectedLabel],
223+
[
224+
extractValue,
225+
itemTemplate?.name,
226+
namedData,
227+
rest.selectedLabel,
228+
selectedObject,
229+
],
188230
);
189231

190232
useEffect(() => {
191-
if (!value) setValue(values?.initialValue);
192-
}, [value, values?.initialValue]);
233+
if (!value && values?.initialValue) {
234+
if (isObject(values.initialValue)) {
235+
// if initial value is an object, extract the key for widget value and store full object
236+
const extractedValue = extractValue({
237+
[itemTemplate?.name ?? ""]: values.initialValue,
238+
});
239+
setValue(extractedValue);
240+
setSelectedObject(values.initialValue);
241+
} else {
242+
// if initial value is primitive, use it directly
243+
setValue(values.initialValue);
244+
}
245+
}
246+
}, [value, values?.initialValue, extractValue, itemTemplate?.name]);
193247

194248
return (
195249
<div
@@ -220,7 +274,7 @@ export const Search: React.FC<SearchProps> = ({
220274
<SelectComponent
221275
allowClear
222276
className={`${values?.styles?.names || ""} ${id}_input`}
223-
defaultValue={values?.initialValue}
277+
defaultValue={selectDefaultValue}
224278
filterOption={false}
225279
id={values?.id}
226280
labelRender={({ label, value: labelValue }): React.ReactNode =>

0 commit comments

Comments
 (0)