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

sveltekit components #455

Merged
merged 18 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
12 changes: 6 additions & 6 deletions src/common/tests/Paginator.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { jest } from "@jest/globals";
import { render, screen, fireEvent } from "@testing-library/svelte";
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/svelte";
import { userEvent } from "@testing-library/user-event";

import Paginator from "../Paginator.svelte";
Expand All @@ -26,7 +26,7 @@ describe("Paginator", () => {
const results = render(Paginator, {
props: { has_previous: true, has_next: true },
});
const mockPrevious = jest.fn();
const mockPrevious = vi.fn();
results.component.$on("previous", mockPrevious);
const previous = screen.getByTitle(/Previous/);
await user.click(previous);
Expand All @@ -38,7 +38,7 @@ describe("Paginator", () => {
const results = render(Paginator, {
props: { has_previous: true, has_next: true },
});
const mockNext = jest.fn();
const mockNext = vi.fn();
results.component.$on("next", mockNext);
const next = screen.getByTitle(/Next/);
await user.click(next);
Expand Down Expand Up @@ -90,7 +90,7 @@ describe("Paginator", () => {
has_next: true,
},
});
const mockGoTo = jest.fn();
const mockGoTo = vi.fn();
results.component.$on("goTo", mockGoTo);
const first = screen.getByTitle(/First/);
await user.click(first);
Expand All @@ -115,7 +115,7 @@ describe("Paginator", () => {
has_next: true,
},
});
const mockGoTo = jest.fn();
const mockGoTo = vi.fn();
results.component.$on("goTo", mockGoTo);
const pageNumber = screen.getByRole("spinbutton");
expect(pageNumber).toHaveValue(1);
Expand Down
77 changes: 77 additions & 0 deletions src/lib/components/common/Button.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script lang="ts">
export let href: string = null;
export let mode: "standard" | "primary" | "ghost" = "standard";

export let disabled = false;

export let title: string = "";
export let type: "submit" | "reset" | "button" = "button";
export let label = "Submit";
</script>

{#if href}
<a {href} {title} on:click class={mode}>
<slot>{label}</slot>
</a>
{:else}
<button {title} on:click class={mode} {disabled} {type}>
<slot>{label}</slot>
</button>
{/if}

<style>
a,
button {
cursor: pointer;
display: inline-flex;
padding: 0.375rem 0.75rem;
justify-content: center;
align-items: center;
gap: 0.5rem;

border-radius: 0.5rem;
border: 1px solid var(--gray-4, #5c717c);
background: var(--gray-3, #99a8b3);
box-shadow: 0px 2px 0px 0px var(--gray-4, #5c717c);

color: var(--gray-1, #f5f6f7);
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
fill: var(--gray-1, #f5f6f7);
text-align: center;
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
font-family: var(--font-sans, "Source Sans Pro");
font-size: var(--font-m, 1rem);
font-weight: var(--font-semibold, 600);
}

a {
text-decoration: none;
}

.primary {
border-color: var(--blue-4, #1367d0);
background: var(--primary, #4294f0);
box-shadow: 0px 2px 0px 0px var(--blue-4, #1367d0);
}

.ghost {
background: none;
border: none;
box-shadow: none;
color: var(--primary, #4294f0);
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
fill: var(--primary, #4294f0);
}

.ghost:hover {
background: var(--blue-1, #eef3f9);
}
eyeseast marked this conversation as resolved.
Show resolved Hide resolved

button:disabled,
.disabled {
opacity: 0.5;
cursor: initial;
}

.ghost:disabled {
color: var(--gray-4, #5c717c);
fill: var(--gray-4, #5c717c);
}
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
</style>
104 changes: 104 additions & 0 deletions src/lib/components/common/Checkbox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<script lang="ts">
import { Check16, Dash16 } from "svelte-octicons";

export let disabled = false;
export let checked = false;
export let indeterminate = false;

export let label = "";

let checkbox: HTMLInputElement;
</script>

<label>
<input
type="checkbox"
bind:this={checkbox}
{disabled}
bind:checked
bind:indeterminate
on:input
on:change
/>
<span>
{#if checked}
<Check16 />
{:else if indeterminate}
<Dash16 />
{/if}
</span>
{label}
</label>

<style>
label {
display: flex;
align-items: center;
gap: 0.625rem;
align-self: stretch;
}

input {
opacity: 0;
position: fixed;
z-index: -10;
}

span {
border: solid 1px var(--gray-3, #bbbbbb);
border-radius: 2px;
cursor: pointer;
display: inline-block;
height: 1.25rem;
flex-shrink: 0;
transition:
box-shadow 0.2s ease,
border 0.2s ease;
user-select: none;
width: 1.25em;
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
}

span:hover {
border: solid 1px var(--primary, #4294f0);
}

/* make the svg the same size as its container */
span :global(svg) {
fill: white;
width: 1.25rem;
height: 1.25rem;
}

input:focus + span {
border: solid 1px var(--primary, #4294f0);
}

input:checked + span,
input:indeterminate + span {
background: var(--primary, #4294f0);
border: solid 1px var(--primary, #4294f0);
}

input:checked:focus + span {
box-shadow: 0 0 0 2px var(--primary-faded, rgba(66, 148, 240, 0.13));
border: solid 1px var(--primary, #4294f0);
}

input:disabled + span {
background: rgba(0, 0, 0, 0.05);
cursor: default;
}

input:disabled + span:hover {
border: solid 1px var(--gray-3, #bbbbbb);
}

input:checked:disabled + span {
background: rgba(0, 0, 0, 0.05);
border: solid 1px var(--gray-3, #bbbbbb);
}

input:checked:disabled + span:hover {
border: solid 1px var(--gray-3, #bbbbbb);
}
</style>
51 changes: 51 additions & 0 deletions src/lib/components/common/PageToolbar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!--
@component
PageToolbar floats at the bottom of the page, holding controls like pagination.
It's slotted for composition based on the page it's on.
-->

<div class="toolbar">
<div class="left">
<slot name="left" />
</div>

<div class="center">
<slot name="center" />
</div>

<div class="right">
<slot name="right" />
</div>
</div>

<style>
.toolbar {
display: flex;
height: 2.5rem;
padding: 0.625rem 1rem;
eyeseast marked this conversation as resolved.
Show resolved Hide resolved
justify-content: space-between;
align-items: center;
width: 100%;

border-radius: 0.5rem;
border: 1px solid var(--gray-2, #d8dee2);
background: #fff;
box-shadow: var(--shadow);
}
eyeseast marked this conversation as resolved.
Show resolved Hide resolved

.left, .center, .right {
flex: 1 0 0;
}

.left {
text-align: left;
}

.center {
text-align: center;
}

.right {
text-align: right;
}
</style>
49 changes: 49 additions & 0 deletions src/lib/components/common/stories/Button.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script context="module" lang="ts">
import { Story, Template } from "@storybook/addon-svelte-csf";
import { PlusCircle16 } from "svelte-octicons";
import Button from "../Button.svelte";

export const meta = {
title: "Components / Common / Button",
component: Button,
tags: ["autodocs"],
parameters: { layout: "centered" },
};
</script>

<Template let:args>
<Button {...args}></Button>
</Template>

<Story name="default" />

<Story name="link" args={{ href: "https://www.documentcloud.org" }} />
eyeseast marked this conversation as resolved.
Show resolved Hide resolved

<Story name="primary" args={{ mode: "primary" }} />

<Story name="ghost" args={{ mode: "ghost" }} />

<Story name="slotted">
<Button>
<PlusCircle16 /> Upload
</Button>
</Story>

<Story name="slotted ghost">
<Button mode="ghost">
<PlusCircle16 /> Upload
</Button>
</Story>

<Story name="disabled ghost">
<Button mode="ghost" disabled>
<PlusCircle16 /> Upload
</Button>
</Story>

<Story name="disabled" args={{ disabled: true }} />

<Story
name="link disabled"
args={{ href: "https://www.documentcloud.org", disabled: true }}
/>
23 changes: 23 additions & 0 deletions src/lib/components/common/stories/Checkbox.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script context="module" lang="ts">
import { Story, Template } from "@storybook/addon-svelte-csf";
import Checkbox from "../Checkbox.svelte";

export const meta = {
title: "Components / Common / Checkbox",
component: Checkbox,
tags: ["autodocs"],
parameters: { layout: "centered" },
};
</script>

<Template let:args>
<Checkbox {...args} />
</Template>

<Story name="off" />

<Story name="on" args={{ checked: true }} />

<Story name="some" args={{ indeterminate: true }} />

<Story name="labeled" args={{ label: "Select all" }} />
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script context="module" lang="ts">
import { Story } from "@storybook/addon-svelte-csf";
import KV from "../common/KV.svelte";
import KV from "../KV.svelte";

export const meta = {
title: "Components / Common / KV",
Expand Down
35 changes: 35 additions & 0 deletions src/lib/components/common/stories/PageToolbar.stories.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script context="module" lang="ts">
import { Story } from "@storybook/addon-svelte-csf";
import PageToolbar from "../PageToolbar.svelte";
import Checkbox from "../Checkbox.svelte";
import Paginator from "@/common/Paginator.svelte";

export const meta = {
title: "Components / Common / Page Toolbar",
component: PageToolbar,
tags: ["autodocs"],
parameters: { layout: "centered" },
};
</script>

<Story name="slots">
<PageToolbar>
<p slot="left">Left</p>
<p slot="center">Center</p>
<p slot="right">Right</p>
</PageToolbar>
</Story>

<Story name="paginator">
<PageToolbar>
<Checkbox label="Select all" slot="left" />

<Paginator slot="center" has_next has_previous page={1} totalPages={100} />

<select name="per_page" slot="right">
<option value="25">25 results per page</option>
<option value="50">50 results per page</option>
<option value="100">100 results per page</option>
</select>
</PageToolbar>
</Story>
Loading
Loading