Skip to content

Commit 5b6976b

Browse files
refactor: improved inline component experience (#893)
* feat: improved inline component experience * updated name
1 parent 79d3b31 commit 5b6976b

File tree

3 files changed

+103
-75
lines changed

3 files changed

+103
-75
lines changed

packages/kit-headless/src/components/accordion/accordion-inline.tsx

Lines changed: 19 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,40 @@
1-
import { Component, JSXNode, PropsOf, QRL, Signal } from '@builder.io/qwik';
2-
import { HAccordionRootImpl } from './accordion-root';
1+
import { Component } from '@builder.io/qwik';
2+
import { type AccordionRootProps, HAccordionRootImpl } from './accordion-root';
33
import { Accordion } from '@qwik-ui/headless';
4+
import { findComponent, processChildren } from '../../utils/inline-component';
45

56
type InternalProps = {
67
accordionItemComponent?: typeof Accordion.Item;
78
};
89

9-
export type AccordionRootProps = PropsOf<'div'> & {
10-
/** If true, multiple items can be open at the same time. */
11-
multiple?: boolean;
12-
13-
/**
14-
* @deprecated Use the multiple prop instead.
15-
*/
16-
behavior?: 'single' | 'multi';
17-
18-
/** The reactive value controlling which item is open. */
19-
'bind:value'?: Signal<string | null>;
20-
21-
/** The initial value of the currently open item. */
22-
value?: string;
23-
24-
/** The initial index of the currently open item. */
25-
initialIndex?: number;
26-
27-
/** A QRL that is called when the selected item changes. */
28-
onChange$?: QRL<(value: string) => void>;
29-
30-
/** A map of the item indexes and their disabled state. */
31-
itemsMap?: Map<number, boolean>;
32-
33-
/** If true, the accordion is disabled. */
34-
disabled?: boolean;
35-
36-
/** If true, the accordion is collapsible. */
37-
collapsible?: boolean;
38-
39-
/** If true, the accordion is animated. */
40-
animated?: boolean;
41-
};
42-
4310
export const HAccordionRoot: Component<AccordionRootProps & InternalProps> = (
4411
props: AccordionRootProps & InternalProps,
4512
) => {
46-
const { children: accordionChildren, accordionItemComponent, ...rest } = props;
47-
13+
const {
14+
children,
15+
accordionItemComponent: GivenItem,
16+
value: initialValue,
17+
...rest
18+
} = props;
19+
const Item = GivenItem || Accordion.Item;
4820
let currItemIndex = 0;
4921
let initialIndex = null;
5022
const itemsMap = new Map();
5123

52-
const InternalItemComponent = accordionItemComponent || Accordion.Item;
53-
const childrenToProcess = (
54-
Array.isArray(accordionChildren) ? [...accordionChildren] : [accordionChildren]
55-
) as Array<JSXNode>;
56-
57-
while (childrenToProcess.length) {
58-
const child = childrenToProcess.shift();
24+
// code executes when the item component's shell is "seen"
25+
findComponent(Item, (itemProps) => {
26+
itemProps._index = currItemIndex;
5927

60-
if (!child) {
61-
continue;
62-
}
28+
itemsMap.set(currItemIndex, itemProps.disabled);
6329

64-
if (Array.isArray(child)) {
65-
childrenToProcess.unshift(...child);
66-
continue;
30+
if (initialValue && initialValue === itemProps.value) {
31+
initialIndex = currItemIndex;
6732
}
6833

69-
switch (child.type) {
70-
case InternalItemComponent: {
71-
child.props._index = currItemIndex;
72-
if (props.value !== undefined && props.value === child.props.value) {
73-
initialIndex = currItemIndex;
74-
}
75-
itemsMap.set(currItemIndex, child.props.disabled === true);
76-
77-
currItemIndex++;
78-
break;
79-
}
34+
currItemIndex++;
35+
});
8036

81-
default: {
82-
if (child) {
83-
const anyChildren = Array.isArray(child.children)
84-
? [...child.children]
85-
: [child.children];
86-
childrenToProcess.unshift(...(anyChildren as JSXNode[]));
87-
}
88-
89-
break;
90-
}
91-
}
92-
}
37+
processChildren(children);
9338

9439
return (
9540
<HAccordionRootImpl

packages/kit-headless/src/components/accordion/accordion-root.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
PropsOf,
3+
QRL,
24
Signal,
35
Slot,
46
component$,
@@ -7,9 +9,42 @@ import {
79
useSignal,
810
useTask$,
911
} from '@builder.io/qwik';
10-
import { AccordionRootProps } from './accordion-inline';
1112
import { accordionContextId } from './accordion-context';
1213

14+
export type AccordionRootProps = PropsOf<'div'> & {
15+
/** If true, multiple items can be open at the same time. */
16+
multiple?: boolean;
17+
18+
/**
19+
* @deprecated Use the multiple prop instead.
20+
*/
21+
behavior?: 'single' | 'multi';
22+
23+
/** The reactive value controlling which item is open. */
24+
'bind:value'?: Signal<string | null>;
25+
26+
/** The initial value of the currently open item. */
27+
value?: string;
28+
29+
/** The initial index of the currently open item. */
30+
initialIndex?: number;
31+
32+
/** A QRL that is called when the selected item changes. */
33+
onChange$?: QRL<(value: string) => void>;
34+
35+
/** A map of the item indexes and their disabled state. */
36+
itemsMap?: Map<number, boolean>;
37+
38+
/** If true, the accordion is disabled. */
39+
disabled?: boolean;
40+
41+
/** If true, the accordion is collapsible. */
42+
collapsible?: boolean;
43+
44+
/** If true, the accordion is animated. */
45+
animated?: boolean;
46+
};
47+
1348
export const HAccordionRootImpl = component$((props: AccordionRootProps) => {
1449
const {
1550
multiple,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { JSXChildren, JSXNode } from '@builder.io/qwik';
2+
3+
/**
4+
*
5+
* This function allows us to process the children of an inline component. We can look into the children and get the proper index, pass data, or make certain API decisions.
6+
*
7+
* See accordion-inline.tsx for a usage example.
8+
*
9+
* @param children
10+
*
11+
*/
12+
export function processChildren(children: JSXChildren) {
13+
const childrenToProcess = (
14+
Array.isArray(children) ? [...children] : children ? [children] : []
15+
) as Array<JSXNode>;
16+
17+
while (childrenToProcess.length) {
18+
const child = childrenToProcess.shift();
19+
20+
if (!child) continue;
21+
22+
if (Array.isArray(child)) {
23+
childrenToProcess.unshift(...child);
24+
continue;
25+
}
26+
27+
const processor = componentRegistry.get(child.type);
28+
29+
if (processor) {
30+
processor(child.props);
31+
} else {
32+
const anyChildren = Array.isArray(child.children)
33+
? [...child.children]
34+
: [child.children];
35+
childrenToProcess.unshift(...(anyChildren as JSXNode[]));
36+
}
37+
}
38+
}
39+
40+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
41+
const componentRegistry = new Map<any, ComponentProcessor>();
42+
43+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
44+
export function findComponent(component: any, processor: ComponentProcessor) {
45+
componentRegistry.set(component, processor);
46+
}
47+
48+
type ComponentProcessor = (props: Record<string, unknown>) => void;

0 commit comments

Comments
 (0)