Skip to content

Commit

Permalink
refactor: improved inline component experience (#893)
Browse files Browse the repository at this point in the history
* feat: improved inline component experience

* updated name
  • Loading branch information
thejackshelton authored Jul 19, 2024
1 parent 79d3b31 commit 5b6976b
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 75 deletions.
93 changes: 19 additions & 74 deletions packages/kit-headless/src/components/accordion/accordion-inline.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,40 @@
import { Component, JSXNode, PropsOf, QRL, Signal } from '@builder.io/qwik';
import { HAccordionRootImpl } from './accordion-root';
import { Component } from '@builder.io/qwik';
import { type AccordionRootProps, HAccordionRootImpl } from './accordion-root';
import { Accordion } from '@qwik-ui/headless';
import { findComponent, processChildren } from '../../utils/inline-component';

type InternalProps = {
accordionItemComponent?: typeof Accordion.Item;
};

export type AccordionRootProps = PropsOf<'div'> & {
/** If true, multiple items can be open at the same time. */
multiple?: boolean;

/**
* @deprecated Use the multiple prop instead.
*/
behavior?: 'single' | 'multi';

/** The reactive value controlling which item is open. */
'bind:value'?: Signal<string | null>;

/** The initial value of the currently open item. */
value?: string;

/** The initial index of the currently open item. */
initialIndex?: number;

/** A QRL that is called when the selected item changes. */
onChange$?: QRL<(value: string) => void>;

/** A map of the item indexes and their disabled state. */
itemsMap?: Map<number, boolean>;

/** If true, the accordion is disabled. */
disabled?: boolean;

/** If true, the accordion is collapsible. */
collapsible?: boolean;

/** If true, the accordion is animated. */
animated?: boolean;
};

export const HAccordionRoot: Component<AccordionRootProps & InternalProps> = (
props: AccordionRootProps & InternalProps,
) => {
const { children: accordionChildren, accordionItemComponent, ...rest } = props;

const {
children,
accordionItemComponent: GivenItem,
value: initialValue,
...rest
} = props;
const Item = GivenItem || Accordion.Item;
let currItemIndex = 0;
let initialIndex = null;
const itemsMap = new Map();

const InternalItemComponent = accordionItemComponent || Accordion.Item;
const childrenToProcess = (
Array.isArray(accordionChildren) ? [...accordionChildren] : [accordionChildren]
) as Array<JSXNode>;

while (childrenToProcess.length) {
const child = childrenToProcess.shift();
// code executes when the item component's shell is "seen"
findComponent(Item, (itemProps) => {
itemProps._index = currItemIndex;

if (!child) {
continue;
}
itemsMap.set(currItemIndex, itemProps.disabled);

if (Array.isArray(child)) {
childrenToProcess.unshift(...child);
continue;
if (initialValue && initialValue === itemProps.value) {
initialIndex = currItemIndex;
}

switch (child.type) {
case InternalItemComponent: {
child.props._index = currItemIndex;
if (props.value !== undefined && props.value === child.props.value) {
initialIndex = currItemIndex;
}
itemsMap.set(currItemIndex, child.props.disabled === true);

currItemIndex++;
break;
}
currItemIndex++;
});

default: {
if (child) {
const anyChildren = Array.isArray(child.children)
? [...child.children]
: [child.children];
childrenToProcess.unshift(...(anyChildren as JSXNode[]));
}

break;
}
}
}
processChildren(children);

return (
<HAccordionRootImpl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {
PropsOf,
QRL,
Signal,
Slot,
component$,
Expand All @@ -7,9 +9,42 @@ import {
useSignal,
useTask$,
} from '@builder.io/qwik';
import { AccordionRootProps } from './accordion-inline';
import { accordionContextId } from './accordion-context';

export type AccordionRootProps = PropsOf<'div'> & {
/** If true, multiple items can be open at the same time. */
multiple?: boolean;

/**
* @deprecated Use the multiple prop instead.
*/
behavior?: 'single' | 'multi';

/** The reactive value controlling which item is open. */
'bind:value'?: Signal<string | null>;

/** The initial value of the currently open item. */
value?: string;

/** The initial index of the currently open item. */
initialIndex?: number;

/** A QRL that is called when the selected item changes. */
onChange$?: QRL<(value: string) => void>;

/** A map of the item indexes and their disabled state. */
itemsMap?: Map<number, boolean>;

/** If true, the accordion is disabled. */
disabled?: boolean;

/** If true, the accordion is collapsible. */
collapsible?: boolean;

/** If true, the accordion is animated. */
animated?: boolean;
};

export const HAccordionRootImpl = component$((props: AccordionRootProps) => {
const {
multiple,
Expand Down
48 changes: 48 additions & 0 deletions packages/kit-headless/src/utils/inline-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { JSXChildren, JSXNode } from '@builder.io/qwik';

/**
*
* 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.
*
* See accordion-inline.tsx for a usage example.
*
* @param children
*
*/
export function processChildren(children: JSXChildren) {
const childrenToProcess = (
Array.isArray(children) ? [...children] : children ? [children] : []
) as Array<JSXNode>;

while (childrenToProcess.length) {
const child = childrenToProcess.shift();

if (!child) continue;

if (Array.isArray(child)) {
childrenToProcess.unshift(...child);
continue;
}

const processor = componentRegistry.get(child.type);

if (processor) {
processor(child.props);
} else {
const anyChildren = Array.isArray(child.children)
? [...child.children]
: [child.children];
childrenToProcess.unshift(...(anyChildren as JSXNode[]));
}
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const componentRegistry = new Map<any, ComponentProcessor>();

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function findComponent(component: any, processor: ComponentProcessor) {
componentRegistry.set(component, processor);
}

type ComponentProcessor = (props: Record<string, unknown>) => void;

0 comments on commit 5b6976b

Please sign in to comment.