Skip to content

Commit 39a4385

Browse files
authored
🥷 DualListBox で規定の Checkbox を使わないケースを実現する (#1595)
1 parent b8daab4 commit 39a4385

File tree

7 files changed

+236
-333
lines changed

7 files changed

+236
-333
lines changed

.changeset/small-moons-crash.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ingred-ui": major
3+
---
4+
5+
Hide Checkbox and RemoveButton in DualListBox if no handler is passed

src/components/DualListBox/DualListBox.stories.tsx

+142
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import DualListBox, {
55
DualListBoxItem,
66
DualListBoxProps,
77
} from "./DualListBox";
8+
import { ActionButton, Flex, Icon, Spacer, Typography } from "..";
9+
import { useTheme } from "../../themes";
810

911
export default {
1012
title: "Components/Data Display/DualListBox",
@@ -170,3 +172,143 @@ export const Nested: StoryObj<DualListBoxProps> = {
170172
);
171173
},
172174
};
175+
176+
/**
177+
* 規定の Checkbox による選択・選択解除ではなく
178+
* DualListBoxItem に配置した Button による選択・選択解除を行う
179+
*/
180+
export const WithoutCheckbox: StoryObj<DualListBoxProps> = {
181+
render: () => {
182+
const theme = useTheme();
183+
184+
const items = React.useMemo(
185+
() => [
186+
{
187+
id: "1",
188+
content: "foo",
189+
},
190+
{
191+
id: "2",
192+
content: "bar",
193+
},
194+
{
195+
id: "3",
196+
content: "hoge",
197+
},
198+
],
199+
[],
200+
);
201+
202+
const [allowedIds, setAllowedIds] = React.useState<string[]>([items[2].id]);
203+
204+
const [disallowedIds, setDisallowedIds] = React.useState<string[]>([]);
205+
206+
const handleAllow = (id: string) => {
207+
setAllowedIds((prevState) => {
208+
if (prevState.includes(id)) {
209+
return prevState;
210+
}
211+
return [...prevState, id];
212+
});
213+
};
214+
215+
const handleDisallow = (id: string) => {
216+
setDisallowedIds((prevState) => {
217+
if (prevState.includes(id)) {
218+
return prevState;
219+
}
220+
return [...prevState, id];
221+
});
222+
};
223+
224+
const handleRemove = (item: DualListBoxItem) => {
225+
setAllowedIds((prevState) =>
226+
prevState.filter((selectedId) => selectedId !== item.id),
227+
);
228+
setDisallowedIds((prevState) =>
229+
prevState.filter((selectedId) => selectedId !== item.id),
230+
);
231+
};
232+
233+
const candidateItems = React.useMemo(
234+
() =>
235+
items
236+
.filter(
237+
(item) =>
238+
!allowedIds.includes(item.id) && !disallowedIds.includes(item.id),
239+
)
240+
.map((item) => ({
241+
id: item.id,
242+
content: (
243+
<Flex alignItems="center" display="flex" flex={1} gap={1}>
244+
<Typography>{item.content}</Typography>
245+
<Flex
246+
alignItems="center"
247+
display="flex"
248+
flex={1}
249+
justifyContent="flex-end"
250+
gap={1}
251+
>
252+
<ActionButton
253+
color="primary"
254+
onClick={() => handleAllow(item.id)}
255+
>
256+
<Icon color="active" name="check_thin" />
257+
</ActionButton>
258+
<ActionButton
259+
color="warning"
260+
onClick={() => handleDisallow(item.id)}
261+
>
262+
<Icon color={theme.palette.danger.main} name="forbid" />
263+
</ActionButton>
264+
</Flex>
265+
</Flex>
266+
),
267+
})),
268+
[allowedIds, disallowedIds, items, theme.palette.danger.main],
269+
);
270+
271+
const selectedItems = React.useMemo(
272+
() =>
273+
items
274+
.filter(
275+
(item) =>
276+
allowedIds.includes(item.id) || disallowedIds.includes(item.id),
277+
)
278+
.map((item) => ({
279+
id: item.id,
280+
content: (
281+
<Flex alignItems="center" display="flex" flex={1} gap={1}>
282+
<Typography>{item.content}</Typography>
283+
<Flex
284+
alignItems="center"
285+
display="flex"
286+
flex={1}
287+
justifyContent="flex-end"
288+
gap={1}
289+
>
290+
<Spacer pr={2}>
291+
{allowedIds.includes(item.id) && (
292+
<Icon color="active" name="check_thin" />
293+
)}
294+
{disallowedIds.includes(item.id) && (
295+
<Icon color={theme.palette.danger.main} name="forbid" />
296+
)}
297+
</Spacer>
298+
</Flex>
299+
</Flex>
300+
),
301+
})),
302+
[allowedIds, disallowedIds, items, theme.palette.danger.main],
303+
);
304+
305+
return (
306+
<DualListBox
307+
candidateItems={candidateItems}
308+
disableCheckbox={true}
309+
selectedItems={selectedItems}
310+
onRemove={handleRemove}
311+
/>
312+
);
313+
},
314+
};

src/components/DualListBox/DualListBox.tsx

+6-15
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type DualListBoxCandidateItem = DualListBoxItem & {
1919
export type DualListBoxProps = {
2020
candidateItems: DualListBoxCandidateItem[];
2121
selectedItems: DualListBoxItem[];
22+
disableCheckbox?: boolean;
2223
onAdd?: (item: DualListBoxItem) => void;
2324
onRemove?: (item: DualListBoxItem) => void;
2425
};
@@ -36,8 +37,9 @@ const DualListBox = React.forwardRef<HTMLDivElement, DualListBoxProps>(
3637
candidateItems: candidateItemsProp,
3738
selectedItems,
3839
selectedItemTitle,
39-
onAdd,
40-
onRemove,
40+
disableCheckbox,
41+
onAdd: handleAdd,
42+
onRemove: handleRemove,
4143
} = useLocaleProps({ props: inProps, name: "DualListBox" });
4244

4345
const theme = useTheme();
@@ -47,24 +49,13 @@ const DualListBox = React.forwardRef<HTMLDivElement, DualListBoxProps>(
4749
[candidateItemsProp, selectedItems],
4850
);
4951

50-
const handleAdd = (item: DualListBoxItem) => {
51-
if (onAdd) {
52-
onAdd(item);
53-
}
54-
};
55-
56-
const handleRemove = (item: DualListBoxItem) => {
57-
if (onRemove) {
58-
onRemove(item);
59-
}
60-
};
61-
6252
return (
6353
<Styled.Container>
6454
<CandidateRenderer
55+
disableCheckbox={disableCheckbox}
6556
items={candidateItems}
6657
onAdd={handleAdd}
67-
onRemove={onRemove}
58+
onRemove={handleRemove}
6859
/>
6960
<Divider color={theme.palette.divider} orientation="vertical" />
7061
<SelectedList

src/components/DualListBox/__tests__/DualListBox.test.tsx

+2-18
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,10 @@ describe("DualListBox component testing", () => {
118118
expect(asFragment()).toMatchSnapshot();
119119
});
120120

121-
test("DualListBox candidateItems and selectedItems with inverse", () => {
121+
test("DualListBox candidateItems and selectedItems without checkbox", () => {
122122
const { asFragment } = renderWithThemeProvider(
123123
<DualListBox
124124
candidateItems={[
125-
{
126-
id: "1",
127-
content: "foo",
128-
},
129125
{
130126
id: "2",
131127
content: "bar",
@@ -144,20 +140,8 @@ describe("DualListBox component testing", () => {
144140
id: "1",
145141
content: "foo",
146142
},
147-
{
148-
id: "2",
149-
content: "bar",
150-
},
151-
{
152-
id: "3",
153-
content: "hoge",
154-
},
155-
{
156-
id: "4",
157-
content: "fuga",
158-
},
159143
]}
160-
onAdd={jest.fn()}
144+
disableCheckbox={true}
161145
onRemove={jest.fn()}
162146
/>,
163147
);

0 commit comments

Comments
 (0)