Skip to content

Commit 5b2a3c4

Browse files
Merge pull request #381 from thejackshelton/autocomplete-improvements
Accordion: Ready State
2 parents 7cb9125 + b21617b commit 5b2a3c4

File tree

12 files changed

+414
-241
lines changed

12 files changed

+414
-241
lines changed

apps/website/src/_state/component-statuses.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ export const componentsStatuses: ComponentKitsStatuses = {
3636
Tabs: 'Planned',
3737
Toast: 'Planned',
3838
Toggle: 'Planned',
39-
Tooltip: 'Planned',
39+
Tooltip: 'Planned'
4040
},
4141
headless: {
42-
Accordion: 'Planned',
42+
Accordion: 'Ready',
4343
Autocomplete: 'Draft',
4444
Carousel: 'Planned',
4545
Popover: 'Planned',
4646
Select: 'Draft',
4747
Tabs: 'Ready',
4848
Toggle: 'Planned',
49-
Tooltip: 'Planned',
50-
},
49+
Tooltip: 'Planned'
50+
}
5151
};

apps/website/src/routes/docs/_components/anatomy-table/anatomy-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const AnatomyTable = component$(({ propDescriptors }: AnatomyTableProps)
2424
{propDescriptors?.map((propDescriptor) => {
2525
return (
2626
<tr key={propDescriptor.name}>
27-
<td class="prose prose-sm py-3 pl-4 align-baseline sm:pl-0 ">
27+
<td class="prose prose-sm py-3 pl-2 pr-2 align-center sm:pl-0 md:align-baseline">
2828
<code>{propDescriptor.name}</code>
2929
</td>
3030
<td class="py-3 align-baseline">

apps/website/src/routes/docs/headless/(components)/accordion/examples.tsx

Lines changed: 90 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const HeroAccordion = component$(() => {
2020
<div class="w-full flex justify-center" q:slot="actualComponent">
2121
<AccordionRoot
2222
animated
23+
enhance={true}
2324
class="bg-gray-100 dark:bg-gray-700 rounded-sm border-slate-200 dark:border-gray-600 border-[1px] box-border w-[min(400px,_100%)]"
2425
>
2526
<AccordionItem>
@@ -589,63 +590,100 @@ export const OnFocusIndexChange = component$(() => {
589590
);
590591
});
591592

592-
export const DynamicAccordion = component$(() => {
593-
const itemStore = useStore<number[]>([1, 2]);
593+
interface DynamicAccordionProps {
594+
itemIndexToDelete?: number;
595+
itemIndexToAdd?: number;
596+
itemsLength: number;
597+
}
594598

595-
return (
596-
<PreviewCodeExample>
597-
<div class="w-full flex flex-col items-center" q:slot="actualComponent">
598-
<AccordionRoot class="bg-gray-100 dark:bg-gray-700 rounded-sm border-slate-200 dark:border-gray-600 border-[1px] border-t-[0px] box-border w-[min(400px,_100%)]">
599-
{itemStore.map((itemNumber, index) => {
600-
return (
601-
<AccordionItem key={index}>
602-
<AccordionTrigger class="bg-violet-50 hover:bg-violet-100 dark:bg-gray-700 px-4 py-2 w-full dark:hover:bg-gray-800 text-left flex items-center justify-between group aria-expanded:rounded-none border-t-[1px] border-slate-200 dark:border-gray-600">
603-
<span>Trigger {itemNumber}</span>
604-
<span class="pl-2 flex">
605-
<p class="group-aria-expanded:transform group-aria-expanded:rotate-45 scale-150">
606-
+
607-
</p>
608-
</span>
609-
</AccordionTrigger>
610-
<AccordionContent class="bg-violet-200 dark:bg-gray-900 p-4 border-t-[1px] dark:border-gray-600 border-slate-200">
611-
Content {itemNumber}
612-
</AccordionContent>
613-
</AccordionItem>
614-
);
615-
})}
616-
</AccordionRoot>
599+
export const DynamicAccordion = component$(
600+
({ itemsLength = 3 }: DynamicAccordionProps) => {
601+
const itemIndexToAdd = useSignal<string>('0');
602+
const itemIndexToDelete = useSignal<string>('0');
603+
604+
// start off with some items
605+
const items = [];
606+
const newItem = { label: 'New Item', id: Math.random() };
607+
608+
for (let i = 0; i < itemsLength; i++) {
609+
items.push({
610+
label: `Original Item ${i + 1}`,
611+
id: Math.random()
612+
});
613+
}
614+
615+
const itemStore = useStore<{ label: string; id: number }[]>(items);
616+
617+
return (
618+
<PreviewCodeExample>
619+
<div class="w-full flex flex-col items-center" q:slot="actualComponent">
620+
<div class="flex gap-4">
621+
<label class="flex flex-col-reverse mb-4 items-center text-center">
622+
<input
623+
class="rounded-md px-2 max-w-[50px] bg-[#374151]"
624+
type="text"
625+
bind:value={itemIndexToAdd}
626+
/>
627+
<span>Index to Add</span>
628+
</label>
617629

618-
<div class="flex flex-col sm:flex-row gap-2 md:gap-4">
619-
<button
620-
style={{ color: 'green', marginTop: '1rem' }}
621-
onClick$={() => {
622-
if (itemStore.length < 4) {
623-
itemStore.push(itemStore.length + 1);
624-
}
625-
}}
626-
>
627-
<strong>Add Item</strong>
628-
</button>
630+
<label class="flex flex-col-reverse mb-4 items-center text-center">
631+
<input
632+
class="rounded-md px-2 max-w-[50px] bg-[#374151]"
633+
type="text"
634+
bind:value={itemIndexToDelete}
635+
/>
636+
<span>Index to Delete</span>
637+
</label>
638+
</div>
629639

630-
<button
631-
style={{ color: 'red', marginTop: '1rem' }}
632-
onClick$={() => {
633-
if (itemStore.length > 1) {
634-
itemStore.pop();
635-
}
636-
}}
637-
>
638-
<strong>Remove Item</strong>
639-
</button>
640+
<AccordionRoot class="bg-gray-100 dark:bg-gray-700 rounded-sm border-slate-200 dark:border-gray-600 border-[1px] border-t-[0px] box-border w-[min(400px,_100%)]">
641+
{itemStore.map(({ label, id }, index) => {
642+
return (
643+
<AccordionItem id={`${id}`} key={id}>
644+
<AccordionHeader>
645+
<AccordionTrigger class="bg-violet-50 hover:bg-violet-100 dark:bg-gray-700 px-4 py-2 w-full dark:hover:bg-gray-800 text-left flex items-center justify-between group aria-expanded:rounded-none border-t-[1px] border-slate-200 dark:border-gray-600">
646+
{label}
647+
</AccordionTrigger>
648+
</AccordionHeader>
649+
<AccordionContent class="bg-violet-200 dark:bg-gray-900 p-4 border-t-[1px] dark:border-gray-600 border-slate-200">
650+
index: {index}
651+
</AccordionContent>
652+
</AccordionItem>
653+
);
654+
})}
655+
</AccordionRoot>
656+
<div class="flex gap-2 md:gap-4">
657+
<button
658+
style={{ color: 'green', marginTop: '1rem' }}
659+
onClick$={() => {
660+
if (itemStore.length < 6) {
661+
itemStore.splice(parseInt(itemIndexToAdd.value), 0, newItem);
662+
}
663+
}}
664+
>
665+
<strong>Add Item</strong>
666+
</button>
667+
<button
668+
style={{ color: 'red', marginTop: '1rem' }}
669+
onClick$={() => {
670+
if (itemStore.length > 2) {
671+
itemStore.splice(parseInt(itemIndexToDelete.value), 1);
672+
}
673+
}}
674+
>
675+
<strong>Remove Item</strong>
676+
</button>
677+
</div>
640678
</div>
641-
</div>
642679

643-
<div q:slot="codeExample">
644-
<Slot />
645-
</div>
646-
</PreviewCodeExample>
647-
);
648-
});
680+
<div q:slot="codeExample">
681+
<Slot />
682+
</div>
683+
</PreviewCodeExample>
684+
);
685+
}
686+
);
649687

650688
export function SVG(props: QwikIntrinsicElements['svg'], key: string) {
651689
return (

apps/website/src/routes/docs/headless/(components)/accordion/index.mdx

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -583,55 +583,90 @@ By default, when using the `AccordionHeader` component, it's rendered as an `h3`
583583

584584
<DynamicAccordion>
585585
```tsx
586-
<AccordionRoot class="accordion-root">
587-
{itemStore.map((itemNumber, index) => {
586+
export const DynamicAccordion = component$(
587+
({ itemsLength = 3}: DynamicAccordionProps) => {
588+
589+
const itemIndexToAdd = useSignal<string>('0');
590+
const itemIndexToDelete = useSignal<string>('0');
591+
592+
// start off with some items
593+
const items = [];
594+
const newItem = { label: 'New Item', id: Math.random() };
595+
596+
for(let i = 0; i < itemsLength; i++) {
597+
items.push({
598+
label: `Original Item ${i + 1}`,
599+
id: Math.random()
600+
});
601+
}
602+
603+
const itemStore = useStore<{ label: string, id: number }[]>(items);
604+
605+
return (
606+
<div>
607+
<label>
608+
<input bind:value={itemIndexToAdd} />
609+
<span>Index to Add</span>
610+
</label>
611+
612+
<label>
613+
<input bind:value={itemIndexToDelete} />
614+
<span>Index to Delete</span>
615+
</label>
616+
</div>
617+
618+
<AccordionRoot class="dynamic-root">
619+
{itemStore.map(({label, id}, index) => {
588620
return (
589-
<AccordionItem key={index}>
590-
<AccordionTrigger class="accordion-trigger"><span>Trigger {itemNumber}</span>
591-
<span class="accordion-icon">
592-
<p>
593-
+
594-
</p>
595-
</span>
596-
</AccordionTrigger>
597-
<AccordionContent class="accordion-content">
598-
Content {itemNumber}
599-
</AccordionContent>
621+
<AccordionItem id={`${id}`} key={id}>
622+
<AccordionHeader>
623+
<AccordionTrigger class="dynamic-trigger">
624+
{label}
625+
</AccordionTrigger>
626+
</AccordionHeader>
627+
<AccordionContent class="dynamic-content">index: {index}</AccordionContent>
600628
</AccordionItem>
601-
)
629+
);
602630
})}
603631
</AccordionRoot>
604-
605632
<div>
606633
<button
607634
style={{ color: 'green', marginTop: '1rem' }}
608635
onClick$={() => {
609-
if (itemStore.length < 4) {
610-
itemStore.push(itemStore.length + 1);
636+
if (itemStore.length > 2) {
637+
itemStore.splice(parseInt(itemIndexToAdd.value), 1);
611638
}
612639
}}
613640
>
614641
<strong>Add Item</strong>
615642
</button>
616-
617643
<button
618644
style={{ color: 'red', marginTop: '1rem' }}
619645
onClick$={() => {
620-
if (itemStore.length > 1) {
621-
itemStore.pop();
646+
if (itemStore.length < 6) {
647+
itemStore.splice(parseInt(itemIndexToAdd.value), 0, newItem);
622648
}
623649
}}
624650
>
625651
<strong>Remove Item</strong>
626652
</button>
627653
</div>
628-
654+
)}
655+
);
629656
```
630657
</DynamicAccordion>
631658

632659
You can embrace reactivity, using signals, stores, and however else you'd like to use the Accordion with dynamic behavior.
633660

634-
When an Accordion Item is removed, a **Visible Task** runs that will clean up the DOM node in the browser, ensuring that you stay clear of race condition or memory leak issues.
661+
When an Accordion Item is removed, a [Visible Task](https://qwik.builder.io/docs/components/tasks/#usevisibletask) runs that will clean up the DOM node in the browser, ensuring that you stay clear of race condition or memory leak issues.
662+
663+
> You can add or remove something at any index and the focus order will adhere to the DOM hierarchy!
664+
665+
<div class="my-4">
666+
If you'd prefer to add your own <strong>id</strong> to the Accordion Item with dynamic behavior, you can add the `id` prop to the Accordion Item. This can be useful when you'd like the id value to be sync with your custom logic.
667+
</div>
668+
669+
By default, the Accordion Item has a locally scoped id with Qwik's `useId` hook. All children elements will be prefixed by its respective item id, followed by a dash and the element. For example, `{id}-trigger`.
635670

636671
## Accessibility
637672

@@ -713,6 +748,27 @@ propDescriptors={[
713748

714749
<br />
715750

751+
### Accordion Item
752+
753+
<APITable
754+
propDescriptors={[
755+
{
756+
name: 'id',
757+
type: 'string',
758+
description:
759+
'Allows the consumer to supply their own id attribute for the item and its descendants.',
760+
},
761+
{
762+
name: 'defaultValue',
763+
type: 'boolean',
764+
description:
765+
'Determines whether the Accordion Item will open by default.',
766+
},
767+
]}
768+
/>
769+
770+
<br />
771+
716772
### Accordion Header
717773

718774
<APITable

0 commit comments

Comments
 (0)