Skip to content

Commit 7d6e584

Browse files
initial collapsible changes
1 parent 7093550 commit 7d6e584

File tree

10 files changed

+161
-143
lines changed

10 files changed

+161
-143
lines changed

apps/website/src/routes/docs/headless/collapsible/collapsible.spec.ts

Lines changed: 0 additions & 123 deletions
This file was deleted.

apps/website/src/routes/docs/headless/collapsible/examples/animation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default component$(() => {
1111
<SVG class="collapsible-transition" />
1212
</CollapsibleTrigger>
1313
<CollapsibleContent class="collapsible-animation collapsible-content">
14-
Content
14+
<p class="collapsible-content-outline">Content</p>
1515
</CollapsibleContent>
1616
</Collapsible>
1717
);

apps/website/src/routes/docs/headless/collapsible/examples/collapsible.css

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
.collapsible-content {
2020
width: 100%;
2121
background-color: hsl(var(--background));
22-
padding: 0.5rem;
23-
border: 2px dotted hsla(var(--foreground) / 0.6);
2422
border-radius: calc(var(--border-radius) / 2);
2523
max-width: var(--select-width);
2624
color: hsl(var(--foreground));
2725
overflow: hidden;
2826
}
2927

28+
.collapsible-content-outline {
29+
padding: 0.5rem;
30+
border: 2px dotted hsla(var(--foreground) / 0.6);
31+
}
32+
3033
/* animations only */
3134
.collapsible-transition {
3235
transition: transform 500ms ease;

apps/website/src/routes/docs/headless/collapsible/examples/hero.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default component$(() => {
1212
<span>Trigger</span>
1313
<SVG />
1414
</CollapsibleTrigger>
15-
<CollapsibleContent class="test-animation collapsible-content">
15+
<CollapsibleContent class="collapsible-content collapsible-content-outline ">
1616
Content
1717
</CollapsibleContent>
1818
</Collapsible>

apps/website/src/routes/docs/headless/collapsible/examples/uncontrolled.tsx

Whitespace-only changes.

apps/website/src/routes/docs/headless/collapsible/index.mdx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,52 @@
11
import { statusByComponent } from '~/_state/component-statuses';
22
import { FeatureList } from '~/components/feature-list/feature-list';
33
import { Note } from '~/components/note/note';
4+
import { AnatomyTable } from '~/components/anatomy-table/anatomy-table';
45

56
<StatusBanner status={statusByComponent.headless.Collapsible} />
67

78
# Collapsible
89

910
An interactive component which expands/collapses a panel.
1011

11-
<div data-testid="collapsible-hero-test">
12-
<Showcase name="hero" />
13-
</div>
12+
<Showcase name="hero" />
13+
14+
## ✨ Features
15+
16+
<FeatureList
17+
features={[
18+
'Accessible as a button that shows content, following web a11y standards.',
19+
'Full keyboard navigation',
20+
'Controlled or uncontrolled',
21+
'Initial open state does not wake up the component',
22+
'Executes on interaction or programmatically',
23+
]}
24+
/>
25+
26+
## Building blocks
27+
28+
<CodeSnippet name="building-blocks" />
29+
30+
### 🎨 Anatomy
31+
32+
<AnatomyTable
33+
propDescriptors={[
34+
{
35+
name: 'Collapsible',
36+
description: 'The root container for the Collapsible component.',
37+
},
38+
{
39+
name: 'CollapsibleTrigger',
40+
description: 'A button that opens the Collapsible content when interacted with.',
41+
},
42+
{
43+
name: 'CollapsibleContent',
44+
description: 'Contains the content associated with a Collapsible.',
45+
},
46+
]}
47+
/>
48+
49+
## Why use a headless collapsible?
1450

1551
## Animation
1652

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { component$ } from '@builder.io/qwik';
2+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@qwik-ui/headless';
3+
4+
export default component$(() => (
5+
<Collapsible>
6+
<CollapsibleTrigger>Trigger</CollapsibleTrigger>
7+
<CollapsibleContent>Content</CollapsibleContent>
8+
</Collapsible>
9+
));
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { expect, test, type Page } from '@playwright/test';
2+
import { createTestDriver } from './collapsible.driver';
3+
4+
async function setup(page: Page, exampleName: string) {
5+
await page.goto(`headless/collapsible/${exampleName}`);
6+
7+
const driver = createTestDriver(page);
8+
9+
return {
10+
driver,
11+
};
12+
}
13+
14+
test.describe('Mouse Behavior', () => {
15+
test(`GIVEN a hero collapsible
16+
WHEN clicking on the trigger
17+
THEN the content should be visible
18+
AND aria-expanded is true`, async ({ page }) => {
19+
const { driver: d } = await setup(page, 'hero');
20+
21+
await d.getTrigger().click();
22+
23+
await expect(d.getContent()).toBeVisible();
24+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'true');
25+
});
26+
27+
test(`GIVEN an open hero collapsible
28+
WHEN clicking on the trigger
29+
THEN the content should be hidden
30+
AND aria-expanded is false`, async ({ page }) => {
31+
const { driver: d } = await setup(page, 'hero');
32+
await d.openCollapsible('click');
33+
34+
await d.getTrigger().click();
35+
36+
await expect(d.getContent()).toBeHidden();
37+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'false');
38+
});
39+
});
40+
41+
test.describe('Keyboard Behavior', () => {
42+
test(`GIVEN a hero collapsible
43+
WHEN pressing the space key
44+
THEN the content should be visible
45+
AND aria-expanded is true`, async ({ page }) => {
46+
const { driver: d } = await setup(page, 'hero');
47+
48+
await d.getTrigger().press('Space');
49+
50+
await expect(d.getContent()).toBeVisible();
51+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'true');
52+
});
53+
54+
test(`GIVEN an open hero collapsible
55+
WHEN pressing the space key
56+
THEN the content should be hidden
57+
AND aria-expanded is false`, async ({ page }) => {
58+
const { driver: d } = await setup(page, 'hero');
59+
await d.openCollapsible('Space');
60+
61+
await d.getTrigger().press('Space');
62+
63+
await expect(d.getContent()).toBeHidden();
64+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'false');
65+
});
66+
67+
test(`GIVEN a hero collapsible
68+
WHEN pressing the enter key
69+
THEN the content should be visible
70+
AND aria-expanded is true`, async ({ page }) => {
71+
const { driver: d } = await setup(page, 'hero');
72+
73+
await d.getTrigger().press('Enter');
74+
75+
await expect(d.getContent()).toBeVisible();
76+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'true');
77+
});
78+
79+
test(`GIVEN an open hero collapsible
80+
WHEN pressing the enter key
81+
THEN the content should be hidden
82+
AND aria-expanded is false`, async ({ page }) => {
83+
const { driver: d } = await setup(page, 'hero');
84+
await d.openCollapsible('Enter');
85+
86+
await d.getTrigger().press('Enter');
87+
88+
await expect(d.getContent()).toBeHidden();
89+
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'false');
90+
});
91+
});
92+
93+
test.describe('Aria', () => {
94+
test(`GIVEN a collapsible with aria-controls
95+
WHEN a collapsible is rendered
96+
THEN the trigger's aria-controls should equal the content's id`, async ({
97+
page,
98+
}) => {
99+
const { driver: d } = await setup(page, 'hero');
100+
await d.openCollapsible('Enter');
101+
102+
const contentId = await d.getContent().getAttribute('id');
103+
104+
await expect(d.getTrigger()).toHaveAttribute('aria-controls', `${contentId}`);
105+
});
106+
});

packages/kit-headless/src/components/collapsible/collapsible.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,6 @@ export const Collapsible = component$((props: CollapsibleProps) => {
4141
);
4242
}
4343

44-
const { padding, border } = window.getComputedStyle(contentRef.value);
45-
46-
// the animation breaks when padding is set, because the height is not initially 0.
47-
if (padding !== '0px') {
48-
contentRef.value.style.padding = '0';
49-
contentChildRef.value.style.padding = padding;
50-
}
51-
52-
if (!border.includes('0px')) {
53-
contentRef.value.style.borderWidth = '0';
54-
contentChildRef.value.style.border = border;
55-
}
56-
5744
if (contentHeightSig.value === null) {
5845
contentHeightSig.value = getHiddenHeight(contentRef.value);
5946
}

0 commit comments

Comments
 (0)