Skip to content

Commit 3a69ab0

Browse files
committed
refactor: Rewrote Dropdown with react-window style props and row rendering
feat: Added DropdownFrame component for custom dropdown content like previous Dropdown had feat: Exposed Dropdown and DropdownFrame to exts
1 parent 2d7a9c6 commit 3a69ab0

File tree

6 files changed

+281
-139
lines changed

6 files changed

+281
-139
lines changed
Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { DropdownRowProps } from 'flashpoint-launcher-renderer';
12
import { ConfigBox, ConfigBoxProps } from './ConfigBox';
23
import { SelectItem } from './ConfigBoxSelect';
34
import { Dropdown } from './Dropdown';
@@ -19,28 +20,44 @@ export function ConfigBoxMultiSelect<T>(props: ConfigBoxMultiSelectProps<T>) {
1920
// key={props.text}
2021
contentClassName={`${props.contentClassName || ''} setting__row__content--toggle`}>
2122
<div>
22-
<Dropdown
23-
text={props.text}>
24-
{props.items.map((item, idx) => (
25-
<label
26-
key={idx}
27-
className='log-page__dropdown-item'>
28-
<div className='simple-center'>
29-
<input
30-
type='checkbox'
31-
checked={item.checked}
32-
onChange={() => props.onChange(item.value)}
33-
className='simple-center__vertical-inner' />
34-
</div>
35-
<div className='simple-center'>
36-
<p className='simple-center__vertical-inner log-page__dropdown-item-text'>
37-
{item.display || (item.value as any)}
38-
</p>
39-
</div>
40-
</label>
41-
))}
23+
<Dropdown<ConfigBoxMultiSelectRowProps<T>>
24+
text={props.text}
25+
rowProps={{
26+
items: props.items,
27+
onChange: props.onChange
28+
}}
29+
rowCount={props.items.length}
30+
rowRenderer={ConfigBoxMultiSelectRow}>
4231
</Dropdown>
4332
</div>
4433
</ConfigBox>
4534
);
4635
}
36+
37+
type ConfigBoxMultiSelectRowProps<T> = {
38+
items: MultiSelectItem<T>[];
39+
onChange: (item: T) => void;
40+
};
41+
42+
function ConfigBoxMultiSelectRow({ items, onChange, index }: DropdownRowProps<ConfigBoxMultiSelectRowProps<any>>) {
43+
const item = items[index];
44+
45+
return (
46+
<label
47+
key={index}
48+
className='log-page__dropdown-item'>
49+
<div className='simple-center'>
50+
<input
51+
type='checkbox'
52+
checked={item.checked}
53+
onChange={() => onChange(item.value)}
54+
className='simple-center__vertical-inner' />
55+
</div>
56+
<div className='simple-center'>
57+
<p className='simple-center__vertical-inner log-page__dropdown-item-text'>
58+
{item.display || (item.value as any)}
59+
</p>
60+
</div>
61+
</label>
62+
);
63+
}
Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import * as React from 'react';
2-
import { useRef, useState } from 'react';
3-
4-
export type DropdownProps = {
5-
/** Extra class name to add to dropdown frame */
6-
className?: string;
7-
headerClassName?: string;
8-
/** Element(s) to show in the drop-down element (only visible when expanded). */
9-
children: React.ReactNode;
10-
/** Text to show in the text field (always visible). */
11-
text: string;
12-
form?: boolean;
13-
};
1+
import { DropdownFrameProps, DropdownProps, DropdownRowProps } from 'flashpoint-launcher-renderer';
2+
import React, { Activity, useEffect, useMemo, useRef, useState } from 'react';
143

154
// A text element, with a drop-down element that can be shown/hidden.
16-
export function Dropdown(props: DropdownProps) {
5+
export function DropdownFrame({ children, form, text, className, headerClassName }: DropdownFrameProps) {
176
// Hooks
187
const [expanded, setExpanded] = useState<boolean>(false);
198
const dropdownRef = useRef<HTMLDivElement>(null);
@@ -29,39 +18,137 @@ export function Dropdown(props: DropdownProps) {
2918
}
3019
};
3120

32-
React.useEffect(() => {
21+
useEffect(() => {
3322
// Add event listener to handle clicks outside the dropdown
3423
document.addEventListener('mousedown', handleClickOutside);
3524

3625
// Cleanup the event listener on component unmount
3726
return () => {
3827
document.removeEventListener('mousedown', handleClickOutside);
3928
};
40-
});
29+
}, []);
4130

42-
const baseClass = props.form ? 'simple-dropdown-form' : 'simple-dropdown';
31+
const baseClass = form ? 'simple-dropdown-form' : 'simple-dropdown';
4332

4433
// Render
4534
return (
4635
<div
47-
className={`${baseClass} ${props.className}`}
36+
className={`${baseClass} ${className}`}
4837
onClick={onToggleExpanded}>
4938
<div
50-
className={`${baseClass}__select-box ${props.headerClassName}`}
39+
className={`${baseClass}__select-box ${headerClassName}`}
5140
tabIndex={0}>
5241
<div className={`${baseClass}__select-text`}>
53-
{props.text}
42+
{text}
5443
</div>
5544
<div className={`${baseClass}__select-icon`} />
5645
</div>
5746
<div
5847
className={`${baseClass}__content` + (expanded ? '' : ` ${baseClass}__content--hidden`)}
5948
ref={dropdownRef}
6049
onClick={(e) => e.stopPropagation()}>
61-
{expanded && (
62-
props.children
63-
)}
50+
<Activity mode={expanded ? 'visible' : 'hidden'}>
51+
{children}
52+
</Activity>
6453
</div>
6554
</div>
6655
);
6756
}
57+
58+
// A text element, with a drop-down element that can be shown/hidden.
59+
export function Dropdown<T>({ rowCount, rowRenderer: RowRenderer, rowProps, form, text, className, headerClassName }: DropdownProps<T>) {
60+
// Hooks
61+
const [expanded, setExpanded] = useState<boolean>(false);
62+
const dropdownRef = useRef<HTMLDivElement>(null);
63+
64+
const onToggleExpanded = () => {
65+
setExpanded(!expanded);
66+
};
67+
68+
// Close dropdown when clicking outside of it
69+
const handleClickOutside = (event: any) => {
70+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
71+
setExpanded(false);
72+
}
73+
};
74+
75+
useEffect(() => {
76+
// Add event listener to handle clicks outside the dropdown
77+
document.addEventListener('mousedown', handleClickOutside);
78+
79+
// Cleanup the event listener on component unmount
80+
return () => {
81+
document.removeEventListener('mousedown', handleClickOutside);
82+
};
83+
}, []);
84+
85+
const baseClass = form ? 'simple-dropdown-form' : 'simple-dropdown';
86+
87+
const rows = useMemo(() => {
88+
const rows = [];
89+
for (let i = 0; i < rowCount; i++) {
90+
rows.push(
91+
<RowRenderer key={i} index={i} {...rowProps} />
92+
);
93+
}
94+
return rows;
95+
}, [RowRenderer, rowCount, rowProps]);
96+
97+
// Render
98+
return (
99+
<div
100+
className={`${baseClass} ${className}`}
101+
onClick={onToggleExpanded}>
102+
<div
103+
className={`${baseClass}__select-box ${headerClassName}`}
104+
tabIndex={0}>
105+
<div className={`${baseClass}__select-text`}>
106+
{text}
107+
</div>
108+
<div className={`${baseClass}__select-icon`} />
109+
</div>
110+
<div
111+
className={`${baseClass}__content` + (expanded ? '' : ` ${baseClass}__content--hidden`)}
112+
ref={dropdownRef}
113+
onClick={(e) => e.stopPropagation()}>
114+
<Activity mode={expanded ? 'visible' : 'hidden'}>
115+
{rows}
116+
</Activity>
117+
</div>
118+
</div>
119+
);
120+
}
121+
122+
export type DropdownCheckboxRowProps<T> = {
123+
labels: T[],
124+
labelRenderer?: (props: { label: T, index: number }) => React.JSX.Element;
125+
onToggle: (index: number) => void;
126+
isChecked: (index: number) => boolean;
127+
}
128+
129+
export function DropdownCheckboxRow<T>({
130+
labels, labelRenderer: LabelRenderer, onToggle, isChecked, index
131+
}: DropdownRowProps<DropdownCheckboxRowProps<T>>) {
132+
const label = labels[index];
133+
134+
return (
135+
<label
136+
key={index}
137+
className='log-page__dropdown-item'>
138+
<div className='simple-center'>
139+
<input
140+
type='checkbox'
141+
checked={isChecked(index)}
142+
onChange={() => onToggle(index)}
143+
className='simple-center__vertical-inner' />
144+
</div>
145+
<div className='simple-center'>
146+
<p className='simple-center__vertical-inner log-page__dropdown-item-text'>
147+
{LabelRenderer ?
148+
<LabelRenderer label={label} index={index} /> :
149+
String(label)}
150+
</p>
151+
</div>
152+
</label>
153+
);
154+
}

0 commit comments

Comments
 (0)