Skip to content

Commit

Permalink
Add PillContainer component (#2168)
Browse files Browse the repository at this point in the history
* Add reusable PillContainer component to Holocene

* Add count to Pill

* Add focus styles
  • Loading branch information
laurakwhit authored Jul 1, 2024
1 parent 7d20a6a commit 8edb57d
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 46 deletions.
11 changes: 5 additions & 6 deletions src/lib/components/event/event-detail-pills.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import Pill from '$lib/components/pill.svelte';
import PillContainer from '$lib/holocene/pill-container/pill-container.svelte';
import Pill from '$lib/holocene/pill-container/pill.svelte';
import { translate } from '$lib/i18n/translate';
import type { AttributeGrouping } from '$lib/utilities/format-event-attributes';
import { attributeGroupingProperties } from '$lib/utilities/format-event-attributes';
export let attributeGrouping: AttributeGrouping;
export let activePill: string;
const dispatch = createEventDispatcher();
Expand All @@ -21,16 +21,15 @@

{#if pillCount > 1}
<div class="p-2 text-center xl:text-left">
<div class="pill-container">
<PillContainer>
{#each Object.entries(attributeGrouping) as [key, value] (key)}
{@const active = activePill === key}
{#if value.length}
<Pill {active} on:click={() => dispatch('pillChange', { key })}
<Pill id={key} onClick={() => dispatch('pillChange', { key })}
>{translate(attributeGroupingProperties[key].label)}</Pill
>
{/if}
{/each}
</div>
</PillContainer>
</div>
{/if}

Expand Down
12 changes: 2 additions & 10 deletions src/lib/components/event/event-details-full.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,7 @@
onGroupClick={handleGroupClick}
/>
<div class="block w-full lg:w-2/3">
<EventDetailPills
{attributeGrouping}
{activePill}
on:pillChange={handlePillChange}
/>
<EventDetailPills {attributeGrouping} on:pillChange={handlePillChange} />
{#each eventDetails as [key, value] (key)}
{#if attributeGrouping[activePill]?.includes(key)}
<EventDetailsRowExpanded {key} {value} {attributes} class="w-full" />
Expand All @@ -51,11 +47,7 @@
</div>
{:else}
<div class="w-full">
<EventDetailPills
{attributeGrouping}
{activePill}
on:pillChange={handlePillChange}
/>
<EventDetailPills {attributeGrouping} on:pillChange={handlePillChange} />
{#each eventDetails as [key, value] (key)}
{#if attributeGrouping[activePill]?.includes(key)}
<EventDetailsRowExpanded {key} {value} {attributes} class="w-full" />
Expand Down
30 changes: 0 additions & 30 deletions src/lib/components/pill.svelte

This file was deleted.

36 changes: 36 additions & 0 deletions src/lib/holocene/pill-container/pill-container.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts" context="module">
import type { Meta } from '@storybook/svelte';
import PillContainer from './pill-container.svelte';
import Pill from './pill.svelte';
export const meta = {
title: 'Pill Container',
component: PillContainer,
subcomponents: { Pill },
argTypes: {
PILLS: {
name: 'Pill Container',
table: {
disable: true,
},
},
},
} satisfies Meta<Omit<PillContainer, 'PILLS'>>;
</script>

<script lang="ts">
import { Story, Template } from '@storybook/addon-svelte-csf';
</script>

<Template let:args>
<PillContainer {...args}>
<Pill id="A">Pill A</Pill>
<Pill id="B">Pill B</Pill>
<Pill id="C">Pill C</Pill>
</PillContainer>
</Template>

<Story name="Light" />

<Story name="Dark" parameters={{ themes: { themeOverride: 'dark' } }} />
53 changes: 53 additions & 0 deletions src/lib/holocene/pill-container/pill-container.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts" context="module">
export type PillsContext = {
activePill: Writable<string>;
registerPill: (pill: string) => void;
selectPill: (pill: string) => void;
};
export const PILLS = {};
</script>

<script lang="ts">
import type { HTMLAttributes } from 'svelte/elements';
import { type Writable, writable } from 'svelte/store';
import { onDestroy, setContext } from 'svelte';
import { twMerge as merge } from 'tailwind-merge';
type $$Props = HTMLAttributes<HTMLDivElement>;
let className = '';
export { className as class };
const pills: string[] = [];
const activePill = writable<string>(null);
setContext<PillsContext>(PILLS, {
registerPill: (pill: string) => {
pills.push(pill);
activePill.update((current) => current || pill);
onDestroy(() => {
const i = pills.indexOf(pill);
pills.splice(i, 1);
activePill.update((current) =>
current === pill ? pills[i] || pills[pills.length - 1] : current,
);
});
},
selectPill: (pill: string) => {
activePill.set(pill);
},
activePill,
});
</script>

<div
class={merge(
'surface-subtle inline-flex flex-col items-center justify-start gap-2 rounded-md px-2 py-2 md:flex-row md:rounded-full',
className,
)}
>
<slot />
</div>
69 changes: 69 additions & 0 deletions src/lib/holocene/pill-container/pill.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
import { noop } from 'svelte/internal';
import { getContext } from 'svelte';
import { twMerge as merge } from 'tailwind-merge';
import Badge from '$lib/holocene/badge.svelte';
import type { IconName } from '$lib/holocene/icon';
import Icon from '$lib/holocene/icon/icon.svelte';
import { isNull } from '$lib/utilities/is';
import { PILLS, type PillsContext } from './pill-container.svelte';
type $$Props = {
id: string;
onClick?: () => void;
disabled?: boolean;
loading?: boolean;
active?: boolean;
icon?: IconName;
count?: number;
class?: string;
} & HTMLButtonAttributes;
export let id: string;
export let onClick: () => void = noop;
export let disabled = false;
export let loading = false;
export let active: boolean = null;
export let icon: IconName = null;
export let count: number = null;
let className = '';
export { className as class };
const { activePill, registerPill, selectPill } =
getContext<PillsContext>(PILLS);
registerPill(id);
$: isActive = isNull(active) ? $activePill === id : active;
const handleClick = () => {
if (disabled) return;
selectPill(id);
onClick && onClick();
};
</script>

<button
on:click|stopPropagation={handleClick}
class={merge(
'surface-subtle flex items-center justify-center gap-2 rounded-full px-3 py-1 text-sm',
'focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-primary/70',
isActive && 'bg-interactive text-white',
className,
)}
{disabled}
>
{#if icon}
<span class:animate-spin={loading}>
<Icon name={loading ? 'spinner' : icon} />
</span>
{/if}
<slot />
{#if !isNull(count)}
<Badge type="count">{count}</Badge>
{/if}
</button>

0 comments on commit 8edb57d

Please sign in to comment.