Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add slots to allow customizing AccordionItem open/close icons #2285

Merged
merged 5 commits into from
Dec 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/modern-ways-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": minor
---

feat: Add slots to allow customizing AccordionItem open/close icons.
54 changes: 34 additions & 20 deletions packages/skeleton/src/lib/components/Accordion/AccordionItem.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@
* @slot {{}} lead - Allows for an optional leading element, such as an icon.
* @slot {{}} summary - Provide the interactive summary of each item.
* @slot {{}} content - Provide the content of each item.
* @slot {{}} iconClosed - Allows for an optional element when the AccordionItem is closed, such as an icon
* @slot {{}} iconOpen - Allows for an optional element when the AccordionItem is open, such as an icon
*/
// Events:
// FORWARDED: do not document these, breaks the type definition
// DISPATCHED: document directly above the definition, like props (ex: paginator)

import { getContext } from 'svelte';
import { createEventDispatcher } from 'svelte';
import { dynamicTransition } from '../../internal/transitions.js';
import { createEventDispatcher, getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import { dynamicTransition } from '../../internal/transitions.js';
import type { CssClasses, SvelteEvent, Transition, TransitionParams } from '../../index.js';

// Event Dispatcher
// Types
type TransitionIn = $$Generic<Transition>;
type TransitionOut = $$Generic<Transition>;
type AccordionItemEvent = {
toggle: { event?: Event; id: string; panelId: string; open: boolean; autocollapse: boolean };
};
const dispatch = createEventDispatcher<AccordionItemEvent>();

// Types
import type { CssClasses, Transition, TransitionParams, SvelteEvent } from '../../index.js';
type TransitionIn = $$Generic<Transition>;
type TransitionOut = $$Generic<Transition>;
// Event Dispatcher
const dispatch = createEventDispatcher<AccordionItemEvent>();

// Props (state)
/** Set open by default on load. */
Expand All @@ -38,7 +39,7 @@
// Classes
const cBase = '';
const cControl = 'text-start w-full flex items-center space-x-4';
const cControlCaret = 'fill-current w-3 transition-transform duration-[200ms]';
const cControlIcons = 'fill-current w-3 transition-transform duration-[200ms]';
const cPanel = '';

// Context API
Expand Down Expand Up @@ -66,7 +67,8 @@
export let regionControl: CssClasses = getContext('regionControl');
/** Provide arbitrary classes to content panel region. */
export let regionPanel: CssClasses = getContext('regionPanel');
/** Provide arbitrary classes caret icon region. */
// FIXME: this will need to be renamed `regionIcons` in the future
/** Provide arbitrary classes default region. */
export let regionCaret: CssClasses = getContext('regionCaret');

// Props (transitions)
Expand All @@ -93,6 +95,11 @@
*/
export let transitionOutParams: TransitionParams<TransitionOut> = getContext('transitionOutParams');

const svgCaretIcon = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class={classesControlCaret}>
<path d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z" />
</svg>`;

// Change open behavior based on auto-collapse mode
function setActive(event?: SvelteEvent<MouseEvent, HTMLButtonElement>): void {
if (autocollapse === true) {
Expand Down Expand Up @@ -128,7 +135,8 @@
$: classesBase = `${cBase} ${$$props.class ?? ''}`;
$: classesControl = `${cControl} ${padding} ${hover} ${rounded} ${regionControl}`;
$: classesCaretState = openState ? caretOpen : caretClosed;
$: classesControlCaret = `${cControlCaret} ${regionCaret} ${classesCaretState}`;
$: classesControlCaret = `${cControlIcons} ${regionCaret} ${classesCaretState}`;
$: classesControlIcons = `${cControlIcons} ${regionCaret}`;
$: classesPanel = `${cPanel} ${padding} ${rounded} ${regionPanel}`;
</script>

Expand Down Expand Up @@ -159,15 +167,21 @@
<div class="accordion-summary flex-1">
<slot name="summary">(summary)</slot>
</div>
<!-- Caret -->
<div class="accordion-summary-caret {classesControlCaret}">
<!-- Icons -->
{#if $$slots.iconClosed || $$slots.iconOpen}
<!-- Custom -->
<!-- If a custom icon is provided, do not use rotation -->
<div class="accordion-summary-icons {classesControlIcons}">
{#if openState}
<slot name="iconClosed">{@html svgCaretIcon}</slot>
{:else}
<slot name="iconOpen">{@html svgCaretIcon}</slot>
{/if}
</div>
{:else}
<!-- SVG Caret -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path
d="M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z"
/>
</svg>
</div>
<div class="accordion-summary-caret {classesControlCaret}">{@html svgCaretIcon}</div>
{/if}
</button>
<!-- Panel -->
{#if openState}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import DocsPreview from '$lib/components/DocsPreview/DocsPreview.svelte';
import DocsShell from '$lib/layouts/DocsShell/DocsShell.svelte';
import { DocsFeature, type DocsShellSettings } from '$lib/layouts/DocsShell/types';
import DocsPreview from '$lib/components/DocsPreview/DocsPreview.svelte';
// Components
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
// Utilities
Expand All @@ -10,6 +10,9 @@
import sveldAccordion from '@skeletonlabs/skeleton/components/Accordion/Accordion.svelte?raw&sveld';
import sveldAccordionItem from '@skeletonlabs/skeleton/components/Accordion/AccordionItem.svelte?raw&sveld';

const loremIpsum =
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cupiditate, ipsa inventore, deserunt tempora ullam accusantium sed ipsam iste impedit beatae praesentium quisquam itaque voluptatibus laborum sunt tenetur, minima porro corrupti.';

// Docs Shell
const settings: DocsShellSettings = {
feature: DocsFeature.Component,
Expand Down Expand Up @@ -128,33 +131,23 @@
<p>Enable the <code class="code">autocollapse</code> setting to limit display to one accordion panel at a time.</p>
<svelte:fragment slot="preview">
<Accordion autocollapse class="card p-4 text-token">
<AccordionItem>
<svelte:fragment slot="lead"><i class="fa-solid fa-question text-xl w-6 text-center" /></svelte:fragment>
<svelte:fragment slot="summary"><p class="font-bold">What is Svelte?</p></svelte:fragment>
<AccordionItem open>
<svelte:fragment slot="lead"><i class="fa-solid fa-skull text-xl w-6 text-center" /></svelte:fragment>
<svelte:fragment slot="summary"><p class="font-bold">What is Día de los Muertos?</p></svelte:fragment>
<svelte:fragment slot="content">
<!-- prettier-ignore -->
<p>
In short, Svelte is a way of writing user interface components — like a navigation bar, comment section, or contact form — that users see and interact with in their browsers. The Svelte compiler converts your components to JavaScript that can be run to render the HTML for the page and to CSS that styles the page.
While Halloween and Day of the Dead occur nearly in tandem and share similar customs (candy, face painting, and community gathering), the two are not related. Halloween has ancient Celtic roots, while Day of the Dead has its own origins that date back to the Indigenous people of Mexico and Central America.
</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="lead"><i class="fa-solid fa-calendar text-xl w-6 text-center" /></svelte:fragment>
<svelte:fragment slot="summary"><p class="font-bold">When was it first released?</p></svelte:fragment>
<svelte:fragment slot="lead"><i class="fa-solid fa-clock text-xl w-6 text-center" /></svelte:fragment>
<svelte:fragment slot="summary"><p class="font-bold">When did it begin?</p></svelte:fragment>
<svelte:fragment slot="content">
<!-- prettier-ignore -->
<p>
Version 1 of Svelte was written in JavaScript and was released on 29 November 2016. It was basically Reactive with a compiler. The name Svelte was chosen by Rich Harris and his coworkers at The Guardian.
</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="lead"><i class="fa-solid fa-code text-xl w-6 text-center" /></svelte:fragment>
<svelte:fragment slot="summary"><p class="font-bold">How do I write Svelte code?</p></svelte:fragment>
<svelte:fragment slot="content">
<!-- prettier-ignore -->
<p>
Svelte applications and components are defined in .svelte files, which are HTML files extended with templating syntax that is based on JavaScript and is similar to JSX. Svelte repurposes JavaScript's native labeled statement syntax $: to mark reactive statements. Top-level variables become the component's state and exported variables become the properties that the component receives. Additionally, the JavaScript code syntax can be used for templating in HTML elements and components.
Roughly 3000 years ago, amongst the Aztec, Toltec, and Mayans, death and the dead were seen as a natural part of life that should be honored and celebrated, rather than mourned. In particular, the Nahua people of central Mexico believed the deceased traveled on a years-long journey to Chicunamictlán, the Land of the Dead. The living would provide supplies, such as food and water to aid them on the trek. This practice inspired the modern tradition of creating altars —known as ofrendas— at their homes, in addition to leaving offerings at the gravesites of loved ones.
</p>
</svelte:fragment>
</AccordionItem>
Expand All @@ -180,5 +173,46 @@
</p>
<CodeBlock language="html" code={`<AccordionItem open>...</AccordionItem>`} />
</section>
<section class="space-y-4">
<h2 class="h2">Custom Icons</h2>
<p>
Set the <code class="code">iconOpen</code> and <code class="code">iconClosed</code> slots within the Accordion Item component.
</p>
<DocsPreview background="neutral" regionFooter="text-center">
<svelte:fragment slot="preview">
<Accordion class="card p-4 text-token">
<AccordionItem>
<svelte:fragment slot="summary"><strong>Default: Caret</strong></svelte:fragment>
<svelte:fragment slot="content">{loremIpsum}</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><strong>Custom: Plus and Minus</strong></svelte:fragment>
<svelte:fragment slot="content">{loremIpsum}</svelte:fragment>
<svelte:fragment slot="iconClosed"><i class="fa-solid fa-minus" /></svelte:fragment>
<svelte:fragment slot="iconOpen"><i class="fa-solid fa-plus" /></svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"><strong>Custom: Smiley and Surprise</strong></svelte:fragment>
<svelte:fragment slot="content">{loremIpsum}</svelte:fragment>
<svelte:fragment slot="iconClosed"><i class="fa-solid fa-face-surprise" /></svelte:fragment>
<svelte:fragment slot="iconOpen"><i class="fa-solid fa-face-smile" /></svelte:fragment>
</AccordionItem>
</Accordion>
</svelte:fragment>
<svelte:fragment slot="source">
<blockquote class="blockquote">TIP: abstract both fragments to a single standalone component for reusability.</blockquote>
<CodeBlock
language="html"
code={`
<AccordionItem>
<svelte:fragment slot="iconClosed">(icon closed)</svelte:fragment>
<svelte:fragment slot="iconOpen">(icon open)</svelte:fragment>
<!-- ... -->
</AccordionItem>
`}
/>
</svelte:fragment>
</DocsPreview>
</section>
</svelte:fragment>
</DocsShell>