Skip to content

Commit 484d1e2

Browse files
committed
✨ Add Collapsible component
1 parent 6e02930 commit 484d1e2

File tree

11 files changed

+393
-0
lines changed

11 files changed

+393
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ html body {
113113
// Checkbox component
114114
--w-checkbox-color: var(--w-color-primary);
115115

116+
// Collapsible
117+
--w-collapsible-initial-height: 0;
118+
--w-collapsible-max-height: 100%;
119+
116120
// Progress component
117121
--w-progress-color: var(--w-color-primary);
118122
--w-progress-background: var(--w-color-primary-50);
@@ -197,6 +201,7 @@ import { Accordion } from 'webcoreui/react'
197201
- [Button](https://github.com/Frontendland/webcoreui/tree/main/src/components/Button)
198202
- [Card](https://github.com/Frontendland/webcoreui/tree/main/src/components/Card)
199203
- [Checkbox](https://github.com/Frontendland/webcoreui/tree/main/src/components/Checkbox)
204+
- [Collapsible](https://github.com/Frontendland/webcoreui/tree/main/src/components/Collapsible)
200205
- [ConditionalWrapper](https://github.com/Frontendland/webcoreui/tree/main/src/components/ConditionalWrapper)
201206
- [Icon](https://github.com/Frontendland/webcoreui/tree/main/src/components/Icon)
202207
- [Input](https://github.com/Frontendland/webcoreui/tree/main/src/components/Input)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
import type { CollapsibleProps } from './collapsible'
3+
4+
import styles from './collapsible.module.scss'
5+
import { classNames } from '../../utils/classNames'
6+
7+
interface Props extends CollapsibleProps {}
8+
9+
const {
10+
initialHeight,
11+
maxHeight,
12+
toggled,
13+
className,
14+
togglesClassName
15+
} = Astro.props
16+
17+
const classes = [
18+
styles.collapsible,
19+
maxHeight && styles.animated,
20+
className
21+
]
22+
23+
const styleVariables = classNames([
24+
initialHeight && `--w-collapsible-initial-height: ${initialHeight};`,
25+
maxHeight && `--w-collapsible-max-height: ${maxHeight};`
26+
])
27+
---
28+
29+
<div
30+
class:list={classes}
31+
data-toggled={toggled ? 'true' : undefined}
32+
data-id="w-collapsible"
33+
>
34+
<div
35+
class={styles.wrapper}
36+
style={styleVariables}
37+
>
38+
<slot />
39+
</div>
40+
<div class:list={togglesClassName}>
41+
<div data-toggle-on="true"><slot name="on" /></div>
42+
<div data-toggle-off="true"><slot name="off" /></div>
43+
</div>
44+
</div>
45+
46+
<script>
47+
const collapsibles = document.querySelectorAll('[data-id="w-collapsible"]')
48+
49+
Array.from(collapsibles).forEach(element => {
50+
element.addEventListener('click', event => {
51+
const collapsible = event.currentTarget as HTMLDivElement
52+
const target = event.target as HTMLDivElement
53+
54+
if (target.parentElement?.dataset.toggleOn) {
55+
collapsible.dataset.toggled = 'true'
56+
}
57+
58+
if (target.parentElement?.dataset.toggleOff) {
59+
collapsible.dataset.toggled = 'false'
60+
}
61+
})
62+
})
63+
</script>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script lang="ts">
2+
import type { CollapsibleProps } from './collapsible'
3+
4+
import styles from './collapsible.module.scss'
5+
import { classNames } from '../../utils/classNames'
6+
7+
export let initialHeight: CollapsibleProps['initialHeight'] = ''
8+
export let maxHeight: CollapsibleProps['maxHeight'] = ''
9+
export let toggled: CollapsibleProps['toggled'] = false
10+
export let className: CollapsibleProps['className'] = ''
11+
export let togglesClassName: CollapsibleProps['togglesClassName'] = ''
12+
13+
const classes = classNames([
14+
styles.collapsible,
15+
maxHeight && styles.animated,
16+
className
17+
])
18+
19+
const styleVariables = classNames([
20+
initialHeight && `--w-collapsible-initial-height: ${initialHeight};`,
21+
maxHeight && `--w-collapsible-max-height: ${maxHeight};`
22+
])
23+
</script>
24+
25+
<div
26+
class={classes}
27+
data-toggled={toggled ? 'true' : undefined}
28+
>
29+
<div
30+
class={styles.wrapper}
31+
style={styleVariables}
32+
>
33+
<slot />
34+
</div>
35+
<div
36+
on:click={() => toggled = !toggled}
37+
on:keyup={() => toggled = !toggled}
38+
role="button"
39+
tabindex={0}
40+
class={togglesClassName}
41+
>
42+
{#if toggled}
43+
<slot name="off" />
44+
{:else}
45+
<slot name="on" />
46+
{/if}
47+
</div>
48+
</div>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useState } from 'react'
2+
import type { ReactCollapsibleProps } from './collapsible'
3+
4+
import styles from './collapsible.module.scss'
5+
import { classNames } from '../../utils/classNames'
6+
7+
const Collapsible = ({
8+
initialHeight,
9+
maxHeight,
10+
toggled,
11+
on,
12+
off,
13+
children,
14+
className,
15+
togglesClassName
16+
}: ReactCollapsibleProps) => {
17+
const [toggle, setToggled] = useState(toggled)
18+
19+
const classes = classNames([
20+
styles.collapsible,
21+
maxHeight && styles.animated,
22+
className
23+
])
24+
25+
const styleVariables = {
26+
...(initialHeight && { '--w-collapsible-initial-height': initialHeight }),
27+
...(maxHeight && { '--w-collapsible-max-height': maxHeight })
28+
} as React.CSSProperties
29+
30+
return (
31+
<div
32+
className={classes}
33+
data-toggled={toggle ? 'true' : undefined}
34+
>
35+
<div
36+
className={styles.wrapper}
37+
style={styleVariables}
38+
>
39+
{children}
40+
</div>
41+
<div
42+
onClick={() => setToggled(!toggle)}
43+
onKeyUp={() => setToggled(!toggle)}
44+
role="button"
45+
tabIndex={0}
46+
className={togglesClassName}
47+
>
48+
{toggle ? off : on}
49+
</div>
50+
</div>
51+
)
52+
}
53+
54+
export default Collapsible
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@import '../../scss/config.scss';
2+
3+
body {
4+
--w-collapsible-initial-height: 0;
5+
--w-collapsible-max-height: 100%;
6+
}
7+
8+
.collapsible {
9+
@include layout(flex, column, xs);
10+
11+
&:not([data-toggled="true"]) [data-toggle-off],
12+
&[data-toggled="true"] [data-toggle-on] {
13+
@include visibility(none);
14+
}
15+
16+
&[data-toggled="true"] .wrapper {
17+
max-height: var(--w-collapsible-max-height);
18+
}
19+
20+
&.animated .wrapper {
21+
@include transition(max-height, .5s);
22+
}
23+
24+
.wrapper {
25+
@include visibility(hidden);
26+
27+
max-height: var(--w-collapsible-initial-height);
28+
}
29+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type CollapsibleProps = {
2+
initialHeight?: string
3+
maxHeight?: string
4+
toggled?: boolean
5+
className?: string
6+
togglesClassName?: string
7+
}
8+
9+
export type ReactCollapsibleProps = {
10+
on: React.ReactNode
11+
off: React.ReactNode
12+
children?: React.ReactNode
13+
} & CollapsibleProps
14+

src/pages/collapsible.astro

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
---
2+
import Layout from '@static/Layout.astro'
3+
import ComponentWrapper from '@static/ComponentWrapper.astro'
4+
import CollapsibleTable from '@static/CollapsibleTable.astro'
5+
6+
import AstroCollapsible from '@components/Collapsible/Collapsible.astro'
7+
import SvelteCollapsible from '@components/Collapsible/Collapsible.svelte'
8+
import ReactCollapsible from '@components/Collapsible/Collapsible.tsx'
9+
10+
import Button from '@components/Button/Button.astro'
11+
12+
import { getSections } from '@helpers'
13+
14+
const sections = getSections({
15+
title: 'collapsibles',
16+
components: [AstroCollapsible, SvelteCollapsible, ReactCollapsible],
17+
showSubTitle: true
18+
})
19+
---
20+
21+
<Layout>
22+
<h1>Collapsible</h1>
23+
<div class="grid md-2 lg-3">
24+
<ComponentWrapper type="Astro">
25+
<AstroCollapsible maxHeight="330px">
26+
<CollapsibleTable />
27+
28+
<Button slot="on">Expand</Button>
29+
<Button slot="off">Collapse</Button>
30+
</AstroCollapsible>
31+
</ComponentWrapper>
32+
33+
<ComponentWrapper type="Svelte">
34+
<SvelteCollapsible maxHeight="330px" client:idle>
35+
<CollapsibleTable />
36+
37+
<Button slot="on">Expand</Button>
38+
<Button slot="off">Collapse</Button>
39+
</SvelteCollapsible>
40+
</ComponentWrapper>
41+
42+
<ComponentWrapper type="React">
43+
<ReactCollapsible
44+
on=""
45+
off=""
46+
maxHeight="330px"
47+
client:idle
48+
>
49+
<CollapsibleTable />
50+
51+
<Button slot="on">Expand</Button>
52+
<Button slot="off">Collapse</Button>
53+
</ReactCollapsible>
54+
</ComponentWrapper>
55+
</div>
56+
57+
{sections.map(section => (
58+
<h1>{section.title}</h1>
59+
<Fragment>
60+
{section.subTitle && <h2 set:html={section.subTitle} />}
61+
</Fragment>
62+
<div class="grid md-2 lg-3">
63+
<ComponentWrapper title="Default">
64+
<section.component>
65+
<CollapsibleTable />
66+
67+
<Button slot="on">Expand</Button>
68+
<Button slot="off">Collapse</Button>
69+
</section.component>
70+
</ComponentWrapper>
71+
72+
<ComponentWrapper title="Toggled by default">
73+
<section.component toggled={true}>
74+
<CollapsibleTable />
75+
76+
<Button slot="on">Expand</Button>
77+
<Button slot="off">Collapse</Button>
78+
</section.component>
79+
</ComponentWrapper>
80+
81+
<ComponentWrapper title="Toggle with same button">
82+
<section.component>
83+
<CollapsibleTable />
84+
85+
<Button slot="on">Toggle</Button>
86+
<Button slot="off">Toggle</Button>
87+
</section.component>
88+
</ComponentWrapper>
89+
90+
<ComponentWrapper title="Animated">
91+
<section.component maxHeight="330px">
92+
<CollapsibleTable />
93+
94+
<Button slot="on">Expand</Button>
95+
<Button slot="off">Collapse</Button>
96+
</section.component>
97+
</ComponentWrapper>
98+
99+
<ComponentWrapper title="Animated with partial visibility">
100+
<section.component
101+
initialHeight="100px"
102+
maxHeight="330px"
103+
>
104+
<CollapsibleTable />
105+
106+
<Button slot="on">Expand</Button>
107+
<Button slot="off">Collapse</Button>
108+
</section.component>
109+
</ComponentWrapper>
110+
111+
<ComponentWrapper title="Toggle with custom layout">
112+
<section.component
113+
className="collapsible-table"
114+
togglesClassName="center"
115+
>
116+
<CollapsibleTable />
117+
118+
<Button slot="on">Expand</Button>
119+
<Button slot="off">Collapse</Button>
120+
</section.component>
121+
</ComponentWrapper>
122+
</div>
123+
))}
124+
</Layout>
125+
126+
<style is:global>
127+
.collapsible-table {
128+
gap: 20px;
129+
}
130+
131+
.center {
132+
align-self: center;
133+
}
134+
</style>

src/pages/index.astro

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Avatar from '@components/Avatar/Avatar.astro'
99
import Badge from '@components/Badge/Badge.astro'
1010
import Button from '@components/Button/Button.astro'
1111
import Checkbox from '@components/Checkbox/Checkbox.astro'
12+
import Collapsible from '@components/Collapsible/Collapsible.astro'
1213
import Icon from '@components/Icon/Icon.astro'
1314
import Input from '@components/Input/Input.astro'
1415
import Menu from '@components/Menu/Menu.astro'
@@ -104,6 +105,12 @@ const tabItems = [{
104105
<CardWrapper title="Checkbox" href="/checkbox">
105106
<Checkbox checked={true} label="Accept terms and conditions" />
106107
</CardWrapper>
108+
<CardWrapper title="Collapsible" href="/collapsible">
109+
<Collapsible initialHeight="20px">
110+
<span>This book has been rated for...</span>
111+
<Badge slot="on">Expand</Badge>
112+
</Collapsible>
113+
</CardWrapper>
107114
<CardWrapper title="Icon" href="/icon">
108115
<Icon
109116
type="github"

0 commit comments

Comments
 (0)