Skip to content

Commit 6e7c2bd

Browse files
authored
Merge pull request #85 from headwirecom/bug/52-expandable-table-row
add expandable table row in array control
2 parents c419637 + eeb2b61 commit 6e7c2bd

File tree

7 files changed

+448
-83
lines changed

7 files changed

+448
-83
lines changed

packages/spectrum/src/additional/ListWithDetailMasterItem.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,16 @@
2727
THE SOFTWARE.
2828
*/
2929
import React from 'react';
30-
import { Text, Flex, ActionButton, View } from '@adobe/react-spectrum';
30+
import {
31+
Text,
32+
Flex,
33+
ActionButton,
34+
View,
35+
DialogTrigger,
36+
TooltipTrigger,
37+
Tooltip,
38+
AlertDialog,
39+
} from '@adobe/react-spectrum';
3140
import { StatePropsOfMasterItem } from '@jsonforms/core';
3241
import { withJsonFormsMasterListItemProps } from '@jsonforms/react';
3342
import Delete from '@spectrum-icons/workflow/Delete';
@@ -62,12 +71,24 @@ const ListWithDetailMasterItem = ({
6271
<Text UNSAFE_style={{ textAlign: 'left' }}>{childLabel}</Text>
6372
</ActionButton>
6473
<View>
65-
<ActionButton
66-
aria-label={`delete-item-${childLabel}`}
67-
onPress={removeItem(path, index)}
68-
>
69-
<Delete />
70-
</ActionButton>
74+
<DialogTrigger>
75+
<TooltipTrigger delay={0}>
76+
<ActionButton aria-label={`delete-item-${childLabel}`}>
77+
<Delete />
78+
</ActionButton>
79+
<Tooltip>Delete</Tooltip>
80+
</TooltipTrigger>
81+
<AlertDialog
82+
variant='confirmation'
83+
title='Delete'
84+
primaryActionLabel='Delete'
85+
cancelLabel='Cancel'
86+
autoFocusButton='primary'
87+
onPrimaryAction={removeItem(path, index)}
88+
>
89+
Are you sure you wish to delete this item?
90+
</AlertDialog>
91+
</DialogTrigger>
7192
</View>
7293
</Flex>
7394
</div>

packages/spectrum/src/complex/array/SpectrumArrayControl.tsx

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,36 @@
2626
THE SOFTWARE.
2727
*/
2828
import range from 'lodash/range';
29-
import React from 'react';
30-
import {
31-
ArrayControlProps,
32-
composePaths,
33-
createDefaultValue,
34-
findUISchema,
35-
} from '@jsonforms/core';
36-
import { ResolvedJsonFormsDispatch } from '@jsonforms/react';
29+
import React, { useCallback, useState } from 'react';
30+
import { ArrayControlProps, createDefaultValue } from '@jsonforms/core';
3731
import { Button, Flex, Heading, Text, View } from '@adobe/react-spectrum';
32+
import SpectrumArrayItem from './SpectrumArrayItem';
3833

3934
export const SpectrumArrayControl = ({
4035
data,
4136
label,
4237
path,
4338
schema,
4439
addItem,
40+
removeItems,
4541
uischema,
4642
uischemas,
4743
renderers,
4844
}: ArrayControlProps) => {
45+
const handleRemoveItem = useCallback(
46+
(p: string, value: any) => () => {
47+
removeItems(p, [value])();
48+
},
49+
[removeItems]
50+
);
51+
52+
const [expaned, setExpaned] = useState<number>();
53+
54+
const isExpaned = (index: number) => expaned === index;
55+
56+
const onExpand = (index: number) => () =>
57+
setExpaned((current) => (current === index ? null : index));
58+
4959
return (
5060
<View>
5161
<Flex direction='row' justifyContent='space-between'>
@@ -58,31 +68,28 @@ export const SpectrumArrayControl = ({
5868
+
5969
</Button>
6070
</Flex>
61-
<View>
62-
{data ? (
71+
<Flex direction='column' gap='size-100'>
72+
{data && data.length ? (
6373
range(0, data.length).map((index) => {
64-
const foundUISchema = findUISchema(
65-
uischemas,
66-
schema,
67-
uischema.scope,
68-
path
69-
);
70-
const childPath = composePaths(path, `${index}`);
71-
7274
return (
73-
<ResolvedJsonFormsDispatch
75+
<SpectrumArrayItem
76+
index={index}
77+
path={path}
7478
schema={schema}
75-
uischema={foundUISchema || uischema}
76-
path={childPath}
77-
key={childPath}
79+
handleExpand={onExpand}
80+
removeItem={handleRemoveItem}
81+
expanded={isExpaned(index)}
82+
uischema={uischema}
83+
uischemas={uischemas}
7884
renderers={renderers}
79-
/>
85+
key={index}
86+
></SpectrumArrayItem>
8087
);
8188
})
8289
) : (
8390
<Text>No data</Text>
8491
)}
85-
</View>
92+
</Flex>
8693
</View>
8794
);
8895
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.spectrum-array-item-number {
2+
--spectrum-array-item-number-size: 1.2rem;
3+
4+
display: flex;
5+
align-items: center;
6+
justify-content: center;
7+
width: var(--spectrum-array-item-number-size);
8+
height: var(--spectrum-array-item-number-size);
9+
border-radius: 50%;
10+
overflow: hidden;
11+
line-height: var(--spectrum-array-item-number-size);
12+
font-size: 0.75em;
13+
font-weight: 500;
14+
text-align: center;
15+
background-color: var(--spectrum-global-color-gray-500);
16+
color: var(--spectrum-global-color-gray-200);
17+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
The MIT License
3+
4+
Copyright (c) 2017-2019 EclipseSource Munich
5+
https://github.com/eclipsesource/jsonforms
6+
7+
Copyright (c) 2020 headwire.com, Inc
8+
https://github.com/headwirecom/jsonforms-react-spectrum-renderers
9+
10+
11+
Permission is hereby granted, free of charge, to any person obtaining a copy
12+
of this software and associated documentation files (the "Software"), to deal
13+
in the Software without restriction, including without limitation the rights
14+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15+
copies of the Software, and to permit persons to whom the Software is
16+
furnished to do so, subject to the following conditions:
17+
18+
The above copyright notice and this permission notice shall be included in
19+
all copies or substantial portions of the Software.
20+
21+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27+
THE SOFTWARE.
28+
*/
29+
import React, { ComponentType } from 'react';
30+
import {
31+
Text,
32+
Flex,
33+
ActionButton,
34+
View,
35+
DialogTrigger,
36+
TooltipTrigger,
37+
Tooltip,
38+
AlertDialog,
39+
} from '@adobe/react-spectrum';
40+
import {
41+
composePaths,
42+
ControlElement,
43+
findUISchema,
44+
getData,
45+
JsonFormsRendererRegistryEntry,
46+
JsonFormsState,
47+
JsonSchema,
48+
Resolve,
49+
UISchemaElement,
50+
UISchemaTester,
51+
} from '@jsonforms/core';
52+
import {
53+
areEqual,
54+
JsonFormsStateContext,
55+
ResolvedJsonFormsDispatch,
56+
withJsonFormsContext,
57+
} from '@jsonforms/react';
58+
import Delete from '@spectrum-icons/workflow/Delete';
59+
import ChevronDown from '@spectrum-icons/workflow/ChevronDown';
60+
import ChevronUp from '@spectrum-icons/workflow/ChevronUp';
61+
import { find, omit } from 'lodash';
62+
63+
import './SpectrumArrayItem.css';
64+
65+
export interface OwnPropsOfSpectrumArrayItem {
66+
index: number;
67+
expanded: boolean;
68+
path: string;
69+
schema: JsonSchema;
70+
handleExpand(index: number): () => void;
71+
removeItem(path: string, value: number): () => void;
72+
uischema: ControlElement;
73+
renderers?: JsonFormsRendererRegistryEntry[];
74+
uischemas?: {
75+
tester: UISchemaTester;
76+
uischema: UISchemaElement;
77+
}[];
78+
}
79+
80+
export interface StatePropsOfSpectrumArrayItem
81+
extends OwnPropsOfSpectrumArrayItem {
82+
childLabel: string;
83+
}
84+
85+
const SpectrumArrayItem = ({
86+
index,
87+
childLabel,
88+
expanded,
89+
removeItem,
90+
path,
91+
handleExpand,
92+
schema,
93+
uischema,
94+
uischemas,
95+
renderers,
96+
}: StatePropsOfSpectrumArrayItem) => {
97+
const foundUISchema = findUISchema(uischemas, schema, uischema.scope, path);
98+
const childPath = composePaths(path, `${index}`);
99+
return (
100+
<View
101+
borderWidth='thin'
102+
borderColor='dark'
103+
borderRadius='medium'
104+
padding='size-250'
105+
>
106+
<View aria-selected={expanded}>
107+
<Flex
108+
direction='row'
109+
margin='size-50'
110+
justifyContent='space-between'
111+
alignItems='center'
112+
>
113+
<View UNSAFE_className='spectrum-array-item-number'>
114+
<Text>{index + 1}</Text>
115+
</View>
116+
<ActionButton
117+
flex='auto'
118+
isQuiet
119+
onPress={handleExpand(index)}
120+
aria-label={`expand-item-${childLabel}`}
121+
>
122+
<Text UNSAFE_style={{ textAlign: 'left' }}>{childLabel}</Text>
123+
</ActionButton>
124+
<View>
125+
<ActionButton
126+
onPress={handleExpand(index)}
127+
isQuiet={true}
128+
aria-label={`expand-item-${childLabel}`}
129+
>
130+
{expanded ? <ChevronUp /> : <ChevronDown />}
131+
</ActionButton>
132+
<DialogTrigger>
133+
<TooltipTrigger delay={0}>
134+
<ActionButton
135+
isQuiet={true}
136+
aria-label={`delete-item-${childLabel}`}
137+
>
138+
<Delete />
139+
</ActionButton>
140+
<Tooltip>Delete</Tooltip>
141+
</TooltipTrigger>
142+
<AlertDialog
143+
variant='confirmation'
144+
title='Delete'
145+
primaryActionLabel='Delete'
146+
cancelLabel='Cancel'
147+
autoFocusButton='primary'
148+
onPrimaryAction={removeItem(path, index)}
149+
>
150+
Are you sure you wish to delete this item?
151+
</AlertDialog>
152+
</DialogTrigger>
153+
</View>
154+
</Flex>
155+
</View>
156+
{expanded ? (
157+
<View>
158+
<ResolvedJsonFormsDispatch
159+
schema={schema}
160+
uischema={foundUISchema || uischema}
161+
path={childPath}
162+
key={childPath}
163+
renderers={renderers}
164+
/>
165+
</View>
166+
) : (
167+
''
168+
)}
169+
</View>
170+
);
171+
};
172+
173+
/**
174+
* Map state to control props.
175+
* @param state the store's state
176+
* @param ownProps any own props
177+
* @returns {StatePropsOfControl} state props for a control
178+
*/
179+
export const mapStateToSpectrumArrayItemProps = (
180+
state: JsonFormsState,
181+
ownProps: OwnPropsOfSpectrumArrayItem
182+
): StatePropsOfSpectrumArrayItem => {
183+
const { schema, path, index, uischema } = ownProps;
184+
const firstPrimitiveProp = schema.properties
185+
? find(Object.keys(schema.properties), (propName: any) => {
186+
const prop = schema.properties[propName];
187+
return (
188+
prop.type === 'string' ||
189+
prop.type === 'number' ||
190+
prop.type === 'integer'
191+
);
192+
})
193+
: undefined;
194+
const childPath = composePaths(path, `${index}`);
195+
const childData = Resolve.data(getData(state), childPath);
196+
const childLabel = uischema.options?.elementLabelProp
197+
? childData[uischema.options.elementLabelProp]
198+
: firstPrimitiveProp
199+
? childData[firstPrimitiveProp]
200+
: '';
201+
202+
return {
203+
...ownProps,
204+
childLabel,
205+
};
206+
};
207+
208+
export const ctxToSpectrumArrayItemProps = (
209+
ctx: JsonFormsStateContext,
210+
ownProps: OwnPropsOfSpectrumArrayItem
211+
) => mapStateToSpectrumArrayItemProps({ jsonforms: { ...ctx } }, ownProps);
212+
213+
const withContextToSpectrumArrayItemProps = (
214+
Component: ComponentType<StatePropsOfSpectrumArrayItem>
215+
): ComponentType<OwnPropsOfSpectrumArrayItem> => ({
216+
ctx,
217+
props,
218+
}: JsonFormsStateContext & StatePropsOfSpectrumArrayItem) => {
219+
const stateProps = ctxToSpectrumArrayItemProps(ctx, props);
220+
return <Component {...stateProps} />;
221+
};
222+
223+
export const withJsonFormsSpectrumArrayItemProps = (
224+
Component: ComponentType<StatePropsOfSpectrumArrayItem>
225+
): ComponentType<OwnPropsOfSpectrumArrayItem> =>
226+
withJsonFormsContext(
227+
withContextToSpectrumArrayItemProps(
228+
React.memo(
229+
Component,
230+
(
231+
prevProps: StatePropsOfSpectrumArrayItem,
232+
nextProps: StatePropsOfSpectrumArrayItem
233+
) =>
234+
areEqual(
235+
omit(prevProps, ['handleExpand', 'removeItem']),
236+
omit(nextProps, ['handleExpand', 'removeItem'])
237+
)
238+
)
239+
)
240+
);
241+
242+
export default withJsonFormsSpectrumArrayItemProps(SpectrumArrayItem);

0 commit comments

Comments
 (0)