Skip to content

Commit c049659

Browse files
committed
updated dropdown to handle target styles. Synced with select. Finished SuggestInput
1 parent e2eb216 commit c049659

File tree

7 files changed

+509
-312
lines changed

7 files changed

+509
-312
lines changed

components/Dropdown.tsx

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export type DropdownData = { label?: string, value: string };
3030

3131
export type DropdownStates = {
3232
//the current selected option/s
33-
active: boolean
33+
selected: boolean,
34+
//ie. for dropdown or for the control
35+
target: string
3436
};
3537

3638
export type DropdownContextProps = {
@@ -44,12 +46,12 @@ export type DropdownContextProps = {
4446
option?: CallableSlotStyleProp<DropdownStates>,
4547
//toggle handler for dropdown
4648
open: (show: boolean) => void,
47-
//options count
48-
options: number,
4949
//select handler
50-
select: (option: string) => void,
50+
select: (option: string, close?: boolean) => void,
5151
//the current selected option/s
52-
selected: string[]
52+
selected: string[],
53+
//ie. for dropdown or for the control
54+
target: string
5355
};
5456

5557
export type DropdownConfig = {
@@ -76,6 +78,8 @@ export type DropdownConfig = {
7678
value?: string|string[]
7779
};
7880

81+
export type DropdownOptionProp = string[] | DropdownData[] | Record<string, string>;
82+
7983
export type DropdownOptionProps = CallableClassStyleProps<DropdownStates>
8084
& CallableChildrenProps<DropdownStates>
8185
& { value: string };
@@ -101,7 +105,7 @@ export type DropdownProps = ClassStyleProps & ChildrenProps & DropdownConfig & {
101105
//slot: style to apply to the select control
102106
option?: CallableSlotStyleProp<DropdownStates>,
103107
//serialized list of options as array or object
104-
options?: DropdownData[]|Record<string, string>
108+
options?: DropdownOptionProp
105109
};
106110

107111
//--------------------------------------------------------------------//
@@ -112,7 +116,7 @@ export type DropdownProps = ClassStyleProps & ChildrenProps & DropdownConfig & {
112116
*/
113117
export function buildOptions(
114118
children?: ReactNode,
115-
data?: DropdownData[] | Record<string, string>,
119+
data?: DropdownOptionProp,
116120
selected: string[] = [],
117121
multiple?: boolean
118122
) {
@@ -189,10 +193,17 @@ export function getComponent(
189193
for (const child of nodes) {
190194
//skip null/undefined child
191195
if (!child) continue;
192-
//if child is a DropdownHead
193-
if (child.type === component || child.props?.[propName]) {
196+
//if child is a [DropdownHead]
197+
if (child.type === component
198+
|| child.props?.[propName]
199+
|| child[propName]
200+
) {
194201
return child;
195202
}
203+
if (Array.isArray(child)) {
204+
const nested = getComponent(component, propName, child);
205+
if (nested) return nested;
206+
}
196207
}
197208
return null;
198209
};
@@ -202,7 +213,7 @@ export function getComponent(
202213
*/
203214
export function getComponents(
204215
children?: ReactNode,
205-
data?: DropdownData[] | Record<string, string>,
216+
data?: DropdownOptionProp,
206217
selected: string[] = [],
207218
multiple?: boolean
208219
) {
@@ -219,7 +230,7 @@ export function getComponents(
219230
export function getControl(children?: ReactNode) {
220231
return getComponent(
221232
DropdownControl,
222-
'selectDropdownControl',
233+
'dropdownControl',
223234
children
224235
);
225236
};
@@ -230,7 +241,7 @@ export function getControl(children?: ReactNode) {
230241
export function getFooter(children?: ReactNode) {
231242
return getComponent(
232243
DropdownFoot,
233-
'selectDropdownFoot',
244+
'dropdownFoot',
234245
children
235246
);
236247
};
@@ -241,7 +252,7 @@ export function getFooter(children?: ReactNode) {
241252
export function getHeader(children?: ReactNode) {
242253
return getComponent(
243254
DropdownHead,
244-
'selectDropdownHead',
255+
'dropdownHead',
245256
children
246257
);
247258
};
@@ -259,6 +270,7 @@ export function getOptions<T = unknown>(
259270
: children;
260271
const options: ReactNode[] = [];
261272
const selected: ReactNode[] = [];
273+
const unselected: ReactNode[] = [];
262274
const others: ReactNode[] = [];
263275
for (const child of nodes) {
264276
//skip null/undefined child
@@ -271,38 +283,42 @@ export function getOptions<T = unknown>(
271283
const nested = getOptions(child, values, multiple);
272284
options.push(...nested.options);
273285
selected.push(...nested.selected);
286+
unselected.push(...nested.unselected);
274287
others.push(...nested.others);
275288
continue;
276289
}
277290
//if child is a DropdownOption
278-
if (child.type === DropdownOption || child.props?.selectOption) {
291+
if (child.type === DropdownOption
292+
|| child.props?.dropdownOption
293+
|| child.dropdownOption
294+
) {
295+
//either way, add to options list
296+
options.push(child);
279297
//if multiple selections allowed
280298
if (multiple) {
281299
//check if option is selected
282-
if (values.includes(child.props.value)) {
283-
//add to selected
284-
selected.push(child);
285-
} else {
286-
//add to options
287-
options.push(child);
288-
}
300+
values.includes(child.props.value)
301+
//add to selected if so
302+
? selected.push(child)
303+
//add to unselected otherwise
304+
: unselected.push(child);
289305
//single selection
290306
} else if (values.includes(child.props.value)) {
291-
//only add first match
292-
if (selected.length === 0) {
293-
//add to selected
294-
selected.push(child);
295-
}
296-
//dont add to options
307+
//if no selection yet
308+
selected.length === 0
309+
//add to selected if so (only first match)
310+
? selected.push(child)
311+
//add to unselected otherwise
312+
: unselected.push(child);
297313
} else {
298-
//add to options
299-
options.push(child);
314+
//add to unselected
315+
unselected.push(child);
300316
}
301317
continue;
302318
}
303319
others.push(child);
304320
}
305-
return { options, selected, others };
321+
return { options, selected, unselected, others };
306322
};
307323

308324
/**
@@ -350,7 +366,7 @@ export function getRelativePosition(
350366
* Make options from data (array or object)
351367
*/
352368
export function makeOptions(
353-
data: DropdownData[] | Record<string, string>,
369+
data: DropdownOptionProp,
354370
selected: string[],
355371
multiple?: boolean
356372
) {
@@ -367,7 +383,10 @@ export function makeOptions(
367383
const style = { padding: '2px 8px' };
368384
const options: ReactNode[] = [];
369385
for (let i = 0; i < data.length; i++) {
370-
const { label, value } = data[i];
386+
const option = data[i];
387+
const { label, value } = typeof option === 'string'
388+
? { label: option, value: option }
389+
: option;
371390
options.push(
372391
<DropdownOption key={i} value={value} style={style}>
373392
{label || value}
@@ -432,7 +451,7 @@ export function useDropdown(config: DropdownConfig) {
432451
// whether the dropdown is open
433452
const [ opened, open ] = useState(false);
434453
// the current selected option/s
435-
const [ selected, setDropdowned ] = useState(defaultValues);
454+
const [ selected, setSelected ] = useState(defaultValues);
436455
// position of the dropdown (applicable for both relative and absolute dropdowns)
437456
const [ position, setPosition ] = useState<[ number, number ]>([0, 0]);
438457
// max width of the dropdown (should be applicable for absolute dropdowns)
@@ -445,7 +464,7 @@ export function useDropdown(config: DropdownConfig) {
445464
const absolute = Boolean(append);
446465
//handlers
447466
const handlers = {
448-
clear: () => setDropdowned([]),
467+
clear: () => setSelected([]),
449468
open,
450469
portal(dropdown: JSX.Element) {
451470
//if no append selector
@@ -457,26 +476,26 @@ export function useDropdown(config: DropdownConfig) {
457476
if (!container) return null;
458477
return createPortal(dropdown, container);
459478
},
460-
select: (option: string) => {
479+
select: (option: string, close = true) => {
461480
//if multiple selections allowed
462481
if (multiple) {
463482
//if option is already selected, remove it
464483
if (selected.includes(option)) {
465-
const values = selected.filter(s => s !== option);
466-
setDropdowned(values);
484+
const values = selected.filter(value => value !== option);
485+
setSelected(values);
467486
onUpdate && onUpdate(values);
468487
//else add it
469488
} else {
470489
const values = [ ...selected, option ];
471-
setDropdowned(values);
490+
setSelected(values);
472491
onUpdate && onUpdate(values);
473492
}
474493
//single selection
475494
} else {
476-
setDropdowned([ option ]);
495+
setSelected([ option ]);
477496
onUpdate && onUpdate(option);
478497
//close the dropdown
479-
open(false);
498+
close && open(false);
480499
}
481500
}
482501
};
@@ -489,7 +508,7 @@ export function useDropdown(config: DropdownConfig) {
489508
handlers.select(value);
490509
} else if (multiple && Array.isArray(value)) {
491510
//update selected values
492-
setDropdowned(value);
511+
setSelected(value);
493512
}
494513
//trigger update handler
495514
value && onUpdate && onUpdate(value);
@@ -602,12 +621,12 @@ export const DropdownContext = createContext<DropdownContextProps>({
602621
open: () => {},
603622
//whether the dropdown is open
604623
opened: false,
605-
//options count
606-
options: 0,
607624
//select an option
608625
select: () => {},
609626
//the current selected option/s
610-
selected: []
627+
selected: [],
628+
//ie. for dropdown or for the control
629+
target: 'dropdown'
611630
});
612631

613632
/**
@@ -618,11 +637,11 @@ export function DropdownOption(props: DropdownOptionProps) {
618637
//props
619638
const { value, className, style, children } = props;
620639
//hooks
621-
const { select, selected, option } = useDropdownContext();
640+
const { target, select, selected, option } = useDropdownContext();
622641
//variables
623642
const active = selected.includes(value);
624643
// get slot styles
625-
const slot = option ? getSlotStyles(option, { active }) : {};
644+
const slot = option ? getSlotStyles(option, { target, selected: active }) : {};
626645
//get final classes and styles
627646
const { classes, styles } = getClassStyles({
628647
//default classes to apply
@@ -638,14 +657,17 @@ export function DropdownOption(props: DropdownOptionProps) {
638657
style: style || slot.style
639658
},
640659
//state to pass to callable props
641-
state: { active }
660+
state: { target, selected: active }
642661
});
643662
//handlers
644663
const onClick = () => select(value);
645664
//render
646665
return (
647666
<div className={classes.join(' ')} style={styles} onClick={onClick}>
648-
{typeof children === 'function' ? children({ active }) : (children || value)}
667+
{typeof children === 'function'
668+
? children({ target, selected: active })
669+
: (children || value)
670+
}
649671
</div>
650672
);
651673
};
@@ -803,13 +825,16 @@ export function Dropdown(props: DropdownProps) {
803825
top: `${position[1]}px`
804826
};
805827
// get slot styles
806-
const containerStyles = container
807-
? getSlotStyles(container, {})
808-
: {};
809-
containerStyles.className = [
810-
'dropdown-container',
811-
containerStyles.className || ''
812-
].filter(Boolean).join(' ');
828+
const containerStyles = getClassStyles({
829+
//default classes to apply
830+
classes: [ 'dropdown-container' ],
831+
//style props
832+
props: container
833+
? getSlotStyles(container, {})
834+
: {},
835+
//state to pass to callable props
836+
state: {}
837+
});
813838
// determine provider
814839
const components = getComponents(
815840
children,
@@ -821,19 +846,19 @@ export function Dropdown(props: DropdownProps) {
821846
multiple,
822847
opened,
823848
option,
824-
options: components.options.length,
825849
clear: handlers.clear,
826850
open: handlers.open,
827851
select: handlers.select,
828-
selected
852+
selected,
853+
target: 'dropdown'
829854
};
830855
//render
831856
return (
832857
<DropdownContext.Provider value={provider}>
833858
<div
834859
ref={refs.container}
835-
className={containerStyles.className}
836-
style={containerStyles.style}
860+
className={containerStyles.classes.join(' ')}
861+
style={containerStyles.styles}
837862
>
838863
{components.control}
839864
{opened && components.options.length > 0 && handlers.portal(

0 commit comments

Comments
 (0)