Skip to content

Commit 7730ff4

Browse files
authored
[feat](@svelteui/core]: Accordion component (#412)
* [WIP]: accordion component * [feat](@svelteui/core): move control to accordion Item and make styles functional * [feat](@svelteui/core): support multiple for accordion * [feat](@svelteui/core): wAI-ARIA support for Accordion * [fix](@svelteui/core): fix Collapse types * [feat](@svelteui/core): remove control component and cleanup cide * [fix](@svelteui/core): remove unused import * [feat](@svelteui/core): improve Accordion types * [feat](@svelteui/core): remove icon from AccordionItem * [fix](@svelteui/core): small fix on hover for Accordion * [fix](@svelteui/core): small fix in Accordion style * [feat](@svelteui/demos): accordion demos * [feat](docs): accordion docs * [test](@svelteui/core): unit test for Accordion
1 parent 0f4dc38 commit 7730ff4

31 files changed

+1413
-2
lines changed

apps/docs/src/lib/components/Sidebar.svelte

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
<li>
162162
<strong><Dashboard /><Space w="md" />Data Display</strong>
163163
<ul>
164+
<li><a href={`${base}/core/accordion`}>Accordion</a></li>
164165
<li><a href={`${base}/core/badge`}>Badge</a></li>
165166
<li><a href={`${base}/core/card`}>Card</a></li>
166167
<li><a href={`${base}/core/image`}>Image</a></li>

apps/docs/src/lib/components/TopBar/data.ts

+4
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ export const searchLinks = [
3636
{ title: 'SvelteUIProvider', link: `${base}/theming/svelteui-provider`, section: 'Theming' },
3737
{ title: 'Utilities', link: `${base}/theming/stitches-utilities`, section: 'Theming' },
3838

39+
{ title: 'Accordion', link: `${base}/core/accordion`, section: 'Components' },
3940
{ title: 'ActionIcon', link: `${base}/core/action-icon`, section: 'Components' },
4041
{ title: 'Affix', link: `${base}/core/affix`, section: 'Components' },
4142
{ title: 'Alert', link: `${base}/core/alert`, section: 'Components' },
4243
{ title: 'Anchor', link: `${base}/core/anchor`, section: 'Components' },
4344
{ title: 'AppShell', link: `${base}/core/app-shell`, section: 'Components' },
45+
{ title: 'AspectRatio', link: `${base}/core/aspect-ratio`, section: 'Components' },
4446
{ title: 'Badge', link: `${base}/core/badge`, section: 'Components' },
4547
{ title: 'Box', link: `${base}/core/box`, section: 'Components' },
4648
{ title: 'Burger', link: `${base}/core/burger`, section: 'Components' },
@@ -53,6 +55,7 @@ export const searchLinks = [
5355
{ title: 'Collapse', link: `${base}/core/collapse`, section: 'Components' },
5456
{ title: 'Container', link: `${base}/core/container`, section: 'Components' },
5557
{ title: 'Divider', link: `${base}/core/divider`, section: 'Components' },
58+
{ title: 'Flex', link: `${base}/core/flex`, section: 'Components' },
5659
{ title: 'Grid', link: `${base}/core/grid`, section: 'Components' },
5760
{ title: 'Group', link: `${base}/core/group`, section: 'Components' },
5861
{ title: 'Image', link: `${base}/core/image`, section: 'Components' },
@@ -79,6 +82,7 @@ export const searchLinks = [
7982
{ title: 'Stack', link: `${base}/core/stack`, section: 'Components' },
8083
{ title: 'Switch', link: `${base}/core/switch`, section: 'Components' },
8184
{ title: 'Tabs', link: `${base}/core/tabs`, section: 'Components' },
85+
{ title: 'Textarea', link: `${base}/core/textarea`, section: 'Components' },
8286
{ title: 'TextInput', link: `${base}/core/text-input`, section: 'Components' },
8387
{ title: 'Text', link: `${base}/core/text`, section: 'Components' },
8488
{ title: 'ThemeIcon', link: `${base}/core/theme-icon`, section: 'Components' },

apps/docs/src/lib/data/main/homepage.js

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Accessibility, Dashboard, Box as BoxIcon } from 'radix-icons-svelte';
22
import TypeScript from '../../components/svgs/TypeScript.svelte';
33
import {
4+
Accordion,
45
Container,
56
Center,
67
Collapse,
@@ -181,6 +182,14 @@ export const components = [
181182
title: 'Switch',
182183
color: '$blue600'
183184
},
185+
{
186+
icon: BoxIcon,
187+
component: Accordion,
188+
link: 'core/accordion',
189+
title: 'Accordion',
190+
color: '$blue600',
191+
content: 'BETA'
192+
},
184193
{
185194
icon: BoxIcon,
186195
component: Badge,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: Accordion
3+
group: 'svelteuidev-core'
4+
packageGroup: '@svelteuidev/core'
5+
slug: /core/accordion/
6+
category: 'data-display'
7+
description: 'Shows and divides content into collapsible sections'
8+
importCode: "import { Accordion } from '@svelteuidev/core';"
9+
source: 'svelteui-core/src/components/Accordion/Accordion.svelte'
10+
docs: 'core/accordion'
11+
---
12+
13+
<script lang="ts">
14+
import { Demo, AccordionDemos } from '@svelteuidev/demos';
15+
import { Heading } from "$lib/components";
16+
import { base } from '$app/paths';
17+
</script>
18+
19+
<svelte:head>
20+
21+
<title>{title} - SvelteUI</title>
22+
</svelte:head>
23+
24+
<Heading {title} {group} {packageGroup} {slug} {category} {description} {importCode} {source} {docs} />
25+
26+
## Usage
27+
28+
<Demo demo={AccordionDemos.configurator} />
29+
30+
## Customize Control
31+
32+
Control can be fully customizable by setting any king of element inside the `control` slot.
33+
34+
<Demo demo={AccordionDemos.custom} />
35+
36+
## Change chevron
37+
38+
<Demo demo={AccordionDemos.chevron} />
39+
40+
## Controlled
41+
42+
The accordion component can be controlled externally with the prop `value` (which can be binded) and with the `on:change` event.
43+
44+
<Demo demo={AccordionDemos.controlled} />
45+
46+
## Default open
47+
48+
It's possible to set default opened items with the prop `defaultValue`. When `multiple` is false, this value should be a string.
49+
50+
<Demo demo={AccordionDemos.defaultValue} />
51+
52+
When `multiple` is `true`, this should be provided as an arrays of strings.
53+
54+
<Demo demo={AccordionDemos.defaultValueMultiple} />
55+
56+
## Disabled items
57+
58+
<Demo demo={AccordionDemos.disabled} />
59+
60+
## Transition
61+
62+
It's possible to change the chevron transition duration by modifying the prop `transitionDuration` (in milliseconds).
63+
64+
<Demo demo={AccordionDemos.transitionDuration} />
65+
66+
To disable the transition completely, set `transitionDuration` to `0`.
67+
68+
<Demo demo={AccordionDemos.noTransition} />
69+
70+
## Data attributes
71+
72+
Each item exposes data attributes that can be used to style the component.
73+
74+
- `data-rotate` for the chevron, which tells if the chevron should rotate. If `disableChevronRotation` is set, it will always be false.
75+
- `data-active` in `Accordion.Item` when it's content is expanded.
76+
77+
<Demo demo={AccordionDemos.data} />
78+
79+
## Accessibility
80+
81+
The accordion component follows the [WAI-ARIA recommendations](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/examples/accordion/) on accessibility.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { HTMLAttributes } from 'svelte/elements';
2+
import { Component } from '$lib/internal';
3+
import type { DefaultProps, SvelteUINumberSize } from '$lib/styles';
4+
5+
export type AccordionVariant = 'default' | 'contained' | 'filled' | 'separated';
6+
7+
export type AccordionValue<Multiple> = Multiple extends true ? string[] : string | null;
8+
9+
export type AccordionContext<Multiple extends boolean = false> = Writable<{
10+
currentValue?: AccordionValue<Multiple>;
11+
variant?: AccordionVariant;
12+
order?: 2 | 3 | 4 | 5 | 6;
13+
radius?: SvelteUINumberSize | number;
14+
chevron?: Component | HTMLOrSVGElement;
15+
chevronPosition?: 'left' | 'right';
16+
chevronSize?: string | number;
17+
disableChevronRotation?: boolean;
18+
transitionDuration?: number?;
19+
updateActive: (value: string) => void;
20+
isItemActive: (value: string) => boolean;
21+
getControlsId: (value: string) => string;
22+
getRegionId: (value: string) => string;
23+
}>;
24+
25+
export interface AccordionProps<Multiple extends boolean = false>
26+
extends DefaultProps,
27+
HTMLAttributes<HTMLElement> {
28+
variant?: AccordionVariant;
29+
value?: AccordionValue<Multiple>;
30+
defaultValue?: AccordionValue<Multiple>;
31+
radius?: SvelteUINumberSize | number;
32+
order?: 2 | 3 | 4 | 5 | 6;
33+
multiple?: Multiple;
34+
loop?: boolean;
35+
id?: string;
36+
chevron?: Component | HTMLOrSVGElement;
37+
chevronPosition?: 'left' | 'right';
38+
chevronSize?: string | number;
39+
disableChevronRotation?: boolean;
40+
transitionDuration?: number?;
41+
}
42+
43+
export interface AccordionEvents<Multiple extends boolean = false> {
44+
change: CustomEvent<AccordionValue<Multiple>>;
45+
[evt: string]: CustomEvent<any>;
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<script lang="ts">
2+
import { Meta, Template, Story } from '@storybook/addon-svelte-csf';
3+
import { Accordion } from './index';
4+
5+
let value = 'typescript';
6+
</script>
7+
8+
<Meta title="Components/Accordion" component={Accordion} />
9+
10+
<Template let:args>
11+
<Accordion {...args} defaultValue="typescript">
12+
<Accordion.Item value="typescript">
13+
<div slot="control">Typescript Based</div>
14+
Build type safe applications. All SvelteUI packages are built with TypeScript and support it by
15+
default. All components and functions export types, are documented, and give developers autocomplete
16+
features!
17+
</Accordion.Item>
18+
<Accordion.Item value="packed">
19+
<div slot="control">Feature packed</div>
20+
SvelteUI contains more than just components. With Actions, Transitions, and Utilities available
21+
to you, development will be fun and easy!
22+
</Accordion.Item>
23+
<Accordion.Item value="accessible">
24+
<div slot="control">Accessible and usable</div>
25+
All components are accessible according to WAI-ARIA standards. On top of that, no annoying focus
26+
ring. It will appear only when user navigates with keyboard.
27+
</Accordion.Item>
28+
</Accordion>
29+
</Template>
30+
31+
<Story name="Accordion" id="accordionStory" />
32+
33+
<Story name="Multiple Tabs Open" id="accordionMultipleStory" args={{ multiple: true }} />
34+
35+
<Story name="Disabled Tabs" id="accordionDisabledStory">
36+
<Accordion defaultValue="typescript">
37+
<Accordion.Item value="typescript">
38+
<div slot="control">Typescript Based</div>
39+
Build type safe applications. All SvelteUI packages are built with TypeScript and support it by
40+
default. All components and functions export types, are documented, and give developers autocomplete
41+
features!
42+
</Accordion.Item>
43+
<Accordion.Item value="packed" disabled>
44+
<div slot="control">Feature packed</div>
45+
SvelteUI contains more than just components. With Actions, Transitions, and Utilities available
46+
to you, development will be fun and easy!
47+
</Accordion.Item>
48+
<Accordion.Item value="accessible">
49+
<div slot="control">Accessible and usable</div>
50+
All components are accessible according to WAI-ARIA standards. On top of that, no annoying focus
51+
ring. It will appear only when user navigates with keyboard.
52+
</Accordion.Item>
53+
</Accordion>
54+
</Story>
55+
56+
<Story name="Multiple Tabs Open With Default" id="accordionMultipleDefaultStory">
57+
<Accordion multiple={true} defaultValue={['packed', 'accessible']}>
58+
<Accordion.Item value="typescript">
59+
<div slot="control">Typescript Based</div>
60+
Build type safe applications. All SvelteUI packages are built with TypeScript and support it by
61+
default. All components and functions export types, are documented, and give developers autocomplete
62+
features!
63+
</Accordion.Item>
64+
<Accordion.Item value="packed">
65+
<div slot="control">Feature packed</div>
66+
SvelteUI contains more than just components. With Actions, Transitions, and Utilities available
67+
to you, development will be fun and easy!
68+
</Accordion.Item>
69+
<Accordion.Item value="accessible">
70+
<div slot="control">Accessible and usable</div>
71+
All components are accessible according to WAI-ARIA standards. On top of that, no annoying focus
72+
ring. It will appear only when user navigates with keyboard.
73+
</Accordion.Item>
74+
</Accordion>
75+
</Story>
76+
77+
<Story name="Controlled" id="accordionControlledStory">
78+
<Accordion {value} on:change={(e) => (value = e.detail)}>
79+
<Accordion.Item value="typescript">
80+
<div slot="control">Typescript Based</div>
81+
Build type safe applications. All SvelteUI packages are built with TypeScript and support it by
82+
default. All components and functions export types, are documented, and give developers autocomplete
83+
features!
84+
</Accordion.Item>
85+
<Accordion.Item value="packed">
86+
<div slot="control">Feature packed</div>
87+
SvelteUI contains more than just components. With Actions, Transitions, and Utilities available
88+
to you, development will be fun and easy!
89+
</Accordion.Item>
90+
<Accordion.Item value="accessible">
91+
<div slot="control">Accessible and usable</div>
92+
All components are accessible according to WAI-ARIA standards. On top of that, no annoying focus
93+
ring. It will appear only when user navigates with keyboard.
94+
</Accordion.Item>
95+
</Accordion>
96+
</Story>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createStyles } from '$lib/styles';
2+
import type { SvelteUINumberSize } from '$lib/styles';
3+
import type { AccordionVariant } from './Accordion';
4+
5+
export interface AccordionStylesParams {
6+
radius: SvelteUINumberSize;
7+
variant: AccordionVariant;
8+
}
9+
10+
export default createStyles((theme, { radius, variant }: AccordionStylesParams) => {
11+
return {
12+
root: {}
13+
};
14+
});

0 commit comments

Comments
 (0)