Skip to content

Commit fc86b17

Browse files
authored
Merge pull request #290 from Alt-Org/SProkopios/enhancement/236-creating-MultiSelectionDropdown
SProkopios/enhancement/236-creating-MultiSelectionDropdown
2 parents b44f62b + 69a4feb commit fc86b17

File tree

6 files changed

+186
-0
lines changed

6 files changed

+186
-0
lines changed

frontend-next-migration/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend-next-migration/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"react-lazy-load-image-component": "^1.6.0",
5959
"react-lite-youtube-embed": "^1.1.55",
6060
"react-modal": "^3.16.1",
61+
"react-multi-select-component": "^4.3.4",
6162
"react-quill": "^2.0.0",
6263
"react-redux": "^8.1.3",
6364
"react-responsive-masonry": "^2.3.0",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
export enum ClanLabel {
2+
ELÄINRAKKAAT = 'eläinrakkaat',
3+
MAAHANMUUTTOMYÖNTEISET = 'maahanmuuttomyönteiset',
4+
LGBTQ = 'lgbtq+',
5+
RAITTIIT = 'raittiit',
6+
KOHTELIAAT = 'kohteliaat',
7+
KIUSAAMISENVASTAISET = 'kiusaamisenvastaiset',
8+
URHEILEVAT = 'urheilevat',
9+
SYVÄLLISET = 'syvälliset',
10+
OIKEUDENMUKAISET = 'oikeudenmukaiset',
11+
KAIKKIEN_KAVERIT = 'kaikkien kaverit',
12+
ITSENÄISET = 'itsenäiset',
13+
RETKEILIJÄT = 'retkeilijät',
14+
SUOMENRUOTSALAISET = 'suomenruotsalaiset',
15+
HUUMORINTAJUISET = 'huumorintajuiset',
16+
RIKKAAT = 'rikkaat',
17+
IKITEINIT = 'ikiteinit',
18+
JUORUILEVAT = 'juoruilevat',
19+
RAKASTAVAT = 'rakastavat',
20+
OLEILIJAT = 'oleilijat',
21+
NÖRTIT = 'nörtit',
22+
MUSADIGGARIT = 'musadiggarit',
23+
TUNTEELLISET = 'tunteelliset',
24+
GAMERIT = 'gamerit',
25+
ANIMEFANIT = 'animefanit',
26+
SINKUT = 'sinkut',
27+
MONIKULTTUURISET = 'monikulttuuriset',
28+
KAUNIIT = 'kauniit',
29+
JÄRJESTELMÄLLISET = 'järjestelmälliset',
30+
EPÄJÄRJESTELMÄLLISET = 'epäjärjestelmälliset',
31+
TASA_ARVOISET = 'tasa-arvoiset',
32+
SOMEPERSOONAT = 'somepersoonat',
33+
KÄDENTAITAJAT = 'kädentaitajat',
34+
MUUSIKOT = 'muusikot',
35+
TAITEILIJAT = 'taiteilijat',
36+
SPÄMMÄÄJÄT = 'spämmääjät',
37+
KASVISSYÖJÄT = 'kasvissyöjät',
38+
TASAPAINOISET = 'tasapainoiset',
39+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ComponentStory, ComponentMeta } from '@storybook/react';
2+
import { NewClanForm } from './NewClanForm';
3+
4+
export default {
5+
title: 'features/NewClanForm',
6+
component: NewClanForm,
7+
args: {
8+
className: '',
9+
},
10+
} as ComponentMeta<typeof NewClanForm>;
11+
12+
const Template: ComponentStory<typeof NewClanForm> = (args) => {
13+
return <NewClanForm {...args} />;
14+
};
15+
16+
export const Default = Template.bind({});

frontend-next-migration/src/shared/ui/CustomForm/ui/CustomForm.stories.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Meta, StoryObj } from '@storybook/react';
22
import MemoizedForm from './CustomForm';
3+
import { useState } from 'react';
4+
import { ClanLabel } from '@/entities/Clan/enum/clanLabel.enum';
5+
36
const meta = {
47
title: 'shared/ui/CustomForm',
58
component: MemoizedForm,
@@ -96,3 +99,20 @@ export const FormCheckbox: Story = {
9699
children: <MemoizedForm.Checkbox label="I agree to the terms" />,
97100
},
98101
};
102+
103+
export const MultiSelectionDropdown: Story = {
104+
render: () => {
105+
const [selected, setSelected] = useState<{ label: any; value: any }[]>([]);
106+
107+
return (
108+
<MemoizedForm.MultiSelectionDropdown
109+
label="Labels"
110+
options={ClanLabel}
111+
defaultSelected={{ ITSENÄISET: ClanLabel.ITSENÄISET }}
112+
maxSelections={5}
113+
value={selected}
114+
onSelectChange={(newSelection) => setSelected(newSelection)}
115+
/>
116+
);
117+
},
118+
};

frontend-next-migration/src/shared/ui/CustomForm/ui/CustomForm.tsx

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
'use client';
12
import React, {
23
ReactNode,
34
HTMLAttributes,
@@ -7,10 +8,13 @@ import React, {
78
memo,
89
DetailedHTMLProps,
910
InputHTMLAttributes,
11+
useState,
12+
useEffect,
1013
} from 'react';
1114
import { Button as CustomButton, ButtonTheme } from '@/shared/ui/Button/Button';
1215
import { classNames } from '@/shared/lib/classNames/classNames';
1316
import cls from './CustomForm.module.scss';
17+
import { MultiSelect } from 'react-multi-select-component';
1418

1519
/**
1620
* Header component for displaying a heading inside the form.
@@ -132,13 +136,107 @@ function Button({ children, className = '', ...props }: ButtonProps) {
132136
);
133137
}
134138

139+
/**
140+
* MultiSelectionDropdown component for rendering a multi-selection dropdown.
141+
*
142+
*
143+
* @param {MultiSelectionFieldProps} props - The properties for the MultiSelectionDropdown.
144+
* options accepts Record<string, string> as a prop type and function convert it to { label: any; value: T }[] -form for MultiSelectionDropdown
145+
*
146+
* @returns {JSX.Element} - The rendered multiselection component.
147+
*
148+
* @example
149+
* const [selected, setSelected] = useState<{ label: any; value: any }[]>([]);
150+
*
151+
* <MemoizedForm.MultiSelectionDropdown
152+
label='Clan labels'
153+
options={ClanLabel}
154+
defaultSelected={{ ITSENÄISET : ClanLabel.ITSENÄISET }}
155+
maxSelections={5}
156+
value={selected}
157+
onSelectChange={(newSelection) => setSelected(newSelection)}
158+
/>
159+
*/
160+
161+
type MultiSelectionFieldProps<T> = {
162+
label: string;
163+
className?: string;
164+
inputProps?: DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
165+
options: Record<any, any>;
166+
onSelectChange: (selected: { label: any; value: T }[]) => void;
167+
error?: any;
168+
maxSelections?: number;
169+
defaultSelected?: Record<any, any>;
170+
value: { label: any; value: T }[];
171+
};
172+
173+
function MultiSelectionDropdown<T>({
174+
label,
175+
error,
176+
maxSelections,
177+
inputProps,
178+
className = '',
179+
value,
180+
defaultSelected,
181+
options,
182+
onSelectChange,
183+
}: MultiSelectionFieldProps<T>) {
184+
const inputId = inputProps?.id || `multiselect-${label}`;
185+
const [selectionError, setSelectionError] = useState<string | null>(null);
186+
187+
const formattedOptions = Object.entries(options).map(([key, value]) => ({
188+
label: value,
189+
value: key,
190+
}));
191+
192+
useEffect(() => {
193+
if (defaultSelected && value.length === 0) {
194+
const refactoredDefaults = Object.entries(defaultSelected).map(([key, value]) => ({
195+
label: value,
196+
value: key as T,
197+
}));
198+
onSelectChange(refactoredDefaults);
199+
}
200+
}, [defaultSelected, onSelectChange]);
201+
202+
const selectionLogic = (newSelection: { label: any; value: T }[]) => {
203+
if (maxSelections && newSelection.length > maxSelections) {
204+
setSelectionError(`Maximum of ${maxSelections} selections!`);
205+
} else {
206+
setSelectionError(null);
207+
onSelectChange(newSelection);
208+
}
209+
};
210+
211+
return (
212+
<div className={classNames(cls.field, {}, [className])}>
213+
<label htmlFor={inputId}>{label}</label>
214+
{(error || selectionError) && (
215+
<p
216+
role="alert"
217+
className={cls.error}
218+
>
219+
{error || selectionError}
220+
</p>
221+
)}
222+
<MultiSelect
223+
options={formattedOptions}
224+
value={value}
225+
onChange={selectionLogic}
226+
labelledBy={label}
227+
/>
228+
</div>
229+
);
230+
}
231+
135232
interface IFormProps extends HTMLAttributes<HTMLFormElement> {}
136233

137234
interface MemoizedFormCompose {
138235
Button: typeof Button;
139236
Header: typeof Header;
140237
InputField: typeof InputField;
141238
Checkbox: typeof Checkbox;
239+
MultiSelectionDropdown: typeof MultiSelectionDropdown;
142240
}
143241

144242
/**
@@ -173,5 +271,6 @@ MemoizedForm.Button = Button;
173271
MemoizedForm.Header = Header;
174272
MemoizedForm.InputField = InputField;
175273
MemoizedForm.Checkbox = Checkbox;
274+
MemoizedForm.MultiSelectionDropdown = MultiSelectionDropdown;
176275

177276
export default MemoizedForm;

0 commit comments

Comments
 (0)