Skip to content
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/toasts.e2e.ts-snapshots/Toast-error-1-Google-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/toasts.e2e.ts-snapshots/Toast-info-1-Google-Chrome-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/toasts.e2e.ts-snapshots/Toast-warn-1-Google-Chrome-linux.png
6 changes: 6 additions & 0 deletions src/docs/constants/docs.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export const COMPONENT_ROUTES: ComponentRoute[] = [
"Checkboxes allow the selection of multiple options from a set of options.",
},

{
path: "/components/chip-group",
title: "Chip Group",
description: "ChipGroup allows users to choose from multiple options.",
},

{
path: "/components/collapsible",
title: "Collapsible",
Expand Down
2 changes: 2 additions & 0 deletions src/lib/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { default as BottomSheet } from "./components/BottomSheet.svelte";
export { default as BusyScreen } from "./components/BusyScreen.svelte";
export { default as Card } from "./components/Card.svelte";
export { default as Checkbox } from "./components/Checkbox.svelte";
export { default as Chip } from "./components/Chip.svelte";
export { default as ChipGroup } from "./components/ChipGroup.svelte";
export { default as Collapsible } from "./components/Collapsible.svelte";
export { default as Content } from "./components/Content.svelte";
export { default as ContentBackdrop } from "./components/ContentBackdrop.svelte";
Expand Down
55 changes: 55 additions & 0 deletions src/lib/components/Chip.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";

export let label: string;
export let id: string;
export let selected: boolean;

const dispatch = createEventDispatcher();
</script>

<button
class="chip"
data-tid="chip-component"
class:selected
role="radio"
aria-checked={selected}
on:click={() => dispatch("nnsClick", id)}>{label}</button
>

<style lang="scss">
@use "../styles/mixins/effect";
@use "../styles/mixins/fonts";
@use "../styles/mixins/button";

.chip {
@include button.base;
@include fonts.small(true);

// Override default button styles
padding: var(--padding) var(--padding-1_5x);
min-height: 0;
border: solid var(--button-border-size) var(--primary);
background: transparent;
color: var(--button-secondary-color);
border-radius: var(--border-radius-5x);

transition: all var(--animation-time-short) ease-in;

&:hover,
&:focus {
background: var(--button-card-focus-background);
}

&.selected {
background: var(--primary);
color: var(--primary-contrast);
pointer-events: none;

&:hover,
&:focus {
background: var(--primary);
}
}
}
</style>
43 changes: 43 additions & 0 deletions src/lib/components/ChipGroup.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts" context="module">
export interface ChipData {
label: string;
id: string;
selected: boolean;
}
</script>

<script lang="ts">
import { createEventDispatcher } from "svelte";
import Chip from "./Chip.svelte";

export let chips: ChipData[] = [];
export let testId: string = "chip-group-component";

const dispatch = createEventDispatcher();
const onChipClick = ({ detail: selectedId }: CustomEvent<string>) => {
chips = chips.map((chip) => ({
...chip,
selected: chip.id === selectedId,
}));
dispatch("nnsSelect", selectedId);
};
</script>

<div
data-tid={testId}
class="chip-group"
role="radiogroup"
aria-label="Options"
>
{#each chips as { id, label, selected } (id)}
<Chip {id} {label} {selected} on:nnsClick={onChipClick} />
{/each}
</div>

<style lang="scss">
.chip-group {
display: flex;
flex-wrap: wrap;
gap: var(--padding-0_5x);
}
</style>
84 changes: 84 additions & 0 deletions src/routes/(split)/components/chip-group/+page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script lang="ts">
import BottomSheet from "$lib/components/BottomSheet.svelte";
import ChipGroup from "$lib/components/ChipGroup.svelte";
import Card from "$lib/components/Card.svelte";
import DocsLoremIpsum from "$docs/components/DocsLoremIpsum.svelte";

const currentMonth = new Date().toLocaleString("en-US", { month: "short" }).toLowerCase();
let chips = [
{ id: "jan", label: "January", selected: currentMonth === "jan" },
{ id: "feb", label: "February", selected: currentMonth === "feb" },
{ id: "mar", label: "March", selected: currentMonth === "mar" },
{ id: "apr", label: "April", selected: currentMonth === "apr" },
{ id: "may", label: "May", selected: currentMonth === "may" },
{ id: "jun", label: "June", selected: currentMonth === "jun" },
{ id: "jul", label: "July", selected: currentMonth === "jul" },
{ id: "aug", label: "August", selected: currentMonth === "aug" },
{ id: "sep", label: "September", selected: currentMonth === "sep" },
{ id: "oct", label: "October", selected: currentMonth === "oct" },
{ id: "nov", label: "November", selected: currentMonth === "nov" },
{ id: "dec", label: "December", selected: currentMonth === "dec" }
];

let nnsSelectDetail = undefined;
const onNnsSelect = ({detail}) => nnsSelectDetail= detail;
</script>

# ChipGroup

ChipGroup allows users to choose from multiple options. It’s possible to listen for the nnsSelect event (where the selected ID is provided in the event detail) or, alternatively, bind the chip property. The current implementation does not support multiple selections.

### ChipData interface

```typescript
export interface ChipData {
label: string;
id: string;
selected: boolean;
}
```

### Sample

```javascript
<script>
// Items of ChipData
let chips = [
{
id: "jan",
label: "January",
selected: false
},
// ...
];
let nnsSelectDetail;
const onNnsSelect = ({detail}) => nnsSelectDetail= detail;
</script>

<ChipGroup bind:chips on:nnsSelect={onNnsSelect} />
```

## Properties

| Property | Description | Type | Default |
| -------- | -------------------------- | ----------------- | ---------------------- |
| `chips` | ChipData items to display. | `Array<ChipData>` | |
| `testId` | Optional data-tid value. | `string` | `chip-group-component` |

## Events

| Event | Description | Detail |
| ----------- | --------------------------------------- | ----------- |
| `nnsSelect` | Triggered when a user clicks on a Chip. | ChipData.id |

## Showcase

<Card>
<ChipGroup on:nnsSelect={onNnsSelect} bind:chips />
</Card>

<h4>nnsSelect event detail:</h4>
<pre>{nnsSelectDetail}</pre>

<h4>bind:chips</h4>
<pre>{JSON.stringify(chips, null, 2)}</pre>
84 changes: 84 additions & 0 deletions src/tests/lib/components/ChipGroup.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import ChipGroup from "$lib/components/ChipGroup.svelte";
import { fireEvent } from "@testing-library/dom";
import { tick } from "svelte";
import { render } from "../../utils/render.test-utils";

describe("ChipGroup", () => {
const testChips = [
{ id: "stake", label: "Stake", selected: false },
{ id: "maturity", label: "Maturity", selected: false },
{ id: "state", label: "State", selected: false },
{ id: "dissolve delay", label: "Dissolve Delay", selected: false },
];

it("should render Chips", () => {
const { getAllByTestId, getByTestId } = render(ChipGroup, {
props: { chips: testChips },
});

expect(getByTestId("chip-group-component")).not.toBeNull();
const chipElements = getAllByTestId("chip-component");
expect(chipElements).toHaveLength(testChips.length);
chipElements.forEach((chip, index) => {
expect(chip.textContent).toEqual(testChips[index].label);
});
});

it("should support initial selection", () => {
const chips = [...testChips];
const selectedIndex = 2;
chips[selectedIndex] = { ...chips[selectedIndex], selected: true };
const { getAllByTestId } = render(ChipGroup, {
props: { chips },
});
const chipElements = getAllByTestId("chip-component");
chipElements.forEach((chip, index) => {
if (index === selectedIndex) {
expect(chip).toHaveClass("selected");
} else {
expect(chip).not.toHaveClass("selected");
}
});
});

it("should update selection on click", async () => {
const selectedIndex = 2;
const { getAllByTestId } = render(ChipGroup, {
props: { chips: testChips },
});

const chipElements = getAllByTestId("chip-component");
chipElements.forEach((chip) => expect(chip).not.toHaveClass("selected"));

fireEvent.click(chipElements[selectedIndex]);
await tick();
chipElements.forEach((chip, index) => {
if (index === selectedIndex) {
expect(chip).toHaveClass("selected");
} else {
expect(chip).not.toHaveClass("selected");
}
});
});

it("should emit nnsSelect", () => {
const spyNnsSelect = vi.fn();
const { getAllByTestId } = render(ChipGroup, {
props: { chips: testChips },
events: {
nnsSelect: spyNnsSelect,
},
});

expect(spyNnsSelect).toHaveBeenCalledTimes(0);
const chipElements = getAllByTestId("chip-component");

fireEvent.click(chipElements[1]);
expect(spyNnsSelect).toHaveBeenCalledTimes(1);
expect(spyNnsSelect).toHaveBeenCalledWith(
expect.objectContaining({
detail: testChips[1].id,
}),
);
});
});
Loading