post format

This commit is contained in:
Nik
2022-08-17 12:44:58 +10:00
parent c0a23988b2
commit ff6351b04e
140 changed files with 9138 additions and 6794 deletions

View File

@@ -11,7 +11,7 @@ A fully featured Svelte UI component library. Skeleton allows you to build fast
## Sponsorship
* [Github Sponsors](https://github.com/sponsors/Brain-Bones)
- [Github Sponsors](https://github.com/sponsors/Brain-Bones)
## Useful Links
@@ -32,13 +32,13 @@ Ensure you've read the [contribution guide](https://skeleton.brainandbonesllc.co
### Install
* Clone the Skeleton project to your local machine.
* Install dependencies with `npm install`
* Create a new feature branch: `git checkout -b {branchName}`
* Start a dev server:
* `npm run dev` - start server
* `npm run dev -- --open` - start and open new browser window
* Make changes, then submit a pull request when ready.
- Clone the Skeleton project to your local machine.
- Install dependencies with `npm install`
- Create a new feature branch: `git checkout -b {branchName}`
- Start a dev server:
- `npm run dev` - start server
- `npm run dev -- --open` - start and open new browser window
- Make changes, then submit a pull request when ready.
### Linting
@@ -58,8 +58,8 @@ npm run test
## Core Maintainers
* [Chris Simmons](https://github.com/endigo9740)
* [Thomas Jespersen](https://github.com/thomasbjespersen)
- [Chris Simmons](https://github.com/endigo9740)
- [Thomas Jespersen](https://github.com/thomasbjespersen)
---

View File

@@ -2,16 +2,21 @@
@tailwind components;
@tailwind utilities;
html, body { @apply h-screen overflow-hidden; }
body { @apply font-sans bg-surface-100 dark:bg-surface-900 text-black dark:text-white; }
html,
body {
@apply h-screen overflow-hidden;
}
body {
@apply font-sans bg-surface-100 dark:bg-surface-900 text-black dark:text-white;
}
.dark body {
/* Via: https://csshero.org/mesher/ */
background-image:
radial-gradient(at 32% 66%, hsla(183,55%,15%,0.33) 0, transparent 53%),
radial-gradient(at 80% 20%, hsla(232,40%,22%,0.5) 0, transparent 56%),
radial-gradient(at 63% 56%, hsla(182,53%,15%,0.33) 0, transparent 59%);
background-size: cover; background-attachment: fixed;
/* Via: https://csshero.org/mesher/ */
background-image: radial-gradient(at 32% 66%, hsla(183, 55%, 15%, 0.33) 0, transparent 53%),
radial-gradient(at 80% 20%, hsla(232, 40%, 22%, 0.5) 0, transparent 56%),
radial-gradient(at 63% 56%, hsla(182, 53%, 15%, 0.33) 0, transparent 59%);
background-size: cover;
background-attachment: fixed;
}
/* --- Focus --- */
@@ -25,85 +30,129 @@ body { @apply font-sans bg-surface-100 dark:bg-surface-900 text-black dark:text-
/* --- Scrollbars --- */
/* Scrollbars (webkit) */
::-webkit-scrollbar { @apply w-2; }
::-webkit-scrollbar-track { @apply bg-white/20 dark:bg-black/20; }
::-webkit-scrollbar-thumb { @apply rounded-full bg-surface-300 dark:bg-surface-700; }
::-webkit-scrollbar {
@apply w-2;
}
::-webkit-scrollbar-track {
@apply bg-white/20 dark:bg-black/20;
}
::-webkit-scrollbar-thumb {
@apply rounded-full bg-surface-300 dark:bg-surface-700;
}
/* Hide Scrollbars */
.hide-scrollbar::-webkit-scrollbar {
display: none;
display: none;
}
.hide-scrollbar {
-ms-overflow-style: none; /* IE/Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
scrollbar-width: none; /* Firefox */
}
/* --- Typography --- */
/* Headings */
h1 { @apply text-4xl md:text-6xl font-bold; }
h2 { @apply text-3xl md:text-4xl font-bold; }
h3 { @apply text-2xl md:text-3xl font-bold; }
h4 { @apply text-xl md:text-2xl font-bold; }
h5 { @apply text-lg md:text-xl font-bold; }
h6 { @apply text-base md:text-lg font-bold; }
h1 {
@apply text-4xl md:text-6xl font-bold;
}
h2 {
@apply text-3xl md:text-4xl font-bold;
}
h3 {
@apply text-2xl md:text-3xl font-bold;
}
h4 {
@apply text-xl md:text-2xl font-bold;
}
h5 {
@apply text-lg md:text-xl font-bold;
}
h6 {
@apply text-base md:text-lg font-bold;
}
/* Blockquote */
blockquote { @apply border-l-2 border-l-accent-500 pl-2 text-base text-surface-500 dark:text-surface-400; }
blockquote {
@apply border-l-2 border-l-accent-500 pl-2 text-base text-surface-500 dark:text-surface-400;
}
/* Code */
pre:not(.codeblock pre) { @apply bg-surface-700 dark:bg-black/20 text-surface-50 p-4 whitespace-pre-wrap text-base overflow-x-auto rounded; }
code:not(.codeblock code) { @apply bg-surface-500/30 text-black dark:text-white text-xs py-1 px-2 rounded; }
pre:not(.codeblock pre) {
@apply bg-surface-700 dark:bg-black/20 text-surface-50 p-4 whitespace-pre-wrap text-base overflow-x-auto rounded;
}
code:not(.codeblock code) {
@apply bg-surface-500/30 text-black dark:text-white text-xs py-1 px-2 rounded;
}
/* Page Styles */
main p { @apply text-base text-surface-500 dark:text-surface-400; }
main p {
@apply text-base text-surface-500 dark:text-surface-400;
}
main a:not(.comp-button):not(.crumb a):not(.list-row):not(.logo):not(.drawer a) {
@apply text-primary-500 underline underline-offset-4 decoration-primary-500/20 decoration-dotted;
@apply text-primary-500 underline underline-offset-4 decoration-primary-500/20 decoration-dotted;
}
/* --- Forms --- */
fieldset { @apply block; }
fieldset {
@apply block;
}
/* Inputs */
[type="text"],
[type="email"],
[type="url"],
[type="password"],
[type="number"],
[type="date"],
[type="datetime-local"],
[type="month"],
[type="search"],
[type="tel"],
[type="time"],
[type="week"],
[type='text'],
[type='email'],
[type='url'],
[type='password'],
[type='number'],
[type='date'],
[type='datetime-local'],
[type='month'],
[type='search'],
[type='tel'],
[type='time'],
[type='week'],
[multiple],
textarea,
select {
@apply w-full text-black bg-surface-50 border-surface-300 rounded-lg shadow-sm focus:border-accent-500 focus:ring-accent-500;
@apply dark:text-white dark:bg-surface-600 dark:border-surface-500;
@apply w-full text-black bg-surface-50 border-surface-300 rounded-lg shadow-sm focus:border-accent-500 focus:ring-accent-500;
@apply dark:text-white dark:bg-surface-600 dark:border-surface-500;
}
/* Selectable */
[type="checkbox"],
[type="radio"] {
@apply border-surface-500 rounded text-accent-600 focus:ring-accent-500;
[type='checkbox'],
[type='radio'] {
@apply border-surface-500 rounded text-accent-600 focus:ring-accent-500;
}
/* Invalid - https://www.bram.us/2021/01/28/form-validation-you-want-notfocusinvalid-not-invalid/ */
input:not(:focus):not(:placeholder-shown):invalid {
@apply bg-warning-300 border-warning-500;
@apply bg-warning-300 border-warning-500;
}
/* Placeholders */
input::-moz-placeholder, textarea::-moz-placeholder { @apply text-surface-400; }
input:-ms-input-placeholder, textarea:-ms-input-placeholder { @apply text-surface-400; }
input::placeholder, textarea::placeholder { @apply text-surface-400; }
input::-moz-placeholder,
textarea::-moz-placeholder {
@apply text-surface-400;
}
input:-ms-input-placeholder,
textarea:-ms-input-placeholder {
@apply text-surface-400;
}
input::placeholder,
textarea::placeholder {
@apply text-surface-400;
}
/* Labels */
label { @apply block overflow-visible; }
label span, legend { @apply block text-surface-700 dark:text-surface-300 text-sm mb-2; }
label {
@apply block overflow-visible;
}
label span,
legend {
@apply block text-surface-700 dark:text-surface-300 text-sm mb-2;
}
/* Accent Color - https://developer.mozilla.org/en-US/docs/Web/CSS/accent-color */
[type="range"]:not(.range-input) { @apply w-full accent-accent-500; }
[type='range']:not(.range-input) {
@apply w-full accent-accent-500;
}

View File

@@ -5,14 +5,23 @@
<!-- Meta Tags -->
<meta charset="utf-8" />
<meta name="description" content="Skeleton is a fully featured Svelte UI component library that allows you to build fast and reactive web UI using Svelte + Tailwind." />
<meta name="keywords" content="svelte, components, svelte components, ui components, sveltekit, vite, astro, framework">
<meta
name="description"
content="Skeleton is a fully featured Svelte UI component library that allows you to build fast and reactive web UI using Svelte + Tailwind."
/>
<meta
name="keywords"
content="svelte, components, svelte components, ui components, sveltekit, vite, astro, framework"
/>
<meta name="theme-color" content="#22c55e" />
<!-- Open Graph - https://ogp.me/ -->
<meta content="Material Design" property="og:site_name"/>
<meta content="website" property="og:type"/>
<meta name="og:description" content="Skeleton is a fully featured Svelte UI component library that allows you to build fast and reactive web UI using Svelte + Tailwind." />
<meta content="Material Design" property="og:site_name" />
<meta content="website" property="og:type" />
<meta
name="og:description"
content="Skeleton is a fully featured Svelte UI component library that allows you to build fast and reactive web UI using Svelte + Tailwind."
/>
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -26,7 +35,9 @@
<script async src="https://www.googletagmanager.com/gtag/js?id=G-FVKTKV7HDL"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-FVKTKV7HDL');
</script>

View File

@@ -1,30 +1,34 @@
<script lang="ts">
import { onMount } from "svelte";
import { onMount } from 'svelte';
// Props
export let collapse: boolean = true;
export let spacing: string = 'space-y-4';
// Props
export let collapse: boolean = true;
export let spacing: string = 'space-y-4';
let elemAccordian: HTMLElement;
let elemAccordian: HTMLElement;
// Lifecycle
onMount(() => {
if (collapse) {
// Ensure only one detail element is open at a time
// https://nikitahl.com/native-html-accordion
const details = Array.from(elemAccordian.querySelectorAll('details'));
details.forEach((detail) => {
detail.addEventListener('click', (e: any) => {
const active = details.find(d => d.open)
if (!e.currentTarget.open && active) { active.open = false; }
});
});
}
});
// Lifecycle
onMount(() => {
if (collapse) {
// Ensure only one detail element is open at a time
// https://nikitahl.com/native-html-accordion
const details = Array.from(elemAccordian.querySelectorAll('details'));
details.forEach((detail) => {
detail.addEventListener('click', (e: any) => {
const active = details.find((d) => d.open);
if (!e.currentTarget.open && active) {
active.open = false;
}
});
});
}
});
</script>
<div bind:this={elemAccordian} class="accordian-group {spacing} {$$props.classes}" data-testid="accordion-group">
<slot />
</div>
<div
bind:this={elemAccordian}
class="accordian-group {spacing} {$$props.classes}"
data-testid="accordion-group"
>
<slot />
</div>

View File

@@ -10,17 +10,15 @@ import { afterEach, describe, expect, it } from 'vitest';
import AccordionGroup from '$lib/Accordion/AccordionGroup.svelte';
describe('Accordion.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(AccordionGroup);
expect(getByTestId('accordion-group')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(AccordionGroup);
expect(getByTestId('accordion-group')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(AccordionGroup, {spacing: 'space-y-4'});
expect(getByTestId('accordion-group')).toBeTruthy();
});
});
it('Renders with props', async () => {
const { getByTestId } = render(AccordionGroup, { spacing: 'space-y-4' });
expect(getByTestId('accordion-group')).toBeTruthy();
});
});

View File

@@ -1,43 +1,50 @@
<script lang="ts">
// Props
export let open: boolean = false;
export let hover: string = 'hover:bg-primary-500/10';
export let spacing: string = 'space-y-2';
// A11y
export let summaryId: string = undefined;
export let contentId: string = undefined;
// Props
export let open: boolean = false;
export let hover: string = 'hover:bg-primary-500/10';
export let spacing: string = 'space-y-2';
// A11y
export let summaryId: string = undefined;
export let contentId: string = undefined;
// Base Classes
const cBaseDetails: string = '';
const cBaseSummary: string = 'flex items-center space-x-4 px-4 py-2 cursor-pointer rounded';
const cBaseIcon: string = 'flex justify-center items-center w-3 fill-black dark:fill-white transition-all duration-[100ms]';
const cBaseDesc: string = 'px-4 py-2';
// Base Classes
const cBaseDetails: string = '';
const cBaseSummary: string = 'flex items-center space-x-4 px-4 py-2 cursor-pointer rounded';
const cBaseIcon: string =
'flex justify-center items-center w-3 fill-black dark:fill-white transition-all duration-[100ms]';
const cBaseDesc: string = 'px-4 py-2';
// Reactive Classes
$: classesDetails = `${cBaseDetails} ${spacing}`;
$: classesSummary = `${cBaseSummary} ${hover}`;
$: classesIconState = open ? '-rotate-180' : '';
$: classesIcon = `${cBaseIcon} ${classesIconState}`;
$: classesDesc = `${cBaseDesc}`;
// Reactive Classes
$: classesDetails = `${cBaseDetails} ${spacing}`;
$: classesSummary = `${cBaseSummary} ${hover}`;
$: classesIconState = open ? '-rotate-180' : '';
$: classesIcon = `${cBaseIcon} ${classesIconState}`;
$: classesDesc = `${cBaseDesc}`;
</script>
<details bind:open={open} class="accordion-item {classesDetails} {$$props.class}" data-testid="accordion-item">
<details
bind:open
class="accordion-item {classesDetails} {$$props.class}"
data-testid="accordion-item"
>
<!-- Summary -->
<summary id={summaryId} class={classesSummary} aria-expanded={open} aria-controls={contentId}>
<!-- Slot: Lead -->
{#if $$slots.lead}<div><slot name="lead" /></div>{/if}
<!-- Slot: Text -->
<div class="flex-auto" role="button"><slot name="summary" /></div>
<!-- Caret -->
<div class={classesIcon}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"
><path
d="M192 384c-8.188 0-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L192 306.8l137.4-137.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-160 160C208.4 380.9 200.2 384 192 384z"
/></svg
>
</div>
</summary>
<!-- Summary -->
<summary id={summaryId} class="{classesSummary}" aria-expanded={open} aria-controls={contentId}>
<!-- Slot: Lead -->
{#if $$slots.lead}<div><slot name="lead"/></div>{/if}
<!-- Slot: Text -->
<div class="flex-auto" role="button"><slot name="summary"/></div>
<!-- Caret -->
<div class="{classesIcon}">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M192 384c-8.188 0-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L192 306.8l137.4-137.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-160 160C208.4 380.9 200.2 384 192 384z"/></svg>
</div>
</summary>
<!-- Content -->
<div id={contentId} role="region" aria-labelledby={summaryId} class="{classesDesc}">
<slot name="content"/>
</div>
</details>
<!-- Content -->
<div id={contentId} role="region" aria-labelledby={summaryId} class={classesDesc}>
<slot name="content" />
</div>
</details>

View File

@@ -8,29 +8,27 @@ import { afterEach, describe, expect, it } from 'vitest';
/* @ts-ignore */
import AccordionItem from '$lib/Accordion/AccordionItem.svelte';
import { writable, type Writable } from "svelte/store";
import { writable, type Writable } from 'svelte/store';
export let selected: Writable<number[]> = writable([1]);
describe('AccordionItem.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('placeholder', async () => {});
it('placeholder', async () => {});
it('Renders without props', async () => {
const { getByTestId } = render(AccordionItem);
expect(getByTestId('accordion-item')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(AccordionItem);
expect(getByTestId('accordion-item')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(AccordionItem, {
open: true,
hover: 'bg-green-500/50',
spacing: 'space-y-2',
summaryId: 'testSummary1',
contentId: 'testContent1'
});
expect(getByTestId('accordion-item')).toBeTruthy();
});
});
it('Renders with props', async () => {
const { getByTestId } = render(AccordionItem, {
open: true,
hover: 'bg-green-500/50',
spacing: 'space-y-2',
summaryId: 'testSummary1',
contentId: 'testContent1'
});
expect(getByTestId('accordion-item')).toBeTruthy();
});
});

View File

@@ -10,39 +10,45 @@
export let radius: string = 'rounded-lg';
// Base Classes
let cBaseCard = 'flex flex-col items-start lg:items-center lg:flex-row p-5 space-y-4 lg:space-y-0 lg:space-x-4';
let cBaseCard =
'flex flex-col items-start lg:items-center lg:flex-row p-5 space-y-4 lg:space-y-0 lg:space-x-4';
// Reactive Classes
$: classesCard = `${cBaseCard} ${radius} ${$$props.class}`;
</script>
{#if visible}
<div class="alert" transition:fade|local={{duration}} data-testid="alert" role="alert" aria-live="polite">
<Card {background} {color} class="{classesCard}">
<!-- Slot: Lead -->
{#if $$slots.lead}
<section class="flex justify-center items-center lg:min-w-[72px]">
<slot name="lead" />
</section>
{/if}
<!-- Content -->
<section class="flex flex-col w-full justify-center space-y-2">
<!-- Slot: Title -->
<h3><slot name="title">(REQUIRED: Title Slot)</slot></h3>
<!-- Slot: Message -->
{#if $$slots.message}
<div class="{color} opacity-70"><slot name="message" /></div>
<div
class="alert"
transition:fade|local={{ duration }}
data-testid="alert"
role="alert"
aria-live="polite"
>
<Card {background} {color} class={classesCard}>
<!-- Slot: Lead -->
{#if $$slots.lead}
<section class="flex justify-center items-center lg:min-w-[72px]">
<slot name="lead" />
</section>
{/if}
</section>
<!-- Slot: Trail -->
{#if $$slots.trail}
<section class="flex items-center space-x-4">
<slot name='trail'/>
<!-- Content -->
<section class="flex flex-col w-full justify-center space-y-2">
<!-- Slot: Title -->
<h3><slot name="title">(REQUIRED: Title Slot)</slot></h3>
<!-- Slot: Message -->
{#if $$slots.message}
<div class="{color} opacity-70"><slot name="message" /></div>
{/if}
</section>
{/if}
</Card>
</div>
{/if}
<!-- Slot: Trail -->
{#if $$slots.trail}
<section class="flex items-center space-x-4">
<slot name="trail" />
</section>
{/if}
</Card>
</div>
{/if}

View File

@@ -2,23 +2,28 @@
* @vitest-environment jsdom
*/
import { cleanup, render, screen } from '@testing-library/svelte';
import { afterEach, describe, expect, it, test, vi } from 'vitest';
import Alert from '$lib/Alert/Alert.svelte';
import { fireEvent } from '@testing-library/dom';
describe('Alert.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(Alert);
expect(getByTestId('alert')).toBeTruthy();
});
import { cleanup, render, screen } from '@testing-library/svelte';
import { afterEach, describe, expect, it, test, vi } from 'vitest';
import Alert from '$lib/Alert/Alert.svelte';
import { fireEvent } from '@testing-library/dom';
it('Renders with props', async () => {
const { getByTestId } = render(Alert,
{props: {visible: true, color: 'bg-primary-500', radius: 'rounding-lg', textColor: 'text-accent-500' }});
describe('Alert.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(Alert);
expect(getByTestId('alert')).toBeTruthy();
});
});
});
it('Renders with props', async () => {
const { getByTestId } = render(Alert, {
props: {
visible: true,
color: 'bg-primary-500',
radius: 'rounding-lg',
textColor: 'text-accent-500'
}
});
expect(getByTestId('alert')).toBeTruthy();
});
});

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { afterUpdate } from "svelte";
import * as f from "$lib/Filters/filter";
import { afterUpdate } from 'svelte';
import * as f from '$lib/Filters/filter';
// Props
export let initials: string = 'A';
@@ -9,40 +9,74 @@
export let background: string = 'bg-surface-500';
export let color: string = 'text-white';
export let outlined: boolean = false;
export let hover: boolean = false;
export let hover: boolean = false;
export let filter: string = undefined; // filter id
// Base Classes
let cBase: string = 'flex aspect-square text-surface-50 font-semibold justify-center items-center rounded-full overflow-hidden isolate';
let cBase: string =
'flex aspect-square text-surface-50 font-semibold justify-center items-center rounded-full overflow-hidden isolate';
let cSize: string, cText: string;
// Set Size
function setSize(): void {
switch (size) {
case 'sm': cSize = 'w-10'; cText = 'text-base'; break;
case 'md': cSize = 'w-12'; cText = 'text-lg'; break;
case 'lg': cSize = 'w-16'; cText = 'text-2xl'; break;
case 'xl': cSize = 'w-24'; cText = 'text-4xl'; break;
case '2xl': cSize = 'w-32'; cText = 'text-5xl'; break;
case '3xl': cSize = 'w-40'; cText = 'text-6xl'; break;
case 'full': cSize = 'w-full max-w-[400px]'; cText = 'text-base sm:text-2xl md:text-4xl lg:text-6xl xl:text-9xl'; break;
case 'sm':
cSize = 'w-10';
cText = 'text-base';
break;
case 'md':
cSize = 'w-12';
cText = 'text-lg';
break;
case 'lg':
cSize = 'w-16';
cText = 'text-2xl';
break;
case 'xl':
cSize = 'w-24';
cText = 'text-4xl';
break;
case '2xl':
cSize = 'w-32';
cText = 'text-5xl';
break;
case '3xl':
cSize = 'w-40';
cText = 'text-6xl';
break;
case 'full':
cSize = 'w-full max-w-[400px]';
cText = 'text-base sm:text-2xl md:text-4xl lg:text-6xl xl:text-9xl';
break;
}
}
// After Prop Update
afterUpdate(() => { setSize(); });
afterUpdate(() => {
setSize();
});
// Reactive Classes
$: cOutlined = outlined === true ? 'ring ring-2 ring-offset-2 ring-offset-surface-50 dark:ring-offset-surface-900 ring-primary-600' : '';
$: cHover = hover === true ? 'dark:ring-offset-surface-900 hover:ring hover:ring-black dark:hover:ring-white cursor-pointer' : '';
$: cOutlined =
outlined === true
? 'ring ring-2 ring-offset-2 ring-offset-surface-50 dark:ring-offset-surface-900 ring-primary-600'
: '';
$: cHover =
hover === true
? 'dark:ring-offset-surface-900 hover:ring hover:ring-black dark:hover:ring-white cursor-pointer'
: '';
$: classesAvatar = `${cBase} ${cSize} ${background} ${color} ${cOutlined} ${cHover} ${$$props.class}`;
</script>
<figure data-testid='wrapper' on:click class="avatar {classesAvatar}">
<figure data-testid="wrapper" on:click class="avatar {classesAvatar}">
{#if src}
<img class="w-full h-full object-cover" {src} alt={$$props.alt||'avatar'} use:f.filter={filter} />
<img
class="w-full h-full object-cover"
{src}
alt={$$props.alt || 'avatar'}
use:f.filter={filter}
/>
{:else}
<span class="{cText}" data-testid="placeholder">{initials.substring(0,2).toUpperCase()}</span>
<span class={cText} data-testid="placeholder">{initials.substring(0, 2).toUpperCase()}</span>
{/if}
</figure>

View File

@@ -25,14 +25,14 @@ describe('Button.svelte', () => {
expect(getByTestId('wrapper')).toBeTruthy();
});
it('Invalid props (Defaults', async()=>{
it('Invalid props (Defaults', async () => {
const { getByTestId } = render(Avatar, {
props: { size: 'large', outlined: 'none', hover: 'none'}
props: { size: 'large', outlined: 'none', hover: 'none' }
});
expect( getByTestId('wrapper').className.includes('text-4xl'));
expect( getByTestId('wrapper').getAttribute('hover')).eq(null);
expect( getByTestId('wrapper').getAttribute('outlined')).eq(null);
})
expect(getByTestId('wrapper').className.includes('text-4xl'));
expect(getByTestId('wrapper').getAttribute('hover')).eq(null);
expect(getByTestId('wrapper').getAttribute('outlined')).eq(null);
});
it('Image shown', async () => {
const { getByTestId } = render(Avatar, { src: img });

View File

@@ -4,20 +4,21 @@
export let fill: string = 'fill-white';
export let rounded: string = 'rounded';
export let icon: boolean = false;
// Base Classes
let cBaseBadge: string = 'inline-flex items-center space-x-2 text-xs font-semibold';
let cBaseIcon: string = icon ? ' w-[18px] h-[18px] justify-center' : ' px-1.5 py-[3px]';
// If icon, set rounded-full
if (icon) { rounded = 'rounded-full'; }
if (icon) {
rounded = 'rounded-full';
}
// Responsive
$: classesBadge = `${cBaseBadge} ${cBaseIcon} ${background} ${color} ${fill} ${rounded} ${$$props.class}`;
</script>
<span data-testid="badge" class="badge {classesBadge}">
<!-- Slot: Lead -->
{#if $$slots.lead}<div><slot name="lead" /></div>{/if}
@@ -26,7 +27,4 @@
<!-- Slot: Trail -->
{#if $$slots.trail}<div><slot name="trail" /></div>{/if}
</span>

View File

@@ -8,18 +8,15 @@ import { afterEach, describe, expect, it } from 'vitest';
import Badge from '$lib/Badge/Badge.svelte';
describe('Badge.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Badge);
expect(getByTestId('badge')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(Badge, {background: 'bg-red-500'});
expect(getByTestId('badge')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Badge);
expect(getByTestId('badge')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(Badge, { background: 'bg-red-500' });
expect(getByTestId('badge')).toBeTruthy();
});
});

View File

@@ -1,18 +1,22 @@
<script lang="ts">
import {setContext} from 'svelte';
// Props
export let separator: string = `&rsaquo;`;
// A11y
export let label: string = undefined;
import { setContext } from 'svelte';
// Base Classes
const cBaseBreadcrumb: string = 'flex align-center space-x-4';
// Props
export let separator: string = `&rsaquo;`;
// A11y
export let label: string = undefined;
// Context
setContext('separator', separator);
// Base Classes
const cBaseBreadcrumb: string = 'flex align-center space-x-4';
// Context
setContext('separator', separator);
</script>
<div class="breadcrumb {cBaseBreadcrumb} {$$props.class}" data-testid="breadcrumb" aria-label={label}>
<slot />
<div
class="breadcrumb {cBaseBreadcrumb} {$$props.class}"
data-testid="breadcrumb"
aria-label={label}
>
<slot />
</div>

View File

@@ -9,33 +9,31 @@ import Breadcrumb from '$lib/Breadcrumb/Breadcrumb.svelte';
// import Crumb from '$lib/Breadcrumb/Crumb.svelte';
describe('Breadcrumb.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Breadcrumb);
expect(getByTestId('breadcrumb')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Breadcrumb);
expect(getByTestId('breadcrumb')).toBeTruthy();
});
// it('Renders with props', () => {
// const { getByTestId } = render(Breadcrumb, {
// props: {display: 'outlined', color: 'primary', separator: '|'},
// });
// expect(getByTestId('breadcrumb')).toBeTruthy();
// })
// it('Renders with props', () => {
// const { getByTestId } = render(Breadcrumb, {
// props: {display: 'outlined', color: 'primary', separator: '|'},
// });
// expect(getByTestId('breadcrumb')).toBeTruthy();
// })
// Test Nested Components
// https://sveltesociety.dev/recipes/testing-and-debugging/unit-testing-svelte-component/
// NOTE: Throwing error below, so disabling this test for now. We may not need to test like this.
/*
// Test Nested Components
// https://sveltesociety.dev/recipes/testing-and-debugging/unit-testing-svelte-component/
// NOTE: Throwing error below, so disabling this test for now. We may not need to test like this.
/*
stderr | unknown test
<Crumb> was created with unknown prop 'Component'
*/
// it('Renders nested Crumb component', () => {
// const { getByTestId } = render(Crumb, {
// props: { Component: render(Breadcrumb) }
// });
// expect(getByTestId('crumb')).toBeTruthy();
// })
});
// it('Renders nested Crumb component', () => {
// const { getByTestId } = render(Crumb, {
// props: { Component: render(Breadcrumb) }
// });
// expect(getByTestId('crumb')).toBeTruthy();
// })
});

View File

@@ -18,16 +18,19 @@
</script>
<div class="crumb {cBaseCrumb} {$$props.class}" data-testid="crumb">
<!-- Anchor -->
<a {href} class="crumb-anchor {cBaseAnchor} {classesCurrent}" data-testid="crumb-anchor" aria-current={current ? 'page' : undefined}>
{#if $$slots.lead}<slot name="lead"></slot>{/if}
<a
{href}
class="crumb-anchor {cBaseAnchor} {classesCurrent}"
data-testid="crumb-anchor"
aria-current={current ? 'page' : undefined}
>
{#if $$slots.lead}<slot name="lead" />{/if}
<div><slot /></div>
</a>
<!-- Seperator -->
{#if !current}
<div class="separator {cBaseSeperator}">{@html separator}</div>
<div class="separator {cBaseSeperator}">{@html separator}</div>
{/if}
</div>
</div>

View File

@@ -4,21 +4,19 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import Crumb from '$lib/Breadcrumb/Crumb.svelte';
describe('Crumb.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Crumb);
expect(getByTestId('crumb')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Crumb, { href: '/', current: true });
expect(getByTestId('crumb')).toBeTruthy();
})
import Crumb from '$lib/Breadcrumb/Crumb.svelte';
describe('Crumb.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(Crumb);
expect(getByTestId('crumb')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Crumb, { href: '/', current: true });
expect(getByTestId('crumb')).toBeTruthy();
});
});

View File

@@ -1,120 +1,178 @@
<script lang="ts">
import { afterUpdate } from "svelte";
import { afterUpdate } from 'svelte';
// Props
export let variant: string = undefined;
export let size: string = 'base';
export let background: string = 'bg-black dark:bg-white';
export let color: string = 'text-white dark:text-black';
export let fill: string = 'fill-white dark:fill-black';
export let ring: string = 'ring-transparent';
export let weight: string = 'ring-1';
export let width: string = 'w-auto';
export let rounded: string = 'rounded-lg';
// A11y
export let label: string = undefined;
export let describedby: string = undefined;
// Props
export let variant: string = undefined;
export let size: string = 'base';
export let background: string = 'bg-black dark:bg-white';
export let color: string = 'text-white dark:text-black';
export let fill: string = 'fill-white dark:fill-black';
export let ring: string = 'ring-transparent';
export let weight: string = 'ring-1';
export let width: string = 'w-auto';
export let rounded: string = 'rounded-lg';
// A11y
export let label: string = undefined;
export let describedby: string = undefined;
// Set tag and href values
const tag: string = $$props.href ? 'a' : 'button';
const href: any = $$props.href ? `href="${$$props.href}"` : undefined;
const role: string = $$props.href ? 'link' : 'button';
// Set tag and href values
const tag: string = $$props.href ? 'a' : 'button';
const href: any = $$props.href ? `href="${$$props.href}"` : undefined;
const role: string = $$props.href ? 'link' : 'button';
// Base Classes
const cBaseButton: string = 'inline-flex justify-center items-center space-x-2 text-center whitespace-nowrap ring-inset pointer-cursor';
// Set Size
let cSize: string;
function setSize(): void {
switch(size) {
case('none'): cSize = 'text-base'; break;
case('sm'): cSize = 'text-sm px-3 py-2'; break;
case('lg'): cSize = 'text-lg px-6 py-3'; break;
case('xl'): cSize = 'text-xl px-7 py-3.5'; break;
default: cSize = 'text-base px-5 py-2.5'; // base
}
}
// Base Classes
const cBaseButton: string =
'inline-flex justify-center items-center space-x-2 text-center whitespace-nowrap ring-inset pointer-cursor';
// Set Variant Styles
// TODO: refactor and improve this
function setProps(vBackground?: string, vColor?: string, vFill?: string, vRing?: string, vWeight?: string, vSize?: string): void {
if (variant) {
if (vSize) size = vSize;
if (vBackground) background = vBackground;
if (vColor) color = vColor;
if (vFill) fill = vFill;
if (vRing) ring = vRing;
if (vWeight) weight = vWeight;
}
}
function setVariant(): void {
switch(variant) {
// Minimal
case('minimal'): setProps('bg-transparent', 'text-initial', 'fill-initial', null, 'none', 'none'); break;
// Text
case('text'): setProps('bg-transparent', 'text-black dark:text-white', 'fill-black dark:fill-white'); break;
case('text-primary'): setProps('bg-transparent', 'text-primary-500', 'fill-primary-500'); break;
case('text-accent'): setProps('bg-transparent', 'text-accent-500', 'fill-accent-500'); break;
case('text-warning'): setProps('bg-transparent', 'text-warning-500', 'fill-warning-500'); break;
// Filled
case('filled'): setProps(null, null, null); break;
case('filled-primary'): setProps('bg-primary-500', 'text-white', 'fill-white'); break;
case('filled-accent'): setProps('bg-accent-500', 'text-white', 'fill-white'); break;
case('filled-warning'): setProps('bg-warning-500', 'text-white', 'fill-white'); break;
// Ring
case('ring'): setProps(
'bg-transparent', 'text-black dark:text-white', 'fill-black dark:fill-white', 'ring-black dark:ring-white'
); break;
case('ring-primary'): setProps('bg-transparent', 'text-primary-500', 'fill-primary-500', 'ring-primary-500'); break;
case('ring-accent'): setProps('bg-transparent', 'text-accent-500', 'fill-accent-500', 'ring-accent-500'); break;
case('ring-warning'): setProps('bg-transparent', 'text-warning-500', 'fill-warning-500', 'ring-warning-500'); break;
// Ghost
case('ghost'): setProps(
'bg-black/10 dark:bg-white/10', 'text-black dark:text-white', 'fill-black dark:fill-white', 'ring-black dark:ring-white'
); break;
case('ghost-primary'): setProps('bg-primary-500/10', 'text-primary-500', 'fill-primary-500', 'ring-primary-500'); break;
case('ghost-accent'): setProps('bg-accent-500/10', 'text-accent-500', 'fill-accent-500', 'ring-accent-500'); break;
case('ghost-warning'): setProps('bg-warning-500/10', 'text-warning-500', 'fill-warning-500', 'ring-warning-500'); break;
}
}
// Set Size
let cSize: string;
function setSize(): void {
switch (size) {
case 'none':
cSize = 'text-base';
break;
case 'sm':
cSize = 'text-sm px-3 py-2';
break;
case 'lg':
cSize = 'text-lg px-6 py-3';
break;
case 'xl':
cSize = 'text-xl px-7 py-3.5';
break;
default:
cSize = 'text-base px-5 py-2.5'; // base
}
}
// On Init
setSize();
setVariant();
// Set Variant Styles
// TODO: refactor and improve this
function setProps(
vBackground?: string,
vColor?: string,
vFill?: string,
vRing?: string,
vWeight?: string,
vSize?: string
): void {
if (variant) {
if (vSize) size = vSize;
if (vBackground) background = vBackground;
if (vColor) color = vColor;
if (vFill) fill = vFill;
if (vRing) ring = vRing;
if (vWeight) weight = vWeight;
}
}
function setVariant(): void {
switch (variant) {
// Minimal
case 'minimal':
setProps('bg-transparent', 'text-initial', 'fill-initial', null, 'none', 'none');
break;
// Text
case 'text':
setProps('bg-transparent', 'text-black dark:text-white', 'fill-black dark:fill-white');
break;
case 'text-primary':
setProps('bg-transparent', 'text-primary-500', 'fill-primary-500');
break;
case 'text-accent':
setProps('bg-transparent', 'text-accent-500', 'fill-accent-500');
break;
case 'text-warning':
setProps('bg-transparent', 'text-warning-500', 'fill-warning-500');
break;
// Filled
case 'filled':
setProps(null, null, null);
break;
case 'filled-primary':
setProps('bg-primary-500', 'text-white', 'fill-white');
break;
case 'filled-accent':
setProps('bg-accent-500', 'text-white', 'fill-white');
break;
case 'filled-warning':
setProps('bg-warning-500', 'text-white', 'fill-white');
break;
// Ring
case 'ring':
setProps(
'bg-transparent',
'text-black dark:text-white',
'fill-black dark:fill-white',
'ring-black dark:ring-white'
);
break;
case 'ring-primary':
setProps('bg-transparent', 'text-primary-500', 'fill-primary-500', 'ring-primary-500');
break;
case 'ring-accent':
setProps('bg-transparent', 'text-accent-500', 'fill-accent-500', 'ring-accent-500');
break;
case 'ring-warning':
setProps('bg-transparent', 'text-warning-500', 'fill-warning-500', 'ring-warning-500');
break;
// Ghost
case 'ghost':
setProps(
'bg-black/10 dark:bg-white/10',
'text-black dark:text-white',
'fill-black dark:fill-white',
'ring-black dark:ring-white'
);
break;
case 'ghost-primary':
setProps('bg-primary-500/10', 'text-primary-500', 'fill-primary-500', 'ring-primary-500');
break;
case 'ghost-accent':
setProps('bg-accent-500/10', 'text-accent-500', 'fill-accent-500', 'ring-accent-500');
break;
case 'ghost-warning':
setProps('bg-warning-500/10', 'text-warning-500', 'fill-warning-500', 'ring-warning-500');
break;
}
}
// After Update
afterUpdate(() => {
setSize();
setVariant();
});
// On Init
setSize();
setVariant();
// Reactive Classes
$: cDisabled = $$props.disabled ? '!opacity-30 !cursor-not-allowed' : 'hover:brightness-110 transition-transform active:scale-95';
$: classesButton = `${cBaseButton} ${cSize} ${background} ${color} ${fill} ${ring} ${weight} ${width} ${rounded} ${cDisabled}`;
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
// After Update
afterUpdate(() => {
setSize();
setVariant();
});
// Reactive Classes
$: cDisabled = $$props.disabled
? '!opacity-30 !cursor-not-allowed'
: 'hover:brightness-110 transition-transform active:scale-95';
$: classesButton = `${cBaseButton} ${cSize} ${background} ${color} ${fill} ${ring} ${weight} ${width} ${rounded} ${cDisabled}`;
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
</script>
<svelte:element
this={tag}
class="comp-button {classesButton} {$$props.class}"
{href}
data-testid="comp-button"
on:click
{...prunedRestProps()}
{role}
tabindex="0"
aria-label={label}
aria-describedby={describedby}
aria-disabled={$$props.disabled}
this={tag}
class="comp-button {classesButton} {$$props.class}"
{href}
data-testid="comp-button"
on:click
{...prunedRestProps()}
{role}
tabindex="0"
aria-label={label}
aria-describedby={describedby}
aria-disabled={$$props.disabled}
>
<!-- {...$$restProps} -->
{#if $$slots.lead}<span><slot name="lead"></slot></span>{/if}
<span><slot /></span>
{#if $$slots.trail}<span><slot name="trail"></slot></span>{/if}
<!-- {...$$restProps} -->
{#if $$slots.lead}<span><slot name="lead" /></span>{/if}
<span><slot /></span>
{#if $$slots.trail}<span><slot name="trail" /></span>{/if}
</svelte:element>

View File

@@ -2,7 +2,7 @@
* @vitest-environment jsdom
*/
import { cleanup, render, screen } from '@testing-library/svelte'
import { cleanup, render, screen } from '@testing-library/svelte';
import { afterEach, describe, it, vi, expect } from 'vitest';
import { fireEvent } from '@testing-library/dom';
@@ -12,25 +12,23 @@ import Button from '$lib/Button/Button.svelte';
const dti = 'data-testid';
describe('Button.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders with props', async () => {
const { getByTestId } = render(Button);
expect(getByTestId).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(Button);
expect( getByTestId ).toBeTruthy();
})
it('Renders with props', async () => {
const { getByTestId } = render(Button, { props: { variant: 'ghost-primary' } });
expect(getByTestId).toBeTruthy();
});
it('Renders with props', async()=>{
const { getByTestId } = render(Button, {props: { variant: 'ghost-primary' }});
expect( getByTestId ).toBeTruthy();
})
it('On Click', async()=>{
const onClick = vi.fn();
const { getByTestId, component } = render(Button);
component.$on('click', onClick);
await fireEvent.click(screen.getByRole('button'));
it('On Click', async () => {
const onClick = vi.fn();
const { getByTestId, component } = render(Button);
component.$on('click', onClick);
await fireEvent.click(screen.getByRole('button'));
expect(onClick).toBeCalled();
})
})
});
});

View File

@@ -1,27 +1,25 @@
<script lang="ts">
export let background: string = 'bg-surface-200 dark:bg-surface-800 space-y-4';
export let color: string = undefined;
export let background: string = 'bg-surface-200 dark:bg-surface-800 space-y-4';
export let color: string = undefined;
// Base Classes
let cBaseCard: string = `p-4 rounded-lg`;
// Base Classes
let cBaseCard: string = `p-4 rounded-lg`;
// Reactive
$: classesCard = `${cBaseCard} ${background} ${color}`;
// Reactive
$: classesCard = `${cBaseCard} ${background} ${color}`;
</script>
<div class="card {classesCard} {$$props.class}">
<!-- Header -->
{#if $$slots.header}
<header><slot name="header" /></header>
{/if}
<!-- Header -->
{#if $$slots.header}
<header><slot name="header"></slot></header>
{/if}
<!-- Body -->
<slot />
<!-- Footer -->
{#if $$slots.footer}
<footer><slot name="footer"></slot></footer>
{/if}
<!-- Body -->
<slot />
<!-- Footer -->
{#if $$slots.footer}
<footer><slot name="footer" /></footer>
{/if}
</div>

View File

@@ -14,7 +14,5 @@ describe('Card.svelte', () => {
render(Card);
});
it('Renders with props', async () => {
});
it('Renders with props', async () => {});
});

View File

@@ -1,28 +1,32 @@
<script lang="ts">
import hljs from 'highlight.js';
export let language: string = 'plaintext';
export let code: string = '';
export let background: string = 'bg-neutral-900';
import hljs from 'highlight.js';
// Base Classes
let cBaseBlock: string = `text-surface-50 p-4 rounded`;
let cBaseHeader: string = 'text-xs opacity-50 pb-2';
export let language: string = 'plaintext';
export let code: string = '';
export let background: string = 'bg-neutral-900';
function languageFormatter(lang: string): string {
if (lang === 'js') { return 'javascript'; }
return lang;
}
// Base Classes
let cBaseBlock: string = `text-surface-50 p-4 rounded`;
let cBaseHeader: string = 'text-xs opacity-50 pb-2';
// Reactive Classes
$: classesBlock = `${cBaseBlock} ${background}`;
function languageFormatter(lang: string): string {
if (lang === 'js') {
return 'javascript';
}
return lang;
}
// Reactive Classes
$: classesBlock = `${cBaseBlock} ${background}`;
</script>
{#if language && code}
<div class="codeblock {classesBlock} {$$props.class}" data-testid="codeblock">
<header class="{cBaseHeader}">{languageFormatter(language)}</header>
<pre class="whitespace-pre-wrap break-all text-sm">
<code class="language-{language} outline-none" contenteditable spellcheck="false">{@html hljs.highlight(code, { language }).value || code}</code>
<div class="codeblock {classesBlock} {$$props.class}" data-testid="codeblock">
<header class={cBaseHeader}>{languageFormatter(language)}</header>
<pre class="whitespace-pre-wrap break-all text-sm">
<code class="language-{language} outline-none" contenteditable spellcheck="false"
>{@html hljs.highlight(code, { language }).value || code}</code
>
</pre>
</div>
</div>
{/if}

View File

@@ -2,27 +2,26 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
// @ts-ignore
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
describe('CodeBlock.svelte', () => {
afterEach(() => cleanup());
it('Renders with props', async () => {
const {getByTestId} = render(CodeBlock, {props: {language: 'html', code: '<div></div>'}});
expect(getByTestId('codeblock')).toBeTruthy();
})
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
// FIXME: Since both props needs to be set for the element to be created per the Svelte file,
// I am unsure if this should pass or fail. Should a user be able to create an empty code block as a placeholder,
// or is the code needed?
// @ts-ignore
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
// it('Renders without props (Default)', async ()=>{
// const {getByTestId} = render(CodeBlock);
// expect(getByTestId('codeblock')).toBeTruthy();
// })
});
describe('CodeBlock.svelte', () => {
afterEach(() => cleanup());
it('Renders with props', async () => {
const { getByTestId } = render(CodeBlock, { props: { language: 'html', code: '<div></div>' } });
expect(getByTestId('codeblock')).toBeTruthy();
});
// FIXME: Since both props needs to be set for the element to be created per the Svelte file,
// I am unsure if this should pass or fail. Should a user be able to create an empty code block as a placeholder,
// or is the code needed?
// it('Renders without props (Default)', async ()=>{
// const {getByTestId} = render(CodeBlock);
// expect(getByTestId('codeblock')).toBeTruthy();
// })
});

View File

@@ -1,93 +1,100 @@
<!-- https://www.w3.org/wiki/CSS/Properties/color/keywords -->
<script lang="ts">
import { afterUpdate } from 'svelte';
// import colors from 'tailwindcss/colors';
export let data: any[] = [
{ label: 'Progress', swathe: { color: 'slate', weight: 500 }, start: 0, end: 100 }
];
export let legend: boolean = false;
export let width: string = 'w-full';
// Data
let currentCone: string;
let currentLegend: any[];
// Styles
const cBase: string = 'inline-block aspect-square rounded-full';
// Generate Conic Gradient style
function genConicGradient(): void {
let d: any = data.map((v) => {
// -- FIXME: temporary solution ---
// // Set Color
// let c: string;
// switch (v.swathe.color) {
// case('white'): c = colors.white; break;
// case('black'): c = colors.black; break;
// case('transparent'): c = 'transparent'; break;
// default: c = colors[v.swathe.color][v.swathe.weight];
// }
// // Return mapped value
// return `${c} ${v.start}% ${v.end}%`;
return `${v.swathe.color} ${v.start}% ${v.end}%`;
});
currentCone = `conic-gradient(${d.join(', ')})`;
}
// Generate Legend
function genLegend(): any {
if (!legend) {
return;
}
currentLegend = data.map((v) => {
return {
label: v.label,
// -- FIXME: temporary solution ---
// swatch: colors[v.swathe.color][v.swathe.weight],
swatch: v.swathe.color,
value: v.end - v.start
};
});
}
// Reactive
afterUpdate(() => {
genConicGradient();
genLegend();
});
$: classes = `${cBase} ${width} ${$$props.class}`;
</script>
<!-- https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient -->
<!-- https://www.sitepoint.com/create-css-conic-gradients-pie-charts/ -->
<!-- https://css-tricks.com/radial-gradient-recipes/ -->
<!-- https://tailwindcss.com/docs/configuration#referencing-in-java-script -->
<!-- Colors -->
<!-- https://www.w3.org/wiki/CSS/Properties/color/keywords -->
<script lang="ts">
import { afterUpdate } from 'svelte';
// import colors from 'tailwindcss/colors';
export let data: any[] = [{label: 'Progress', swathe: {color: 'slate', weight: 500}, start: 0, end: 100}];
export let legend: boolean = false;
export let width: string = "w-full";
// Data
let currentCone: string;
let currentLegend: any[];
// Styles
const cBase: string = 'inline-block aspect-square rounded-full';
// Generate Conic Gradient style
function genConicGradient(): void {
let d: any = data.map(v => {
// -- FIXME: temporary solution ---
// // Set Color
// let c: string;
// switch (v.swathe.color) {
// case('white'): c = colors.white; break;
// case('black'): c = colors.black; break;
// case('transparent'): c = 'transparent'; break;
// default: c = colors[v.swathe.color][v.swathe.weight];
// }
// // Return mapped value
// return `${c} ${v.start}% ${v.end}%`;
return `${v.swathe.color} ${v.start}% ${v.end}%`;
});
currentCone = `conic-gradient(${d.join(', ')})`;
}
// Generate Legend
function genLegend(): any {
if (!legend) { return; }
currentLegend = data.map(v => {
return {
label: v.label,
// -- FIXME: temporary solution ---
// swatch: colors[v.swathe.color][v.swathe.weight],
swatch: v.swathe.color,
value: v.end - v.start
};
});
}
// Reactive
afterUpdate(() => {
genConicGradient();
genLegend();
});
$: classes = `${cBase} ${width} ${$$props.class}`;
</script>
<!-- <pre>{JSON.stringify(currentCone, null, 2)}</pre> -->
<figure class="conic-gradient text-center space-y-4" data-testid="conic-gradient">
<!-- Conic Shape -->
{#if currentCone}
<div class="{classes}" style:background={currentCone}></div>
{/if}
<!-- Slot -->
{#if $$slots.default}
<figcaption><slot /></figcaption>
{/if}
<!-- Legend -->
{#if legend && currentLegend}
<figcaption class="text-sm">
<ul class="space-y-4">
{#each currentLegend as {swatch, label, value}}
<li class="flex justify-between">
<div class="flex space-x-4">
<span class="block aspect-square bg-black w-5 rounded-full mr-2" style:background={swatch} />
<strong>{label}</strong>
</div>
<span>{value}%</span>
</li>
{/each}
</ul>
</figcaption>
{/if}
<!-- Conic Shape -->
{#if currentCone}
<div class={classes} style:background={currentCone} />
{/if}
<!-- Slot -->
{#if $$slots.default}
<figcaption><slot /></figcaption>
{/if}
<!-- Legend -->
{#if legend && currentLegend}
<figcaption class="text-sm">
<ul class="space-y-4">
{#each currentLegend as { swatch, label, value }}
<li class="flex justify-between">
<div class="flex space-x-4">
<span
class="block aspect-square bg-black w-5 rounded-full mr-2"
style:background={swatch}
/>
<strong>{label}</strong>
</div>
<span>{value}%</span>
</li>
{/each}
</ul>
</figcaption>
{/if}
</figure>

View File

@@ -8,27 +8,25 @@ import { afterEach, describe, expect, it } from 'vitest';
import ConicGradient from '$lib/ConicGradient/ConicGradient.svelte';
describe('ConicGradient.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(ConicGradient);
expect(getByTestId('conic-gradient')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(ConicGradient);
expect(getByTestId('conic-gradient')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(ConicGradient, {
props: {
data: [
{label: 'Emerald', swathe: {color: 'emerald', weight: 500}, start: 0, end: 35},
{label: 'Indigo', swathe: {color: 'indigo', weight: 500}, start: 35, end: 60},
{label: 'Rose', swathe: {color: 'rose', weight: 500}, start: 60, end: 100},
],
legend: true,
width: 'w-8',
},
});
expect(getByTestId('conic-gradient')).toBeTruthy();
})
});
it('Renders with props', () => {
const { getByTestId } = render(ConicGradient, {
props: {
data: [
{ label: 'Emerald', swathe: { color: 'emerald', weight: 500 }, start: 0, end: 35 },
{ label: 'Indigo', swathe: { color: 'indigo', weight: 500 }, start: 35, end: 60 },
{ label: 'Rose', swathe: { color: 'rose', weight: 500 }, start: 60, end: 100 }
],
legend: true,
width: 'w-8'
}
});
expect(getByTestId('conic-gradient')).toBeTruthy();
});
});

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { afterUpdate } from "svelte";
import { afterUpdate } from 'svelte';
// Props
export let weight: number = 1;
@@ -7,30 +7,38 @@
export let orientation: string = 'h';
// Base Classes
const cBaseDivider = `block border-surface-300 dark:border-surface-700`;
const cBaseDivider = `block border-surface-300 dark:border-surface-700`;
// Set Variant
// Set Variant
let cVariant: string;
function setVariant(): void {
switch(variant) {
case('dashed'): cVariant = 'border-dashed'; break;
case('dotted'): cVariant = 'border-dotted'; break;
default: cVariant = 'border-solid';
switch (variant) {
case 'dashed':
cVariant = 'border-dashed';
break;
case 'dotted':
cVariant = 'border-dotted';
break;
default:
cVariant = 'border-solid';
}
}
// Set Orientation
const cWeight = {
1: {v: 'border-l', h: 'border-t'},
2: {v: 'border-l-2', h: 'border-t-2'},
4: {v: 'border-l-4', h: 'border-t-4'},
8: {v: 'border-l-8', h: 'border-t-8'},
1: { v: 'border-l', h: 'border-t' },
2: { v: 'border-l-2', h: 'border-t-2' },
4: { v: 'border-l-4', h: 'border-t-4' },
8: { v: 'border-l-8', h: 'border-t-8' }
};
let cOrientation: string;
function setOrientation(): void {
switch(orientation) {
case('v'): cOrientation = `border-0 ${cWeight[weight].v} h-full`; break;
default: cOrientation = `border-0 ${cWeight[weight].h} border-t w-full`;
switch (orientation) {
case 'v':
cOrientation = `border-0 ${cWeight[weight].v} h-full`;
break;
default:
cOrientation = `border-0 ${cWeight[weight].h} border-t w-full`;
}
}
@@ -41,7 +49,7 @@
});
// Reactive Classes
$: classes = `${cBaseDivider} ${cVariant} ${cOrientation}`;
$: classes = `${cBaseDivider} ${cVariant} ${cOrientation}`;
</script>
<hr class="divider {classes} {$$props.class}" data-testid='divider' />
<hr class="divider {classes} {$$props.class}" data-testid="divider" />

View File

@@ -20,7 +20,7 @@ describe('Divider.svelte', () => {
it('Renders without props (Defaults)', async () => {
const { getByTestId } = render(Divider);
expect(getByTestId('divider')).toBeTruthy();
expect(getByTestId('divider').className).toContain('border-solid')
expect(getByTestId('divider').className).toContain('border-t')
expect(getByTestId('divider').className).toContain('border-solid');
expect(getByTestId('divider').className).toContain('border-t');
});
});

View File

@@ -1,57 +1,70 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { Writable } from 'svelte/store';
import { fade } from 'svelte/transition';
import type { Writable } from 'svelte/store';
// Props
export let visible: Writable<boolean> = undefined;
export let fixed: string = undefined;
export let backdrop: string = 'bg-white/50 dark:bg-black/50';
export let background: string = 'bg-surface-50 dark:bg-surface-900';
export let border: string = 'border-r border-surface-200 dark:border-surface-800';
// Props
export let visible: Writable<boolean> = undefined;
export let fixed: string = undefined;
export let backdrop: string = 'bg-white/50 dark:bg-black/50';
export let background: string = 'bg-surface-50 dark:bg-surface-900';
export let border: string = 'border-r border-surface-200 dark:border-surface-800';
// Base Classes
const cBaseBackdrop: string = 'lg:hidden fixed top-0 left-0 right-0 bottom-0 z-30';
const cBaseDrawer: string = 'flex flex-col w-[280px] h-screen';
// Set Fixed Position
let cFixed: string;
let cPosition: string;
if (fixed !== undefined) {
cFixed = 'flex-none fixed lg:static top-0 z-40 shadow-2xl lg:shadow-none lg:translate-x-0 transition-transform';
switch(fixed) {
case('left'): cPosition = 'left-0 -translate-x-full'; break;
case('right'): cPosition = 'right-0 translate-x-full'; break;
}
}
// Base Classes
const cBaseBackdrop: string = 'lg:hidden fixed top-0 left-0 right-0 bottom-0 z-30';
const cBaseDrawer: string = 'flex flex-col w-[280px] h-screen';
// Handle Backdrop Click
const onBackdropClick = () => { visible.set(false); }
// Set Fixed Position
let cFixed: string;
let cPosition: string;
if (fixed !== undefined) {
cFixed =
'flex-none fixed lg:static top-0 z-40 shadow-2xl lg:shadow-none lg:translate-x-0 transition-transform';
switch (fixed) {
case 'left':
cPosition = 'left-0 -translate-x-full';
break;
case 'right':
cPosition = 'right-0 translate-x-full';
break;
}
}
// Reactive Classes
$: classesDrawer = `${cBaseDrawer} ${background} ${cFixed} ${cPosition} ${border}`;
$: classesBackdrop = `${cBaseBackdrop} ${backdrop}`;
// Handle Backdrop Click
const onBackdropClick = () => {
visible.set(false);
};
// Reactive Classes
$: classesDrawer = `${cBaseDrawer} ${background} ${cFixed} ${cPosition} ${border}`;
$: classesBackdrop = `${cBaseBackdrop} ${backdrop}`;
</script>
<div class="drawer {classesDrawer} {$$props.class}" class:translate-x-0={$visible} data-testid="drawer">
<div
class="drawer {classesDrawer} {$$props.class}"
class:translate-x-0={$visible}
data-testid="drawer"
>
<!-- Header -->
{#if $$slots.header}
<header class="flex-none"><slot name="header" /></header>
{/if}
<!-- Header -->
{#if $$slots.header}
<header class="flex-none"><slot name="header"></slot></header>
{/if}
<!-- Main -->
{#if $$slots.main}
<section class="flex-auto overflow-y-auto"><slot name="main"></slot></section>
{/if}
<!-- Footer -->
{#if $$slots.footer}
<footer class="flex-none"><slot name="footer"></slot></footer>
{/if}
<!-- Main -->
{#if $$slots.main}
<section class="flex-auto overflow-y-auto"><slot name="main" /></section>
{/if}
<!-- Footer -->
{#if $$slots.footer}
<footer class="flex-none"><slot name="footer" /></footer>
{/if}
</div>
<!-- Backdrop -->
{#if $visible}
<div class="drawer-backdrop {classesBackdrop}" on:click={onBackdropClick} transition:fade|local={{duration: 250}}></div>
{/if}
<div
class="drawer-backdrop {classesBackdrop}"
on:click={onBackdropClick}
transition:fade|local={{ duration: 250 }}
/>
{/if}

View File

@@ -9,19 +9,17 @@ import { afterEach, describe, it, expect } from 'vitest';
import Drawer from '$lib/Drawer/Drawer.svelte';
describe('Drawer.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders with props', async () => {
const { getByTestId } = render(Drawer, {
props: { fixed: true, visible: writable(false) }
});
expect(getByTestId('drawer')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(Drawer, {
props: { fixed: true, visible: writable(false) }
});
expect(getByTestId('drawer')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Drawer);
expect(getByTestId('drawer')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Drawer);
expect(getByTestId('drawer')).toBeTruthy();
});
});

View File

@@ -1,47 +1,44 @@
/**
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import Emerald from '$lib/Filters/svg/Emerald.svelte';
import BlueNight from '$lib/Filters/svg/BlueNight.svelte';
import XPro from '$lib/Filters/svg/XPro.svelte';
import Summer84 from '$lib/Filters/svg/Summer84.svelte';
import Rustic from '$lib/Filters/svg/Rustic.svelte';
import Apollo from '$lib/Filters/svg/Apollo.svelte';
import GreenFall from '$lib/Filters/svg/GreenFall.svelte';
import Noir from '$lib/Filters/svg/Noir.svelte';
import NoirLight from '$lib/Filters/svg/NoirLight.svelte';
import {filter} from '$lib/Filters/filter';
describe('Filter.svelte', () => {
afterEach(() => cleanup());
import Emerald from '$lib/Filters/svg/Emerald.svelte';
import BlueNight from '$lib/Filters/svg/BlueNight.svelte';
import XPro from '$lib/Filters/svg/XPro.svelte';
import Summer84 from '$lib/Filters/svg/Summer84.svelte';
import Rustic from '$lib/Filters/svg/Rustic.svelte';
import Apollo from '$lib/Filters/svg/Apollo.svelte';
import GreenFall from '$lib/Filters/svg/GreenFall.svelte';
import Noir from '$lib/Filters/svg/Noir.svelte';
import NoirLight from '$lib/Filters/svg/NoirLight.svelte';
import { filter } from '$lib/Filters/filter';
// Test that all filters are hidden
it('Class hidden', async ()=>{
render(Emerald);
render(BlueNight);
render(XPro);
render(Summer84);
render(Rustic);
render(Apollo);
render(GreenFall);
render(NoirLight, Noir);
let elements: HTMLCollection = document.getElementsByClassName('filter');
for(let i = 0; i < elements.length; ++i) {
expect(elements[i].getAttribute('class').includes('hidden'));
}
})
describe('Filter.svelte', () => {
afterEach(() => cleanup());
// Test the node gets the filter url
it('Actions: Filter', async ()=>{
const elem: HTMLElement = document.createElement('div');
filter(elem, 'XPro');
expect(elem.getAttribute('style').includes('filter: url("#Emerald")'));
})
// Test that all filters are hidden
it('Class hidden', async () => {
render(Emerald);
render(BlueNight);
render(XPro);
render(Summer84);
render(Rustic);
render(Apollo);
render(GreenFall);
render(NoirLight, Noir);
let elements: HTMLCollection = document.getElementsByClassName('filter');
for (let i = 0; i < elements.length; ++i) {
expect(elements[i].getAttribute('class').includes('hidden'));
}
});
});
// Test the node gets the filter url
it('Actions: Filter', async () => {
const elem: HTMLElement = document.createElement('div');
filter(elem, 'XPro');
expect(elem.getAttribute('style').includes('filter: url("#Emerald")'));
});
});

View File

@@ -1,21 +1,20 @@
// Filter
// Export SVG Components
export {default as Apollo} from '$lib/Filters/svg/Apollo.svelte';
export {default as BlueNight} from '$lib/Filters/svg/BlueNight.svelte';
export {default as Emerald} from '$lib/Filters/svg/Emerald.svelte';
export {default as GreenFall} from '$lib/Filters/svg/GreenFall.svelte';
export {default as Noir} from '$lib/Filters/svg/Noir.svelte';
export {default as NoirLight} from '$lib/Filters/svg/NoirLight.svelte';
export {default as Rustic} from '$lib/Filters/svg/Rustic.svelte';
export {default as Summer84} from '$lib/Filters/svg/Summer84.svelte';
export {default as XPro} from '$lib/Filters/svg/XPro.svelte';
export { default as Apollo } from '$lib/Filters/svg/Apollo.svelte';
export { default as BlueNight } from '$lib/Filters/svg/BlueNight.svelte';
export { default as Emerald } from '$lib/Filters/svg/Emerald.svelte';
export { default as GreenFall } from '$lib/Filters/svg/GreenFall.svelte';
export { default as Noir } from '$lib/Filters/svg/Noir.svelte';
export { default as NoirLight } from '$lib/Filters/svg/NoirLight.svelte';
export { default as Rustic } from '$lib/Filters/svg/Rustic.svelte';
export { default as Summer84 } from '$lib/Filters/svg/Summer84.svelte';
export { default as XPro } from '$lib/Filters/svg/XPro.svelte';
// Action
export function filter (node: HTMLElement, filter_name: string): void {
const isFirefox: boolean = navigator.userAgent.indexOf('Firefox') > -1;
if (!isFirefox) {
node.setAttribute('style', `filter: url("#${filter_name}")`);
}
export function filter(node: HTMLElement, filter_name: string): void {
const isFirefox: boolean = navigator.userAgent.indexOf('Firefox') > -1;
if (!isFirefox) {
node.setAttribute('style', `filter: url("#${filter_name}")`);
}
}

View File

@@ -1,12 +1,19 @@
<svg id="svg-filter-apollo" class="filter hidden">
<!-- Apollo: `filter: url(#Apollo)` -->
<filter id="Apollo" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feColorMatrix values="
<!-- Apollo: `filter: url(#Apollo)` -->
<filter
id="Apollo"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feColorMatrix
values="
0.8 0.6 -0.4 0.1 0,
0 1.2 0.222 0 0,
0 -1 3 0.02 0,
0 0 0 50 0" result="final" in="SourceGraphic"></feColorMatrix>
</filter>
</svg>
0 0 0 50 0"
result="final"
in="SourceGraphic"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 414 B

After

Width:  |  Height:  |  Size: 411 B

View File

@@ -1,13 +1,17 @@
<svg id="svg-filter-bluenight" class="filter hidden">
<!-- BlueNight: `filter: url(#BlueNight)` -->
<filter id="BlueNight" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feColorMatrix
type="matrix"
values=" 1.000 0.000 0.000 0.000 0.000
<!-- BlueNight: `filter: url(#BlueNight)` -->
<filter
id="BlueNight"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feColorMatrix
type="matrix"
values=" 1.000 0.000 0.000 0.000 0.000
0.000 1.000 0.000 0.000 0.05
0.000 0.000 1.000 0.000 0.400
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
0.000 0.000 0.000 1.000 0.000"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 503 B

View File

@@ -1,22 +1,30 @@
<svg id="svg-filter-emerald" class="filter hidden">
<!-- Emerald: `filter: url(#Emerald)` -->
<filter id="Emerald" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<!-- RGB, RGB, RGB, 0pacity -->
<feColorMatrix type="matrix" in="SourceGraphic" result="colormatrix" values="
<!-- Emerald: `filter: url(#Emerald)` -->
<filter
id="Emerald"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<!-- RGB, RGB, RGB, 0pacity -->
<feColorMatrix
type="matrix"
in="SourceGraphic"
result="colormatrix"
values="
.16 .185 .129 0 0
.16 .185 .129 0 0
.16 .185 .129 0 0
0 0 0 0.33 0" />
<feComponentTransfer in="colormatrix" result="componentTransfer">
<feFuncR type="table" tableValues="0.03 0.9"/>
<feFuncG type="table" tableValues="0.57 1"/>
<feFuncB type="table" tableValues="0.49 0.53"/>
<feFuncA type="table" tableValues="0 1"/>
0 0 0 0.33 0"
/>
</feComponentTransfer>
<feComponentTransfer in="colormatrix" result="componentTransfer">
<feFuncR type="table" tableValues="0.03 0.9" />
<feFuncG type="table" tableValues="0.57 1" />
<feFuncB type="table" tableValues="0.49 0.53" />
<feFuncA type="table" tableValues="0 1" />
</feComponentTransfer>
<feBlend mode="normal" in="componentTransfer" in2="SourceGraphic" result="blend"/>
</filter>
<feBlend mode="normal" in="componentTransfer" in2="SourceGraphic" result="blend" />
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 927 B

After

Width:  |  Height:  |  Size: 871 B

View File

@@ -1,9 +1,26 @@
<svg id="svg-filter-greenfall" class="filter hidden">
<filter id="GreenFall" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB">
<feColorMatrix type="matrix" values="0.5 -0.4 0.3332 0 0
<filter
id="GreenFall"
x="-20%"
y="-20%"
width="140%"
height="140%"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="linearRGB"
>
<feColorMatrix
type="matrix"
values="0.5 -0.4 0.3332 0 0
0 0.4 0.3 0 0
0 0 0.5 0 0
0 0 0 500 -20"
x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" result="colormatrix"/>
</filter>
0 0 0 500 -20"
x="0%"
y="0%"
width="100%"
height="100%"
in="SourceGraphic"
result="colormatrix"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 474 B

After

Width:  |  Height:  |  Size: 498 B

View File

@@ -1,12 +1,56 @@
<svg id='svg-filter-noir' class='filter hidden'>
<filter id="Noir" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB">
<feColorMatrix type="saturate" values="0" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" result="colormatrix1"/>
<feBlend mode="lighten" x="0%" y="0%" width="100%" height="100%" in="colormatrix1" in2="colormatrix1" result="blend"/>
<feDiffuseLighting surfaceScale="7.7" diffuseConstant="7.3" lighting-color="#707070" x="0%" y="0%" width="100%" height="100%" in="blend" result="diffuseLighting">
<fePointLight x="200" y="157" z="200"/>
</feDiffuseLighting>
<feBlend mode="multiply" x="0%" y="0%" width="100%" height="100%" in="colormatrix1" in2="diffuseLighting" result="blend1"/>
</filter>
</svg>
<svg id="svg-filter-noir" class="filter hidden">
<filter
id="Noir"
x="-20%"
y="-20%"
width="140%"
height="140%"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="linearRGB"
>
<feColorMatrix
type="saturate"
values="0"
x="0%"
y="0%"
width="100%"
height="100%"
in="SourceGraphic"
result="colormatrix1"
/>
<feBlend
mode="lighten"
x="0%"
y="0%"
width="100%"
height="100%"
in="colormatrix1"
in2="colormatrix1"
result="blend"
/>
<feDiffuseLighting
surfaceScale="7.7"
diffuseConstant="7.3"
lighting-color="#707070"
x="0%"
y="0%"
width="100%"
height="100%"
in="blend"
result="diffuseLighting"
>
<fePointLight x="200" y="157" z="200" />
</feDiffuseLighting>
<feBlend
mode="multiply"
x="0%"
y="0%"
width="100%"
height="100%"
in="colormatrix1"
in2="diffuseLighting"
result="blend1"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 972 B

View File

@@ -1,9 +1,62 @@
<svg id='svg-filter-noirlight' class='filter hidden'>
<filter id="NoirLight" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB">
<feColorMatrix type="saturate" values="0" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" result="colormatrix2"/>
<feBlend mode="saturation" x="0%" y="0%" width="100%" height="100%" in="SourceGraphic" in2="colormatrix2" result="blend2"/>
<feBlend mode="screen" x="0%" y="0%" width="100%" height="100%" in="colormatrix2" in2="blend2" result="blend3"/>
<feColorMatrix type="luminanceToAlpha" x="0%" y="0%" width="100%" height="100%" in="blend3" result="colormatrix3"/>
<feBlend mode="exclusion" x="0%" y="0%" width="100%" height="100%" in="blend3" in2="colormatrix3" result="blend5"/>
</filter>
</svg>
<svg id="svg-filter-noirlight" class="filter hidden">
<filter
id="NoirLight"
x="-20%"
y="-20%"
width="140%"
height="140%"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="linearRGB"
>
<feColorMatrix
type="saturate"
values="0"
x="0%"
y="0%"
width="100%"
height="100%"
in="SourceGraphic"
result="colormatrix2"
/>
<feBlend
mode="saturation"
x="0%"
y="0%"
width="100%"
height="100%"
in="SourceGraphic"
in2="colormatrix2"
result="blend2"
/>
<feBlend
mode="screen"
x="0%"
y="0%"
width="100%"
height="100%"
in="colormatrix2"
in2="blend2"
result="blend3"
/>
<feColorMatrix
type="luminanceToAlpha"
x="0%"
y="0%"
width="100%"
height="100%"
in="blend3"
result="colormatrix3"
/>
<feBlend
mode="exclusion"
x="0%"
y="0%"
width="100%"
height="100%"
in="blend3"
in2="colormatrix3"
result="blend5"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 886 B

After

Width:  |  Height:  |  Size: 1001 B

View File

@@ -1,11 +1,20 @@
<svg id="svg-filter-rustic" class="filter hidden">
<!-- Rustic: `filter: url(#Rustic)` -->
<filter id="Rustic" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feColorMatrix type="matrix" in="SourceGraphic" result="colormatrix" values="
<!-- Rustic: `filter: url(#Rustic)` -->
<filter
id="Rustic"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feColorMatrix
type="matrix"
in="SourceGraphic"
result="colormatrix"
values="
0.39215686274509803 0.39215686274509803 0.39215686274509803 0 0
0.3333333333333333 0.3333333333333333 0.3333333333333333 0 0
0.30980392156862746 0.30980392156862746 0.30980392156862746 0 0
0 0 0 1 0" />
</filter>
</svg>
0 0 0 1 0"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 577 B

View File

@@ -1,13 +1,17 @@
<svg id="svg-filter-summer84" class="filter hidden">
<!-- Summer84: `filter: url(#Summer84)` -->
<filter id="Summer84" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feColorMatrix
type="matrix"
values=" 1.300 0.200 0.000 0.000 0.000
<!-- Summer84: `filter: url(#Summer84)` -->
<filter
id="Summer84"
filterUnits="objectBoundingBox"
primitiveUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feColorMatrix
type="matrix"
values=" 1.300 0.200 0.000 0.000 0.000
0.300 0.600 0.200 0.000 0.000
0.200 1.0 0.200 0.000 0.000
0.000 0.000 0.000 1.000 0.000">
</feColorMatrix>
</filter>
</svg>
0.000 0.000 0.000 1.000 0.000"
/>
</filter>
</svg>

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 510 B

View File

@@ -1,18 +1,23 @@
<script lang="ts">
export let tag: string = 'h1';
export let direction: String = 'bg-gradient-to-r';
export let from: string = 'from-primary-500';
export let to: string = 'to-accent-500';
export let tag: string = 'h1';
export let direction: String = 'bg-gradient-to-r';
export let from: string = 'from-primary-500';
export let to: string = 'to-accent-500';
// Base Clases
let cBaseHeading: string = 'bg-clip-text text-transparent box-decoration-clone';
// Base Clases
let cBaseHeading: string = 'bg-clip-text text-transparent box-decoration-clone';
// Reactive
$: classesWrapper = `${cBaseHeading} ${direction} ${from} ${to}`;
// Reactive
$: classesWrapper = `${cBaseHeading} ${direction} ${from} ${to}`;
</script>
<svelte:element this={tag} class="gradient-heading {$$props.class}" data-testid="gradient-heading" role="heading">
<span class="{classesWrapper}">
<slot />
</span>
</svelte:element>
<svelte:element
this={tag}
class="gradient-heading {$$props.class}"
data-testid="gradient-heading"
role="heading"
>
<span class={classesWrapper}>
<slot />
</span>
</svelte:element>

View File

@@ -13,7 +13,7 @@ const defaultDirection: String = 'bg-gradient-to-r';
const defaultFrom: string = 'from-primary-500';
const defaultTo: string = 'to-accent-500';
const exampleDirections: String[] = ['bg-gradient-to-r', 'bg-gradient-to-l'];
const exampleDirections: String[] = ['bg-gradient-to-r', 'bg-gradient-to-l'];
const testid = 'gradient-heading';
@@ -21,33 +21,35 @@ describe('Card.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const {getByTestId} = render(GradientHeading, {tag: 'h1'});
const { getByTestId } = render(GradientHeading, { tag: 'h1' });
expect(getByTestId(testid)).toBeTruthy();
});
// Test default color-from prop
it('Default "from" prop applied")', async () => {
const {getByTestId} = render(GradientHeading, {tag: 'h1'});
const { getByTestId } = render(GradientHeading, { tag: 'h1' });
expect(getByTestId(testid).getAttribute('class').includes(defaultFrom));
});
// Test default color-to prop
it('Default "to" prop applied")', async () => {
const {getByTestId} = render(GradientHeading, {tag: 'h1'});
const { getByTestId } = render(GradientHeading, { tag: 'h1' });
expect(getByTestId(testid).getAttribute('class').includes(defaultTo));
});
// Test that each direction is implemented
exampleDirections.forEach((direction) =>{
it(`Direction: ${direction} applied`, async ()=>{
const {getByTestId} = render(GradientHeading, { tag: 'h1', directions: direction });
exampleDirections.forEach((direction) => {
it(`Direction: ${direction} applied`, async () => {
const { getByTestId } = render(GradientHeading, { tag: 'h1', directions: direction });
expect(getByTestId(testid).getAttribute('class').includes(`bg-gradient-to-${direction}`));
})
})
});
});
// Test that default direction is applied on invalid prop
it('Default direction applied (invalid prop)', async () => {
const {getByTestId} = render(GradientHeading, { tag: 'h1', direction: 'bg-gradient-to-r' });
expect(getByTestId(testid).getAttribute('class').includes(`bg-gradient-to-${defaultDirection}`));
const { getByTestId } = render(GradientHeading, { tag: 'h1', direction: 'bg-gradient-to-r' });
expect(
getByTestId(testid).getAttribute('class').includes(`bg-gradient-to-${defaultDirection}`)
);
});
});

View File

@@ -1,36 +1,35 @@
<!-- https://tailwindcss.com/docs/dark-mode -->
<script lang="ts">
import { svg } from "$lib/icons";
import { onMount } from "svelte";
import { svg } from '$lib/icons';
import { onMount } from 'svelte';
import Card from "$lib/Card/Card.svelte";
import List from "$lib/List/List.svelte";
import ListItem from "$lib/List/ListItem.svelte";
import Menu from "$lib/Menu/Menu.svelte";
import Card from '$lib/Card/Card.svelte';
import List from '$lib/List/List.svelte';
import ListItem from '$lib/List/ListItem.svelte';
import Menu from '$lib/Menu/Menu.svelte';
export let select: boolean = true;
export let open: boolean = false;
export let origin: string = 'auto'; // tl | tr | bl | br
export let duration: number = 100; // ms
export let disabled: boolean = false;
export let origin: string = 'auto'; // tl | tr | bl | br
export let duration: number = 100; // ms
export let disabled: boolean = false;
let browser: boolean = false;
onMount(() =>{
// lifecycle functions don't run during SSR
browser = true
setThemeClass();
});
let browser: boolean = false;
onMount(() => {
// lifecycle functions don't run during SSR
browser = true;
setThemeClass();
});
let currentTheme: string = 'load';
// A11y Input Handlers
function onKeyDown(event: any, t: any): void {
if (['Enter', 'Space'].includes(event.detail.code)) {
event.preventDefault();
selectTheme(t);
}
}
if (['Enter', 'Space'].includes(event.detail.code)) {
event.preventDefault();
selectTheme(t);
}
}
// Handle Selection
function selectTheme(t: string): void {
@@ -48,7 +47,12 @@
function setThemeClass(): void {
if (browser) {
// dark / light
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.hasOwnProperty('matchMedia') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
if (
localStorage.theme === 'dark' ||
(!('theme' in localStorage) &&
window.hasOwnProperty('matchMedia') &&
window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark');
currentTheme = 'dark';
} else {
@@ -62,8 +66,15 @@
setThemeClass();
</script>
<Menu {select} {open} {origin} {duration} {disabled} class="lightswitch {$$props.class}" data-testid="lightswitch">
<Menu
{select}
{open}
{origin}
{duration}
{disabled}
class="lightswitch {$$props.class}"
data-testid="lightswitch"
>
<!-- Switcher -->
<button
slot="trigger"
@@ -80,27 +91,38 @@
<Card slot="content" class="bg-surface-300 dark:bg-surface-700 p-0 overflow-hidden">
<List tag="nav" class="fill-surface-500">
<ListItem
on:click="{() => { selectTheme('light') }}"
on:keydown="{(e) => { onKeyDown(e, 'light') }}"
on:click={() => {
selectTheme('light');
}}
on:keydown={(e) => {
onKeyDown(e, 'light');
}}
>
<svelte:fragment slot="lead">{@html svg.light}</svelte:fragment>
<span>Light</span>
</ListItem>
<ListItem
on:click="{() => { selectTheme('dark') }}"
on:keydown="{(e) => { onKeyDown(e, 'dark') }}"
on:click={() => {
selectTheme('dark');
}}
on:keydown={(e) => {
onKeyDown(e, 'dark');
}}
>
<svelte:fragment slot="lead">{@html svg.dark}</svelte:fragment>
<span>Dark</span>
</ListItem>
<ListItem
on:click="{() => { selectTheme('system') }}"
on:keydown="{(e) => { onKeyDown(e, 'system') }}"
on:click={() => {
selectTheme('system');
}}
on:keydown={(e) => {
onKeyDown(e, 'system');
}}
>
<svelte:fragment slot="lead">{@html svg.system}</svelte:fragment>
<span>System</span>
</ListItem>
</List>
</Card>
</Menu>

View File

@@ -10,16 +10,15 @@ import LightSwitch from '$lib/LightSwitch/LightSwitch.svelte';
// FIXME: skipped, resolve error window.matchMedia is not a function
describe.skip('LightSwitch.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(LightSwitch);
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(LightSwitch, {origin: 'tr'});
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(LightSwitch);
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(LightSwitch, { origin: 'tr' });
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
});

View File

@@ -1,67 +1,69 @@
<script lang='ts'>
import { setContext } from 'svelte';
import type { Writable } from 'svelte/store';
<script lang="ts">
import { setContext } from 'svelte';
import type { Writable } from 'svelte/store';
// Props
export let tag: string = 'ul';
export let selected: Writable<any> = undefined; // (store)
export let highlight: string = '!bg-primary-500'; // '!' required
export let hover: string = 'hover:bg-primary-500/10'; // 'hover:' required
// A11y
export let title: string = undefined;
export let label: string = undefined;
export let labelledby: string = undefined;
// Props
export let tag: string = 'ul';
export let selected: Writable<any> = undefined; // (store)
export let highlight: string = '!bg-primary-500'; // '!' required
export let hover: string = 'hover:bg-primary-500/10'; // 'hover:' required
// A11y
export let title: string = undefined;
export let label: string = undefined;
export let labelledby: string = undefined;
// Context
setContext('parentTag', tag);
setContext('selected', selected);
setContext('highlight', highlight);
setContext('hover', hover);
// Context
setContext('parentTag', tag);
setContext('selected', selected);
setContext('highlight', highlight);
setContext('hover', hover);
// Classes
const cBase: string = 'whitespace-nowrap';
const cSpacing: string = 'space-y-1';
// Classes
const cBase: string = 'whitespace-nowrap';
const cSpacing: string = 'space-y-1';
// Local
let elemList: HTMLElement;
// Local
let elemList: HTMLElement;
// A11y Input Handler
function onKeyDown(event: any): void {
// Home/End Keys
if (['Home', 'End'].includes(event.code)) {
event.preventDefault();
const currentElem: any = tag === 'nav' ? elemList.children[0] : elemList;
if(event.code === 'Home'){ (currentElem.children[0] as HTMLElement).focus(); }
if(event.code === 'End'){ (currentElem.children[currentElem.children.length-1] as HTMLElement).focus(); }
};
}
// A11y Input Handler
function onKeyDown(event: any): void {
// Home/End Keys
if (['Home', 'End'].includes(event.code)) {
event.preventDefault();
const currentElem: any = tag === 'nav' ? elemList.children[0] : elemList;
if (event.code === 'Home') {
(currentElem.children[0] as HTMLElement).focus();
}
if (event.code === 'End') {
(currentElem.children[currentElem.children.length - 1] as HTMLElement).focus();
}
}
}
// Reactive Classes
$: classes = `list-group ${cBase} ${cSpacing} ${$$props.class||''}`;
// Reactive Classes
$: classes = `list-group ${cBase} ${cSpacing} ${$$props.class || ''}`;
</script>
<svelte:element
bind:this={elemList}
this={tag}
class={classes}
data-testid="list-group"
on:keydown={onKeyDown}
{title}
this={tag}
bind:this={elemList}
class={classes}
data-testid="list-group"
on:keydown={onKeyDown}
{title}
>
<!-- Wrap <nav> (listbox) to meet ARIA spec requirements -->
{#if tag === 'nav'}
<ul
class="{cSpacing}"
role="listbox"
aria-label={label}
aria-labelledby={labelledby}
aria-multiselectable={Array.isArray($selected)}
>
<slot />
</ul>
{:else}
<slot />
{/if}
<!-- Wrap <nav> (listbox) to meet ARIA spec requirements -->
{#if tag === 'nav'}
<ul
class={cSpacing}
role="listbox"
aria-label={label}
aria-labelledby={labelledby}
aria-multiselectable={Array.isArray($selected)}
>
<slot />
</ul>
{:else}
<slot />
{/if}
</svelte:element>

View File

@@ -7,49 +7,47 @@ import { afterEach, describe, it, expect } from 'vitest';
import { writable } from 'svelte/store';
// @ts-ignore
import List from '$lib/List/List.svelte'
import List from '$lib/List/List.svelte';
describe('List.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(List);
expect(getByTestId('list-group')).toBeTruthy();
});
it('Renders without props', async () => {
const {getByTestId} = render(List);
expect(getByTestId('list-group')).toBeTruthy();
});
// Standard Lists
// Standard Lists
it('Renders ul list', async () => {
const { getByTestId } = render(List, { props: { role: 'ul' } });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
it('Renders ul list', async()=>{
const {getByTestId} = render(List, {props: { role: 'ul' }});
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
it('Renders ol list', async()=>{
const {getByTestId} = render(List, {props: { role: 'ol' }});
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
it('Renders dl list', async()=>{
const {getByTestId} = render(List, {props: { role: 'dl' }});
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
it('Renders ol list', async () => {
const { getByTestId } = render(List, { props: { role: 'ol' } });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
// Nav & Selection
it('Renders dl list', async () => {
const { getByTestId } = render(List, { props: { role: 'dl' } });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
it('Renders nav a if selected is single value', async ()=>{
const {getByTestId} = render(List, { role:'nav', selected: writable('') });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
})
// Nav & Selection
it('Renders nav a if selected has multiple values', async ()=>{
const {getByTestId} = render(List, { role:'nav', selected: writable([]) });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
})
it('Renders nav a if selected is single value', async () => {
const { getByTestId } = render(List, { role: 'nav', selected: writable('') });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
});
it('Renders nav a if selected has multiple values', async () => {
const { getByTestId } = render(List, { role: 'nav', selected: writable([]) });
const element = getByTestId('list-group');
expect(element).toBeTruthy();
});
});

View File

@@ -1,119 +1,123 @@
<script lang='ts'>
import {createEventDispatcher, getContext} from 'svelte';
import type { Writable } from 'svelte/store';
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte';
import type { Writable } from 'svelte/store';
// Event Handler
const dispatch = createEventDispatcher();
// Event Handler
const dispatch = createEventDispatcher();
// Props
// NOTE: 'value' is handled by $$props.value
// A11y
export let setsize: number = undefined;
export let posinset: number = undefined;
// Props
// NOTE: 'value' is handled by $$props.value
// A11y
export let setsize: number = undefined;
export let posinset: number = undefined;
// Context
export let parentTag: string = getContext('parentTag');
export let selected: Writable<any> = getContext('selected');
export let highlight: string = getContext('highlight');
export let hover: string = getContext('hover');
// Context
export let parentTag: string = getContext('parentTag');
export let selected: Writable<any> = getContext('selected');
export let highlight: string = getContext('highlight');
export let hover: string = getContext('hover');
// Base Classes
const cBase: string = 'list-none px-4 py-3';
const cRowFlex: string = 'flex flex-row items-center space-x-4';
const cItemHover: string = `${hover} cursor-pointer`;
// Base Classes
const cBase: string = 'list-none px-4 py-3';
const cRowFlex: string = 'flex flex-row items-center space-x-4';
const cItemHover: string = `${hover} cursor-pointer`;
// Local
let elemItem: HTMLElement;
let tag: string = 'li';
let role: string = parentTag === 'nav' ? 'option' : undefined;
// Local
let elemItem: HTMLElement;
let tag: string = 'li';
let role: string = parentTag === 'nav' ? 'option' : undefined;
// Set Wrapping Tag
// = parentTag === 'dl' ? 'div' : 'li';
switch (parentTag) {
case ('dl'): tag = 'div'; break;
case ('nav'): tag = 'a'; break;
default: break;
}
// Set Wrapping Tag
// = parentTag === 'dl' ? 'div' : 'li';
switch (parentTag) {
case 'dl':
tag = 'div';
break;
case 'nav':
tag = 'a';
break;
default:
break;
}
// A11y Input Handler
function onKeyDown(event: any): void {
dispatch('keydown', event);
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
parentTag === 'nav' ? elemItem.querySelector('a').click() : elemItem.click();
}
}
// A11y Input Handler
function onKeyDown(event: any): void {
dispatch('keydown', event);
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
parentTag === 'nav' ? elemItem.querySelector('a').click() : elemItem.click();
}
}
// Input Handler
function onClickHandler(event: any): void {
dispatch('click', event);
if (!$selected || !$$props.value) { return; }
typeof($selected) === 'object' ? handleMultiSelect() : handleSingleSelect();
}
function handleSingleSelect(): void {
selected.set($$props.value);
}
function handleMultiSelect(): void {
const v: any = $$props.value;
const local: any[] = $selected;
// Add
if (local.includes(v)) {
local.splice(local.indexOf(v), 1);
selected.set(local);
}
// Remove
else {
selected.set([...local, v]);
}
}
// Input Handler
function onClickHandler(event: any): void {
dispatch('click', event);
if (!$selected || !$$props.value) {
return;
}
typeof $selected === 'object' ? handleMultiSelect() : handleSingleSelect();
}
function handleSingleSelect(): void {
selected.set($$props.value);
}
function handleMultiSelect(): void {
const v: any = $$props.value;
const local: any[] = $selected;
// Add
if (local.includes(v)) {
local.splice(local.indexOf(v), 1);
selected.set(local);
}
// Remove
else {
selected.set([...local, v]);
}
}
// Reactive Selection State
$: isSelected = () => {
if ($selected && $$props.value) {
return typeof($selected) === 'object' ? $selected.includes($$props.value) : $selected === $$props.value;
}
return false;
};
// Reactive Clases
$: classesHighlight = isSelected() ? highlight : '';
$: classesHover = parentTag === 'nav' ? cItemHover : '';
$: classesRowFlex = parentTag !== 'dl' ? cRowFlex : '';
$: classesBase = `list-row ${cBase} ${classesRowFlex} ${classesHover} ${classesHighlight}`;
// Reactive Selection State
$: isSelected = () => {
if ($selected && $$props.value) {
return typeof $selected === 'object'
? $selected.includes($$props.value)
: $selected === $$props.value;
}
return false;
};
// Reactive Clases
$: classesHighlight = isSelected() ? highlight : '';
$: classesHover = parentTag === 'nav' ? cItemHover : '';
$: classesRowFlex = parentTag !== 'dl' ? cRowFlex : '';
$: classesBase = `list-row ${cBase} ${classesRowFlex} ${classesHover} ${classesHighlight}`;
</script>
<svelte:element
bind:this={elemItem}
this={tag}
href={$$props.href}
class={classesBase}
data-testid="list-row"
on:click={onClickHandler}
on:keydown={onKeyDown}
{role}
aria-setsize={setsize}
aria-posinset={posinset}
tabindex="0"
this={tag}
bind:this={elemItem}
href={$$props.href}
class={classesBase}
data-testid="list-row"
on:click={onClickHandler}
on:keydown={onKeyDown}
{role}
aria-setsize={setsize}
aria-posinset={posinset}
tabindex="0"
>
{#if parentTag === 'dl'}
<dt><slot name="dt" /></dt>
<dd><slot name="dd" /></dd>
{:else}
<!-- Slot: Lead -->
{#if $$slots.lead}
<div class="flex-none"><slot name="lead" /></div>
{/if}
{#if parentTag === 'dl'}
<dt><slot name="dt" /></dt>
<dd><slot name="dd" /></dd>
{:else}
<!-- Slot: Lead -->
{#if $$slots.lead}
<div class="flex-none"><slot name="lead" /></div>
{/if}
<!-- Slot: Content -->
<div class="flex-1"><slot /></div>
<!-- Slot: Trail -->
{#if $$slots.trail}
<div class="flex-none"><slot name="trail" /></div>
{/if}
{/if}
<!-- Slot: Content -->
<div class="flex-1"><slot /></div>
<!-- Slot: Trail -->
{#if $$slots.trail}
<div class="flex-none"><slot name="trail" /></div>
{/if}
{/if}
</svelte:element>

View File

@@ -6,17 +6,15 @@ import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
// @ts-ignore
import ListItem from '$lib/List/ListItem.svelte'
import ListItem from '$lib/List/ListItem.svelte';
describe('ListItem.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(ListItem);
expect(getByTestId('list-row')).toBeTruthy();
});
it('Renders without props', async () => {
const {getByTestId} = render(ListItem);
expect(getByTestId('list-row')).toBeTruthy();
});
// NOTE: no props available
});
// NOTE: no props available
});

View File

@@ -1,37 +1,40 @@
<script lang="ts">
import { getContext } from "svelte";
import { getContext } from 'svelte';
// Context
export let background = getContext('background');
export let color = getContext('color');
export let text = getContext('text');
// Set tag and href values
const tag: string = $$props.href ? 'a' : 'div';
const href: any = $$props.href ? `href="${$$props.href}"` : undefined;
// Context
export let background = getContext('background');
export let color = getContext('color');
export let text = getContext('text');
// Base Classes
const cBaseLogo: string = 'flex-auto text-center py-10 space-x-4 hover:brightness-110';
// Set tag and href values
const tag: string = $$props.href ? 'a' : 'div';
const href: any = $$props.href ? `href="${$$props.href}"` : undefined;
// Reactive Classes
$: classesLogo = `${cBaseLogo} ${background} ${color} ${text}`;
// Base Classes
const cBaseLogo: string = 'flex-auto text-center py-10 space-x-4 hover:brightness-110';
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
// Reactive Classes
$: classesLogo = `${cBaseLogo} ${background} ${color} ${text}`;
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
</script>
<svelte:element this={tag} class="logo {classesLogo} {$$props.class}" data-testid="logo" {...prunedRestProps()}>
<svelte:element
this={tag}
class="logo {classesLogo} {$$props.class}"
data-testid="logo"
{...prunedRestProps()}
>
<!-- Slot: lead -->
{#if $$slots.lead}<slot name="lead" />{/if}
<!-- Slot: lead -->
{#if $$slots.lead}<slot name="lead"></slot>{/if}
<!-- Slot: Label -->
{#if $$slots.label}<span class="text-lg"><slot name="label" /></span>{/if}
<!-- Slot: Label -->
{#if $$slots.label}<span class="text-lg"><slot name="label"></slot></span>{/if}
<!-- Default -->
{#if !$$slots.lead && !$$slots.label}<slot />{/if}
</svelte:element>
<!-- Default -->
{#if !$$slots.lead && !$$slots.label}<slot />{/if}
</svelte:element>

View File

@@ -1,20 +1,21 @@
<script lang="ts">
import { setContext } from "svelte";
import { setContext } from 'svelte';
// Props
export let background: string = 'bg-surface-200 dark:bg-surface-800';
export let color: string = 'text-black dark:text-white';
export let text: string = 'text-base font-bold';
// Props
export let background: string = 'bg-surface-200 dark:bg-surface-800';
export let color: string = 'text-black dark:text-white';
export let text: string = 'text-base font-bold';
// Context
setContext('background', background);
setContext('color', color);
setContext('text', text);
// Context
setContext('background', background);
setContext('color', color);
setContext('text', text);
// Base Classes
const cBaseCloud: string = 'flex flex-col lg:flex-row space-y-1 lg:space-y-0 lg:space-x-1 overflow-hidden rounded-xl';
// Base Classes
const cBaseCloud: string =
'flex flex-col lg:flex-row space-y-1 lg:space-y-0 lg:space-x-1 overflow-hidden rounded-xl';
</script>
<div class="{cBaseCloud} {$$props.class}" data-testid="logo-cloud">
<slot />
</div>
<slot />
</div>

View File

@@ -14,5 +14,4 @@ describe('LogoCloud.svelte', () => {
const { getByTestId } = render(LogoCloud);
expect(getByTestId('logo-cloud')).toBeTruthy();
});
});

View File

@@ -1,127 +1,146 @@
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { fade } from 'svelte/transition';
import { onDestroy, onMount } from 'svelte';
import { fade } from 'svelte/transition';
export let select: boolean = false;
export let select: boolean = false;
export let open: boolean = false;
export let origin: string = 'auto'; // auto | tl | tr | bl | br
export let duration: number = 100; // ms
export let disabled: boolean = false;
export let origin: string = 'auto'; // auto | tl | tr | bl | br
export let duration: number = 100; // ms
export let disabled: boolean = false;
let elemMenu: HTMLElement;
let autoOriginMode: boolean = origin === 'auto' ? true : false; // Persist `origin: auto` state
// Base Classes
const cBaseMenu: string = 'relative inline-block';
const cBaseContent: string = 'absolute z-10';
let elemMenu: HTMLElement;
let autoOriginMode: boolean = origin === 'auto' ? true : false; // Persist `origin: auto` state
// Set content anchor origin
let cOrigin: string;
function setOrigin(): void {
switch (origin) {
case ('tl'): cOrigin = 'origin-top-left left-0 mt-0'; break;
case ('tr'): cOrigin = 'origin-top-right right-0 mt-0'; break;
case ('bl'): cOrigin = 'origin-bottom-left top-[-5px] left-0 -translate-y-full'; break;
case ('br'): cOrigin = 'origin-bottom-right top-[-5px] right-0 -translate-y-full'; break;
default: setAutoOrigin(); break;
}
}
setOrigin(); // on init
// Auto-update origin based on viewport position
function setAutoOrigin(): void {
if (!elemMenu) return;
// Get the Menu's bounds
let elemMenuBounds = elemMenu.getBoundingClientRect();
// Set verticle and horizontal values
let vert: string = elemMenuBounds.y < (window.innerHeight/2) ? 't' : 'b'; // top/bottom
let horz: string = elemMenuBounds.x < (window.innerWidth/2) ? 'l' : 'r'; // left/right
// Update origin styles
origin = `${vert}${horz}`;
setOrigin();
}
// Base Classes
const cBaseMenu: string = 'relative inline-block';
const cBaseContent: string = 'absolute z-10';
// Searches for the first parent node that can scroll
// https://thewebdev.info/2021/06/27/how-to-find-the-first-scrollable-parent-element-with-javascript/
function getFirstScrollableParent(node): any {
if (node === null) { return null; }
return node.scrollHeight > node.clientHeight ? node : getFirstScrollableParent(node.parentNode);
}
// Set content anchor origin
let cOrigin: string;
function setOrigin(): void {
switch (origin) {
case 'tl':
cOrigin = 'origin-top-left left-0 mt-0';
break;
case 'tr':
cOrigin = 'origin-top-right right-0 mt-0';
break;
case 'bl':
cOrigin = 'origin-bottom-left top-[-5px] left-0 -translate-y-full';
break;
case 'br':
cOrigin = 'origin-bottom-right top-[-5px] right-0 -translate-y-full';
break;
default:
setAutoOrigin();
break;
}
}
setOrigin(); // on init
// Toggle Visibility
// NOTE: 1ms delay required to avoid race condition for select mode
function toggle(): void {
if (disabled) return;
setTimeout(() => { open = !open; }, 1);
}
// Auto-update origin based on viewport position
function setAutoOrigin(): void {
if (!elemMenu) return;
// Get the Menu's bounds
let elemMenuBounds = elemMenu.getBoundingClientRect();
// Set verticle and horizontal values
let vert: string = elemMenuBounds.y < window.innerHeight / 2 ? 't' : 'b'; // top/bottom
let horz: string = elemMenuBounds.x < window.innerWidth / 2 ? 'l' : 'r'; // left/right
// Update origin styles
origin = `${vert}${horz}`;
setOrigin();
}
// Handle click on <body> element
// Source: https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7
function handleBodyClick(event: any): void {
// If menu not open, exit
if (!open) return;
// If click is outside menu, close menu
if (elemMenu && !elemMenu.contains(event.target) && !event.defaultPrevented) {
open = false;
return;
}
// If select enabled and click is inside menu, close menu
if (select === true) {
open = false;
return;
}
}
// Searches for the first parent node that can scroll
// https://thewebdev.info/2021/06/27/how-to-find-the-first-scrollable-parent-element-with-javascript/
function getFirstScrollableParent(node): any {
if (node === null) {
return null;
}
return node.scrollHeight > node.clientHeight ? node : getFirstScrollableParent(node.parentNode);
}
// A11y Input Handler
function onKeyDown(event: any): void {
if (open && event.code === 'Escape') { toggle(); }
}
// Toggle Visibility
// NOTE: 1ms delay required to avoid race condition for select mode
function toggle(): void {
if (disabled) return;
setTimeout(() => {
open = !open;
}, 1);
}
// Lifecycle Events
onMount(() => {
// Event: Window Keydown (ESC)
window.addEventListener('keydown', onKeyDown);
// If auto-origin enabled, add event listeners
if (autoOriginMode === true) {
// Event: Window Resize
window.addEventListener('resize', setAutoOrigin);
// Event: Parent Scroll
const scrollParent = getFirstScrollableParent(elemMenu)
scrollParent.addEventListener('scroll', setAutoOrigin);
}
// Handle click on <body> element
// Source: https://svelte.dev/repl/0ace7a508bd843b798ae599940a91783?version=3.16.7
function handleBodyClick(event: any): void {
// If menu not open, exit
if (!open) return;
// If click is outside menu, close menu
if (elemMenu && !elemMenu.contains(event.target) && !event.defaultPrevented) {
open = false;
return;
}
// If select enabled and click is inside menu, close menu
if (select === true) {
open = false;
return;
}
}
// A11y Input Handler
function onKeyDown(event: any): void {
if (open && event.code === 'Escape') {
toggle();
}
}
// Lifecycle Events
onMount(() => {
// Event: Window Keydown (ESC)
window.addEventListener('keydown', onKeyDown);
// If auto-origin enabled, add event listeners
if (autoOriginMode === true) {
// Event: Window Resize
window.addEventListener('resize', setAutoOrigin);
// Event: Parent Scroll
const scrollParent = getFirstScrollableParent(elemMenu);
scrollParent.addEventListener('scroll', setAutoOrigin);
}
});
onDestroy(() => {
// close when navigating
open = false;
});
onDestroy(() => {
// close when navigating
open = false;
});
// Responsive Classes
$: classesMenu = `${cBaseMenu}`;
$: classesContent = `${cBaseContent} ${cOrigin}`;
// Responsive Classes
$: classesMenu = `${cBaseMenu}`;
$: classesContent = `${cBaseContent} ${cOrigin}`;
</script>
<svelte:body on:click={handleBodyClick} />
<div bind:this={elemMenu} class="menu-wrapper {classesMenu} {$$props.class}" data-testid="menu-wrapper">
<!-- Trigger Button -->
<!-- REMOVED: role="button" aria-haspopup="true" aria-expanded={open} -->
<div class="menu-trigger" on:click={toggle} data-testid="menu-trigger">
{#if $$slots.trigger}<slot name="trigger" />{/if}
</div>
<!-- Content -->
<!-- NOTE: most A11y settings are built into List/ListItem -->
{#if open}
<div
role="menu"
class="menu-content {classesContent}"
data-testid="menu-content"
in:fade="{{duration}}" out:fade="{{duration}}"
>
{#if $$slots.content}<slot name="content" />{/if}
<div
bind:this={elemMenu}
class="menu-wrapper {classesMenu} {$$props.class}"
data-testid="menu-wrapper"
>
<!-- Trigger Button -->
<!-- REMOVED: role="button" aria-haspopup="true" aria-expanded={open} -->
<div class="menu-trigger" on:click={toggle} data-testid="menu-trigger">
{#if $$slots.trigger}<slot name="trigger" />{/if}
</div>
{/if}
<!-- Content -->
<!-- NOTE: most A11y settings are built into List/ListItem -->
{#if open}
<div
role="menu"
class="menu-content {classesContent}"
data-testid="menu-content"
in:fade={{ duration }}
out:fade={{ duration }}
>
{#if $$slots.content}<slot name="content" />{/if}
</div>
{/if}
</div>

View File

@@ -7,40 +7,38 @@ import { afterEach, describe, expect, it } from 'vitest';
// @ts-ignore
import Menu from '$lib/Menu/Menu.svelte';
// FIXME: skipped, resolve error window.matchMedia is not a function
describe.skip('Menu.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Menu);
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Menu, {
props: {
select: true,
open: false,
origin: 'tr',
duration: 250,
disabled: false,
},
});
expect(getByTestId('menu-wrapper')).toBeTruthy();
})
afterEach(() => cleanup());
it('Renders with default origin (auto)', () => {
const { getByTestId } = render(Menu, {
props: {
select: true,
open: false,
duration: 250,
disabled: false,
},
});
expect(getByTestId('menu-wrapper')).toBeTruthy();
})
});
it('Renders without props', async () => {
const { getByTestId } = render(Menu);
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Menu, {
props: {
select: true,
open: false,
origin: 'tr',
duration: 250,
disabled: false
}
});
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
it('Renders with default origin (auto)', () => {
const { getByTestId } = render(Menu, {
props: {
select: true,
open: false,
duration: 250,
disabled: false
}
});
expect(getByTestId('menu-wrapper')).toBeTruthy();
});
});

View File

@@ -1,149 +1,165 @@
<!-- Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog -->
<script lang="ts">
import { fade, scale } from 'svelte/transition';
import { fade, scale } from 'svelte/transition';
import Button from '$lib/Button/Button.svelte';
import { dialogStore } from '$lib/Notifications/Stores';
import Button from '$lib/Button/Button.svelte';
import { dialogStore } from '$lib/Notifications/Stores';
// Props
export let backdrop: string = 'bg-surface-400/70 dark:bg-surface-900/70';
export let blur: string = 'backdrop-blur-none';
export let card: string = 'bg-surface-50 dark:bg-surface-700';
export let width: string = 'max-w-[640px]';
export let duration: number = 100;
// Props
export let backdrop: string = 'bg-surface-400/70 dark:bg-surface-900/70';
export let blur: string = 'backdrop-blur-none';
export let card: string = 'bg-surface-50 dark:bg-surface-700';
export let width: string = 'max-w-[640px]';
export let duration: number = 100;
// Local Settings
let elemModal: HTMLElement;
let dialogValue: string;
// Local Settings
let elemModal: HTMLElement;
let dialogValue: string;
// Base Classes
const cBaseBackdrop: string = 'fixed top-0 left-0 right-0 bottom-0 z-[999] flex justify-center items-center p-4 backdrop-blur-sm';
const cBaseDialog: string = 'p-4 w-full space-y-4 rounded-xl drop-shadow';
const cBaseHeader: string = 'flex justify-start items-center space-x-4';
const cBaseIcon: string = 'fill-black dark:fill-white bg-primary-500/20 flex justify-center items-center w-10 mx-auto aspect-square rounded-full';
const cBaseImage: string = 'w-full h-auto rounded-lg';
const cBaseFooter: string = 'flex justify-end space-x-4';
// Base Classes
const cBaseBackdrop: string =
'fixed top-0 left-0 right-0 bottom-0 z-[999] flex justify-center items-center p-4 backdrop-blur-sm';
const cBaseDialog: string = 'p-4 w-full space-y-4 rounded-xl drop-shadow';
const cBaseHeader: string = 'flex justify-start items-center space-x-4';
const cBaseIcon: string =
'fill-black dark:fill-white bg-primary-500/20 flex justify-center items-center w-10 mx-auto aspect-square rounded-full';
const cBaseImage: string = 'w-full h-auto rounded-lg';
const cBaseFooter: string = 'flex justify-end space-x-4';
// Set the Result value response based on button selection
function setResult(v: any): void {
if ($dialogStore[0].result) { $dialogStore[0].result(v); }
}
// Set the Result value response based on button selection
function setResult(v: any): void {
if ($dialogStore[0].result) {
$dialogStore[0].result(v);
}
}
// Click Handlers
function onDialogClose(): void {
setResult(false);
dialogStore.close();
}
function onDialogConfirmSubmit(): void {
setResult(true);
dialogStore.close();
}
function onDialogPromptSubmit(): void {
setResult(dialogValue);
dialogStore.close();
}
// Click Handlers
function onDialogClose(): void {
setResult(false);
dialogStore.close();
}
function onDialogConfirmSubmit(): void {
setResult(true);
dialogStore.close();
}
function onDialogPromptSubmit(): void {
setResult(dialogValue);
dialogStore.close();
}
// Subscribe to dialog updates
dialogStore.subscribe(dArr => {
// Dialog quey updated and includes a value
if (dArr.length) {
// Focus on first valid modal element
setTimeout(() => {
if (elemModal !== null) {
const elemWhitelist: string = 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])';
const focusableElems: any = elemModal.querySelectorAll(elemWhitelist);
if (focusableElems !== null) { focusableElems[0].focus(); }
}
}, 100);
// firstFooterButton.focus();
// Set the local dialog value (for prompt)
dialogValue = dArr[0].value;
}
});
// Subscribe to dialog updates
dialogStore.subscribe((dArr) => {
// Dialog quey updated and includes a value
if (dArr.length) {
// Focus on first valid modal element
setTimeout(() => {
if (elemModal !== null) {
const elemWhitelist: string =
'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])';
const focusableElems: any = elemModal.querySelectorAll(elemWhitelist);
if (focusableElems !== null) {
focusableElems[0].focus();
}
}
}, 100);
// firstFooterButton.focus();
// Set the local dialog value (for prompt)
dialogValue = dArr[0].value;
}
});
// A11y Input Handler
function onKeyPress(event: any): void {
// ESC to close dialog
if (event.code === 'Escape') { onDialogClose(); }
}
// A11y Input Handler
function onKeyPress(event: any): void {
// ESC to close dialog
if (event.code === 'Escape') {
onDialogClose();
}
}
// Reactive Classes
$: classesBackdrop = `${cBaseBackdrop} ${backdrop} ${blur}`;
$: classesDialog = `${cBaseDialog} ${card} ${width}`;
// Reactive Classes
$: classesBackdrop = `${cBaseBackdrop} ${backdrop} ${blur}`;
$: classesDialog = `${cBaseDialog} ${card} ${width}`;
</script>
{#if $dialogStore.length}
<!-- Backdrop Shim -->
<div
class="dialog-backdrop {classesBackdrop} {$$props.class}"
on:click={onDialogClose}
on:keydown={onKeyPress}
transition:fade|local={{duration}}
>
<!-- Backdrop Shim -->
<div
class="dialog-backdrop {classesBackdrop} {$$props.class}"
on:click={onDialogClose}
on:keydown={onKeyPress}
transition:fade|local={{ duration }}
>
<!-- Dialog -->
<div
bind:this={elemModal}
class="dialog {classesDialog}"
on:click|preventDefault|stopPropagation
transition:scale|local={{ duration, opacity: 0, start: 0.5 }}
role="dialog"
aria-modal="true"
aria-label={$dialogStore[0].title}
>
<!-- Header -->
<header class="dialog-header {cBaseHeader}">
<!-- Icon -->
{#if $dialogStore[0].icon}
<div class="dialog-icon {cBaseIcon}">{@html $dialogStore[0].icon}</div>
{/if}
<!-- Dialog -->
<div
bind:this={elemModal}
class="dialog {classesDialog}"
on:click|preventDefault|stopPropagation transition:scale|local="{{duration, opacity: 0, start: 0.5}}"
role="dialog"
aria-modal="true"
aria-label={$dialogStore[0].title}
>
<!-- Title -->
<span class="flex-1 text-xl font-bold">{@html $dialogStore[0].title}</span>
</header>
<!-- Header -->
<header class="dialog-header {cBaseHeader}">
<!-- Content -->
<section class="dialog-content space-y-4">
<p class="opacity-60">{@html $dialogStore[0].body}</p>
<!-- Icon -->
{#if $dialogStore[0].icon}
<div class="dialog-icon {cBaseIcon}">{@html $dialogStore[0].icon}</div>
{/if}
<!-- If: image -->
{#if $dialogStore[0].image}
<img
src={$dialogStore[0].image}
class="dialog-image {cBaseImage}"
alt="Dialog"
loading="lazy"
/>
{/if}
<!-- Title -->
<span class="flex-1 text-xl font-bold">{@html $dialogStore[0].title}</span>
<!-- If: HTML -->
{#if $dialogStore[0].html}
{@html $dialogStore[0].html}
{/if}
</header>
<!-- If: Component -->
{#if $dialogStore[0].component}
<svelte:component
this={$dialogStore[0].component.element}
{...$dialogStore[0].component.props}
>
{@html $dialogStore[0].component.slot}
</svelte:component>
{/if}
<!-- Content -->
<section class="dialog-content space-y-4">
<!-- If: Prompt -->
{#if $dialogStore[0].type === 'prompt'}
<input type="text" bind:value={dialogValue} placeholder="Enter value..." required />
{/if}
</section>
<p class="opacity-60">{@html $dialogStore[0].body}</p>
<!-- If: image -->
{#if $dialogStore[0].image}
<img src={$dialogStore[0].image} class="dialog-image {cBaseImage}" alt="Dialog" loading="lazy">
{/if}
<!-- If: HTML -->
{#if $dialogStore[0].html}
{@html $dialogStore[0].html}
{/if}
<!-- If: Component -->
{#if $dialogStore[0].component}
<svelte:component this={$dialogStore[0].component.element} {...$dialogStore[0].component.props}>
{@html $dialogStore[0].component.slot}
</svelte:component>
{/if}
<!-- If: Prompt -->
{#if $dialogStore[0].type === 'prompt'}
<input type="text" bind:value={dialogValue} placeholder="Enter value..." required>
{/if}
</section>
<!-- Footer -->
<footer class="dialog-footer {cBaseFooter}">
<!-- Button: Cancel -->
<Button variant="ghost" on:click={onDialogClose}>Close</Button>
<!-- If Confirm - Button: Confirm -->
{#if $dialogStore[0].type === 'confirm'}<Button variant="filled-primary" on:click={onDialogConfirmSubmit}>Confirm</Button>{/if}
<!-- If Promopt - Button: Submit -->
{#if $dialogStore[0].type === 'prompt'}<Button variant="filled-primary" on:click={onDialogPromptSubmit}>Submit</Button>{/if}
</footer>
</div>
</div>
{/if}
<!-- Footer -->
<footer class="dialog-footer {cBaseFooter}">
<!-- Button: Cancel -->
<Button variant="ghost" on:click={onDialogClose}>Close</Button>
<!-- If Confirm - Button: Confirm -->
{#if $dialogStore[0].type === 'confirm'}<Button
variant="filled-primary"
on:click={onDialogConfirmSubmit}>Confirm</Button
>{/if}
<!-- If Promopt - Button: Submit -->
{#if $dialogStore[0].type === 'prompt'}<Button
variant="filled-primary"
on:click={onDialogPromptSubmit}>Submit</Button
>{/if}
</footer>
</div>
</div>
{/if}

View File

@@ -5,39 +5,43 @@ import { writable } from 'svelte/store';
// Dialogs ---
export interface DialogAlert {
icon?: string;
title: string;
body: string;
image?: any;
component?: any;
html?: any;
icon?: string;
title: string;
body: string;
image?: any;
component?: any;
html?: any;
}
export interface DialogConfirm extends DialogAlert {
type: string;
result: any;
type: string;
result: any;
}
export interface DialogPrompt extends DialogAlert {
type: string;
value: any;
result: any;
type: string;
value: any;
result: any;
}
function dialogService(): any {
const { subscribe, set, update } = writable([]);
return {
subscribe,
// Trigger - append to end of queue
trigger: (dialog: (DialogAlert|DialogAlert|DialogAlert)) => update(dStore => {
dStore.push(dialog);
return dStore;
}),
// Close - remove first item in queue
close: () => update(dStore => {
if (dStore.length > 0) { dStore.shift(); }
return dStore;
}),
// Clear - remove all items from queue
clear: () => set([]),
// Trigger - append to end of queue
trigger: (dialog: DialogAlert | DialogAlert | DialogAlert) =>
update((dStore) => {
dStore.push(dialog);
return dStore;
}),
// Close - remove first item in queue
close: () =>
update((dStore) => {
if (dStore.length > 0) {
dStore.shift();
}
return dStore;
}),
// Clear - remove all items from queue
clear: () => set([])
};
}
@@ -46,34 +50,38 @@ export const dialogStore: any = dialogService();
// Toasts ---
export interface ToastMessage {
message: string;
autohide?: boolean,
timeout?: number;
button?: {
label: string;
action: any;
};
message: string;
autohide?: boolean;
timeout?: number;
button?: {
label: string;
action: any;
};
}
const toastDefaults: any = {message: 'Default Toast Message', autohide: true, timeout: 5000};
const toastDefaults: any = { message: 'Default Toast Message', autohide: true, timeout: 5000 };
function toastService(): any {
const { subscribe, set, update } = writable([]);
return {
subscribe,
// Trigger - append to end of queue
trigger: (toast: any) => update(tStore => {
let tMerged: any = {...toastDefaults, ...toast};
tStore.push(tMerged);
return tStore;
}),
// Close - remove first item in queue
close: () => update(tStore => {
if (tStore.length > 0) { tStore.shift(); }
return tStore;
}),
// Clear - remove all items from queue
clear: () => set([]),
// Trigger - append to end of queue
trigger: (toast: any) =>
update((tStore) => {
let tMerged: any = { ...toastDefaults, ...toast };
tStore.push(tMerged);
return tStore;
}),
// Close - remove first item in queue
close: () =>
update((tStore) => {
if (tStore.length > 0) {
tStore.shift();
}
return tStore;
}),
// Clear - remove all items from queue
clear: () => set([])
};
}
@@ -81,13 +89,19 @@ export const toastStore: any = toastService();
// Handle Queue Autohide
let timeoutAutoHide: any;
toastStore.subscribe(t => {
// On update, clear any existing timers
clearTimeout(timeoutAutoHide);
// If the list is empty, return
if (!t.length) { return; }
// If autohide is false, return
if (!t[0].autohide) { return; }
// Set a timeout for the amount specified but the current visible toast
timeoutAutoHide = setTimeout(() => { toastStore.close(); }, t[0].timeout);
});
toastStore.subscribe((t) => {
// On update, clear any existing timers
clearTimeout(timeoutAutoHide);
// If the list is empty, return
if (!t.length) {
return;
}
// If autohide is false, return
if (!t[0].autohide) {
return;
}
// Set a timeout for the amount specified but the current visible toast
timeoutAutoHide = setTimeout(() => {
toastStore.close();
}, t[0].timeout);
});

View File

@@ -1,65 +1,87 @@
<script lang="ts">
import { fly, fade } from 'svelte/transition';
import { fly, fade } from 'svelte/transition';
import { toastStore } from '$lib/Notifications/Stores';
import Button from '$lib/Button/Button.svelte';
import { toastStore } from '$lib/Notifications/Stores';
import Button from '$lib/Button/Button.svelte';
// Props
export let background: string = 'bg-primary-500';
export let position: string = 'b'; // bottom
export let variant: string = 'ghost';
export let duration: number = 250;
// Props
export let background: string = 'bg-primary-500';
export let position: string = 'b'; // bottom
export let variant: string = 'ghost';
export let duration: number = 250;
// Base Classes
const cBaseToast: string = 'fixed z-50 flex items-center mx-auto py-3 px-4 max-w-[480px] space-x-4 rounded-xl';
const cBaseMessage: string = 'flex-1 text-base';
const cBaseActions: string = 'flex-none space-x-2';
// Base Classes
const cBaseToast: string =
'fixed z-50 flex items-center mx-auto py-3 px-4 max-w-[480px] space-x-4 rounded-xl';
const cBaseMessage: string = 'flex-1 text-base';
const cBaseActions: string = 'flex-none space-x-2';
// Set Position
let y: number = 100;
let cPosition: string;
switch (position) {
// Centered
case('t'): cPosition = 'left-4 right-4 top-4'; y = -100; break;
case('b'): cPosition = 'left-4 right-4 bottom-4'; break;
// Corners
case ('tr'): cPosition = 'top-4 right-4 ml-4'; y = -100; break;
case ('tl'): cPosition = 'top-4 left-4 mr-4'; y = -100; break;
case ('br'): cPosition = 'bottom-4 right-4 ml-4'; break;
case ('bl'): cPosition = 'bottom-4 left-4 mr-4'; break;
default: cPosition = 'left-4 right-4 bottom-4';
}
// Set Position
let y: number = 100;
let cPosition: string;
switch (position) {
// Centered
case 't':
cPosition = 'left-4 right-4 top-4';
y = -100;
break;
case 'b':
cPosition = 'left-4 right-4 bottom-4';
break;
// Corners
case 'tr':
cPosition = 'top-4 right-4 ml-4';
y = -100;
break;
case 'tl':
cPosition = 'top-4 left-4 mr-4';
y = -100;
break;
case 'br':
cPosition = 'bottom-4 right-4 ml-4';
break;
case 'bl':
cPosition = 'bottom-4 left-4 mr-4';
break;
default:
cPosition = 'left-4 right-4 bottom-4';
}
// Functionality
function onAction(): void {
$toastStore[0].button.action();
toastStore.close();
}
function onDismiss(): void {
toastStore.close();
}
// Functionality
function onAction(): void {
$toastStore[0].button.action();
toastStore.close();
}
function onDismiss(): void {
toastStore.close();
}
// Reactive Classes
$: classesToast = `${cBaseToast} ${background} ${cPosition}`;
// Reactive Classes
$: classesToast = `${cBaseToast} ${background} ${cPosition}`;
</script>
{#if $toastStore.length}
<div class="toast {classesToast}" transition:fly|local={{y, duration}} role="alert" aria-live="polite">
<div
class="toast {classesToast}"
transition:fly|local={{ y, duration }}
role="alert"
aria-live="polite"
>
<!-- Message -->
{#key $toastStore[0].message}
<div class="toast-message {cBaseMessage}" in:fade={{ duration: 250 }}>
{@html $toastStore[0].message}
</div>
{/key}
<!-- Message -->
{#key $toastStore[0].message}
<div class="toast-message {cBaseMessage}" in:fade={{duration: 250}}>
{@html $toastStore[0].message}
</div>
{/key}
<!-- Action -->
<div class="toast-actions {cBaseActions}">
{#if $toastStore[0].button}<Button {variant} on:click={onAction}>{$toastStore[0].button.label}</Button>{/if}
<Button {variant} on:click={onDismiss}>
{@html $toastStore[0].button ? '&#10005;' : 'Dismiss'}
</Button>
</div>
</div>
<!-- Action -->
<div class="toast-actions {cBaseActions}">
{#if $toastStore[0].button}<Button {variant} on:click={onAction}
>{$toastStore[0].button.label}</Button
>{/if}
<Button {variant} on:click={onDismiss}>
{@html $toastStore[0].button ? '&#10005;' : 'Dismiss'}
</Button>
</div>
</div>
{/if}

View File

@@ -1,53 +1,65 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Button from "$lib/Button/Button.svelte";
import { createEventDispatcher } from 'svelte';
import Button from '$lib/Button/Button.svelte';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
// Props
export let offset: number = 0;
export let limit: number = 5;
export let size: number = 10;
export let amounts: number[] = [1,5,10,50,100];
// Props: Design
export let justify: string = 'justify-between';
export let text: string = 'text-xs';
export let select: string = undefined;
// Props: Button
export let variant: string = 'filled-primary';
export let rounded: string = undefined;
// Props
export let offset: number = 0;
export let limit: number = 5;
export let size: number = 10;
export let amounts: number[] = [1, 5, 10, 50, 100];
// Props: Design
export let justify: string = 'justify-between';
export let text: string = 'text-xs';
export let select: string = undefined;
// Props: Button
export let variant: string = 'filled-primary';
export let rounded: string = undefined;
// Base Classes
// const cBasePaginator: string = 'flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4';
const cBasePaginator: string = 'flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4';
const cBaseText: string = 'opacity-60 whitespace-nowrap';
// Base Classes
// const cBasePaginator: string = 'flex flex-col md:flex-row items-center space-y-4 md:space-y-0 md:space-x-4';
const cBasePaginator: string =
'flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4';
const cBaseText: string = 'opacity-60 whitespace-nowrap';
// Functionality
function onChangeLength(e: any): void { offset = 0; dispatch('amount', length); }
function onPrev(): void { offset--; dispatch('page', offset); }
function onNext(): void { offset++; dispatch('page', offset); }
// Functionality
function onChangeLength(e: any): void {
offset = 0;
dispatch('amount', length);
}
function onPrev(): void {
offset--;
dispatch('page', offset);
}
function onNext(): void {
offset++;
dispatch('page', offset);
}
// Reactive Classes
$: classesPaginator = `${cBasePaginator} ${justify}`;
$: classesText = `${cBaseText} ${text}`;
// Reactive Classes
$: classesPaginator = `${cBasePaginator} ${justify}`;
$: classesText = `${cBaseText} ${text}`;
</script>
<div class="paginator {classesPaginator} {$$props.class}" data-testid="paginator">
<!-- Select Amount -->
<label class="w-full md:w-auto">
<select bind:value={limit} on:change={onChangeLength} class="{select}" aria-label="Select Amount">
{#each amounts as amount}<option value={amount}>Show {amount}</option>{/each}
</select>
</label>
<!-- Select Amount -->
<label class="w-full md:w-auto">
<select bind:value={limit} on:change={onChangeLength} class={select} aria-label="Select Amount">
{#each amounts as amount}<option value={amount}>Show {amount}</option>{/each}
</select>
</label>
<!-- Context Text -->
<div class="{classesText}">{offset*limit+1} to {offset*limit+limit} of <strong>{size}</strong></div>
<!-- Context Text -->
<div class={classesText}>
{offset * limit + 1} to {offset * limit + limit} of <strong>{size}</strong>
</div>
<!-- Arrows -->
<div class="space-x-2">
<Button {variant} {rounded} on:click={onPrev} disabled={offset===0}>&larr;</Button>
<Button {variant} {rounded} on:click={onNext} disabled={(offset+1)*limit >= size}>&rarr;</Button>
</div>
</div>
<!-- Arrows -->
<div class="space-x-2">
<Button {variant} {rounded} on:click={onPrev} disabled={offset === 0}>&larr;</Button>
<Button {variant} {rounded} on:click={onNext} disabled={(offset + 1) * limit >= size}
>&rarr;</Button
>
</div>
</div>

View File

@@ -2,7 +2,7 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte'
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import Paginator from '$lib/Paginator/Paginator.svelte';
@@ -10,25 +10,25 @@ import Paginator from '$lib/Paginator/Paginator.svelte';
let offset: number = 1;
let limit: number = 50;
let size: number = 100;
let amounts: number[] = [1,5,10,50,100];
let amounts: number[] = [1, 5, 10, 50, 100];
describe('Paginator.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders with props', async () => {
const { getByTestId } = render(Paginator);
expect(getByTestId('paginator')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(Paginator);
expect( getByTestId('paginator') ).toBeTruthy();
})
it('Renders with props', async()=>{
const { getByTestId } = render(Paginator, {props: {
offset,
limit,
size,
amounts
}});
expect( getByTestId('paginator') ).toBeTruthy();
})
})
it('Renders with props', async () => {
const { getByTestId } = render(Paginator, {
props: {
offset,
limit,
size,
amounts
}
});
expect(getByTestId('paginator')).toBeTruthy();
});
});

View File

@@ -1,52 +1,61 @@
<script lang="ts">
// Props
export let label: string = '';
export let value: number = undefined;
export let max: number = 10;
export let height: string = 'h-2';
export let color: string = 'bg-accent-500';
// Props
export let label: string = '';
export let value: number = undefined;
export let max: number = 10;
export let height: string = 'h-2';
export let color: string = 'bg-accent-500';
// Base Classes
const cBaseWrapper: string = 'w-full';
const cBaseLabel: string = 'block text-sm mb-2';
const cBaseTrack: string = `w-full bg-surface-500/20 overflow-hidden rounded-full`;
const cBaseMeterDeterminate: string = 'h-full rounded-full';
const cBaseMeterIndeterminate: string = 'h-full w-full rounded-full';
// Fill Percent
$: fillPercent = (100 * value) / max;
// Base Classes
const cBaseWrapper: string = 'w-full';
const cBaseLabel: string = 'block text-sm mb-2';
const cBaseTrack: string = `w-full bg-surface-500/20 overflow-hidden rounded-full`;
const cBaseMeterDeterminate: string = 'h-full rounded-full';
const cBaseMeterIndeterminate: string = 'h-full w-full rounded-full';
// Reactive Classes
$: classesTrack = `${cBaseTrack} ${height} ${$$props.class}`;
// Fill Percent
$: fillPercent = (100 * value) / max;
// Reactive Classes
$: classesTrack = `${cBaseTrack} ${height} ${$$props.class}`;
</script>
<div
class="progress-wrapper {cBaseWrapper}"
data-testid="progress-wrapper"
role="meter"
aria-label={label}
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={max}
class="progress-wrapper {cBaseWrapper}"
data-testid="progress-wrapper"
role="meter"
aria-label={label}
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={max}
>
<!-- Label -->
{#if label}<label for="progress" class="progress-label {cBaseLabel}">{label}</label>{/if}
<!-- Track -->
<div class="progress-track {classesTrack}">
<!-- Meter - Determinate / Indeterminate -->
{#if value >= 0}
<div class="progress-meter {cBaseMeterDeterminate} {color}" style:width="{fillPercent}%"></div>
{:else}
<div class="progress-meter {cBaseMeterIndeterminate} {color} animIndeterminate"></div>
{/if}
</div>
<!-- Label -->
{#if label}<label for="progress" class="progress-label {cBaseLabel}">{label}</label>{/if}
<!-- Track -->
<div class="progress-track {classesTrack}">
<!-- Meter - Determinate / Indeterminate -->
{#if value >= 0}
<div class="progress-meter {cBaseMeterDeterminate} {color}" style:width="{fillPercent}%" />
{:else}
<div class="progress-meter {cBaseMeterIndeterminate} {color} animIndeterminate" />
{/if}
</div>
</div>
<style lang="postcss">
.animIndeterminate { transform-origin: 0% 50%; animation: animIndeterminate 2s infinite linear; }
@keyframes animIndeterminate {
0% { transform: translateX(0) scaleX(0); }
40% { transform: translateX(0) scaleX(0.4); }
100% { transform: translateX(100%) scaleX(0.5); }
}
</style>
.animIndeterminate {
transform-origin: 0% 50%;
animation: animIndeterminate 2s infinite linear;
}
@keyframes animIndeterminate {
0% {
transform: translateX(0) scaleX(0);
}
40% {
transform: translateX(0) scaleX(0.4);
}
100% {
transform: translateX(100%) scaleX(0.5);
}
}
</style>

View File

@@ -6,27 +6,25 @@ import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import ProgressBar from '$lib/Progress/ProgressBar.svelte';
describe('ProgressBar.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(ProgressBar);
expect(getByTestId('progress-wrapper')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(ProgressBar, {
props: {
label: 'Test',
value: 50,
max: 100,
height: 'h-1',
color: 'bg-warning-500',
},
});
expect(getByTestId('progress-wrapper')).toBeTruthy();
})
});
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(ProgressBar);
expect(getByTestId('progress-wrapper')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(ProgressBar, {
props: {
label: 'Test',
value: 50,
max: 100,
height: 'h-1',
color: 'bg-warning-500'
}
});
expect(getByTestId('progress-wrapper')).toBeTruthy();
});
});

View File

@@ -1,90 +1,95 @@
<!-- https://css-tricks.com/building-progress-ring-quickly/ -->
<!-- https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor -->
<script lang="ts">
import { afterUpdate } from "svelte";
import { afterUpdate } from 'svelte';
// Props
export let value: number = undefined; // %
export let stroke: number = 20; // px
export let track: string = 'stroke-surface-300 dark:stroke-surface-700';
export let meter: string = 'stroke-black dark:stroke-white';
export let color: string = 'fill-black dark:fill-white';
export let font: number = 56; // px
// Ally
export let label: string = undefined;
// Props
export let value: number = undefined; // %
export let stroke: number = 20; // px
export let track: string = 'stroke-surface-300 dark:stroke-surface-700';
export let meter: string = 'stroke-black dark:stroke-white';
export let color: string = 'fill-black dark:fill-white';
export let font: number = 56; // px
// Ally
export let label: string = undefined;
// Base Classes
const cBaseTrack: string = 'fill-transparent';
const cBaseMeter: string = 'fill-transparent transition-[stroke-dashoffset] duration-200 -rotate-90 origin-[50%_50%]';
// Base Classes
const cBaseTrack: string = 'fill-transparent';
const cBaseMeter: string =
'fill-transparent transition-[stroke-dashoffset] duration-200 -rotate-90 origin-[50%_50%]';
// Calculated Values
const baseSize: number = 512; // px
const radius: number = baseSize/2;
let circumference: number = radius;
let dashoffset: number;
// Calculated Values
const baseSize: number = 512; // px
const radius: number = baseSize / 2;
let circumference: number = radius;
let dashoffset: number;
// Set Progress Amount
function setProgress(percent) {
circumference = radius * 2 * Math.PI;
dashoffset = circumference - percent / 100 * circumference;
}
// Set Progress Amount
function setProgress(percent) {
circumference = radius * 2 * Math.PI;
dashoffset = circumference - (percent / 100) * circumference;
}
// On Init
setProgress(0);
// Reactive
afterUpdate(() => {
// If indeterminate set 25, else set the value
setProgress(value === undefined ? 25 : value);
});
// On Init
setProgress(0);
// Reactive
afterUpdate(() => {
// If indeterminate set 25, else set the value
setProgress(value === undefined ? 25 : value);
});
</script>
<!-- https://css-tricks.com/building-progress-ring-quickly/ -->
<figure
class="progress-radial relative overflow-hidden {$$props.class}"
data-testid="progress-radial"
role="meter"
aria-label={label}
aria-valuenow={value || 0}
aria-valuetext={value ? `${value}%` : 'Indeterminate Spinner'}
aria-valuemin={0}
aria-valuemax={100}
class="progress-radial relative overflow-hidden {$$props.class}"
data-testid="progress-radial"
role="meter"
aria-label={label}
aria-valuenow={value || 0}
aria-valuetext={value ? `${value}%` : 'Indeterminate Spinner'}
aria-valuemin={0}
aria-valuemax={100}
>
<!-- Draw SVG -->
<svg
viewBox="0 0 {baseSize} {baseSize}"
class="rounded-full"
class:animate-spin={value === undefined}
>
<!-- Track -->
<circle
class="progress-track {cBaseTrack} {track}"
stroke-width={stroke}
r={baseSize / 2}
cx="50%"
cy="50%"
/>
<!-- Draw SVG -->
<svg viewBox="0 0 {baseSize} {baseSize}" class="rounded-full" class:animate-spin={value === undefined}>
<!-- Track -->
<circle
class="progress-track {cBaseTrack} {track}"
stroke-width={stroke}
r={baseSize/2}
cx="50%" cy="50%"
/>
<!-- Meter -->
<circle
class="progress-meter {cBaseMeter} {meter}"
stroke-width={stroke}
r={baseSize/2}
cx="50%" cy="50%"
style:stroke-dasharray="{circumference} {circumference}"
style:stroke-dashoffset="{dashoffset}"
/>
<!-- Center Text -->
{#if value >= 0 && $$slots.default}
<text
x="50%" y="50%"
text-anchor="middle"
dominant-baseline="middle"
font-weight="bold"
font-size={font}
class="{color}"
><slot /></text>
{/if}
</svg>
<!-- Meter -->
<circle
class="progress-meter {cBaseMeter} {meter}"
stroke-width={stroke}
r={baseSize / 2}
cx="50%"
cy="50%"
style:stroke-dasharray="{circumference}
{circumference}"
style:stroke-dashoffset={dashoffset}
/>
<!-- Center Text -->
{#if value >= 0 && $$slots.default}
<text
x="50%"
y="50%"
text-anchor="middle"
dominant-baseline="middle"
font-weight="bold"
font-size={font}
class={color}><slot /></text
>
{/if}
</svg>
</figure>

View File

@@ -1,27 +1,32 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { setContext } from "svelte";
import type { Writable } from 'svelte/store';
import { setContext } from 'svelte';
// Props
export let selected: Writable<any>;
export let background: string = 'bg-primary-500';
export let color: string = 'text-black dark:text-white';
export let width: string = 'w-auto';
// A11y
export let label: string = 'radiogroup';
// Context
setContext('selected', selected);
setContext('background', background);
setContext('color', color);
// Props
export let selected: Writable<any>;
export let background: string = 'bg-primary-500';
export let color: string = 'text-black dark:text-white';
export let width: string = 'w-auto';
// A11y
export let label: string = 'radiogroup';
// Base Classes
let cBaseGroup: string = `inline-flex items-center rounded overflow-hidden space-x-[2px]`;
// Context
setContext('selected', selected);
setContext('background', background);
setContext('color', color);
// Reactive
$: classesGroup = `${cBaseGroup} ${width}`;
// Base Classes
let cBaseGroup: string = `inline-flex items-center rounded overflow-hidden space-x-[2px]`;
// Reactive
$: classesGroup = `${cBaseGroup} ${width}`;
</script>
<div class="radio-group {classesGroup} {$$props.class||''}" data-testid="radio-group" role="radiogroup" aria-label={label}>
<slot />
<div
class="radio-group {classesGroup} {$$props.class || ''}"
data-testid="radio-group"
role="radiogroup"
aria-label={label}
>
<slot />
</div>

View File

@@ -4,26 +4,24 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import { writable } from 'svelte/store';
import RadioGroup from '$lib/Radio/RadioGroup.svelte';
const testStore = writable(0);
describe('RadioGroup.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(RadioGroup);
expect(getByTestId('radio-group')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(RadioGroup, {
props: {active: testStore, background: 'bg-warning-500', color: 'text-white'},
});
expect(getByTestId('radio-group')).toBeTruthy();
})
});
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(RadioGroup);
expect(getByTestId('radio-group')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(RadioGroup, {
props: { active: testStore, background: 'bg-warning-500', color: 'text-white' }
});
expect(getByTestId('radio-group')).toBeTruthy();
});
});

View File

@@ -1,53 +1,53 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { createEventDispatcher, getContext } from "svelte";
import type { Writable } from 'svelte/store';
import { createEventDispatcher, getContext } from 'svelte';
// Event Handler
const dispatch = createEventDispatcher();
// Event Handler
const dispatch = createEventDispatcher();
// Props
// Props
export let value: any = undefined;
// A11y
export let label: string = undefined;
// Context
export let selected: Writable<any> = getContext('selected');
export let background: string = getContext('background');
export let color: string = getContext('color');
// A11y
export let label: string = undefined;
// Base Classes
let cBaseItem: string = 'flex-1 text-base fill-black dark:fill-white cursor-pointer text-center';
let cBaseUnselected: string = 'bg-surface-300 dark:bg-surface-700';
let cBaseHover: string = 'hover:brightness-110';
// Context
export let selected: Writable<any> = getContext('selected');
export let background: string = getContext('background');
export let color: string = getContext('color');
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space triggers selecton event
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
dispatch('keydown', event);
event.target.click();
}
}
// Base Classes
let cBaseItem: string = 'flex-1 text-base fill-black dark:fill-white cursor-pointer text-center';
let cBaseUnselected: string = 'bg-surface-300 dark:bg-surface-700';
let cBaseHover: string = 'hover:brightness-110';
// Reactive Classes
$: isChecked = (value === $selected);
$: cSelected = isChecked ? ` ${background} ${color}` : cBaseUnselected;
$: classesItem = `${cBaseItem} ${cSelected} ${cBaseHover}`;
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space triggers selecton event
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
dispatch('keydown', event);
event.target.click();
}
}
// Reactive Classes
$: isChecked = value === $selected;
$: cSelected = isChecked ? ` ${background} ${color}` : cBaseUnselected;
$: classesItem = `${cBaseItem} ${cSelected} ${cBaseHover}`;
</script>
<div
id={label}
class="radio-item {classesItem}"
data-testid="radio-item"
on:keydown={onKeyDown}
role="radio"
aria-checked={isChecked}
aria-label={label}
tabindex="0"
id={label}
class="radio-item {classesItem}"
data-testid="radio-item"
on:keydown={onKeyDown}
role="radio"
aria-checked={isChecked}
aria-label={label}
tabindex="0"
>
<label class="px-5 py-2.5">
<input class="hidden" type="radio" {value} bind:group={$selected} />
<div class="inline-block mx-auto"><slot /></div>
</label>
<label class="px-5 py-2.5">
<input class="hidden" type="radio" {value} bind:group={$selected} />
<div class="inline-block mx-auto"><slot /></div>
</label>
</div>

View File

@@ -4,28 +4,26 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import { writable } from 'svelte/store';
import RadioItem from '$lib/Radio/RadioItem.svelte';
const testStore = writable(0);
describe('RadioItem.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(RadioItem, {
props: {selected: testStore}
});
expect(getByTestId('radio-item')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(RadioItem, {
props: {value: 'foobar', selected: testStore},
});
expect(getByTestId('radio-item')).toBeTruthy();
})
});
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(RadioItem, {
props: { selected: testStore }
});
expect(getByTestId('radio-item')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(RadioItem, {
props: { value: 'foobar', selected: testStore }
});
expect(getByTestId('radio-item')).toBeTruthy();
});
});

View File

@@ -1,107 +1,130 @@
<script lang="ts">
// Props
export let id: string = (Math.random() * 10e15).toString(16); // unique id
export let name: string = id;
// Props: Values
export let min: number = 0;
export let max: number = 10;
export let step: number = 1;
export let value: number = 0;
// Props: Options
export let label: string = '';
export let ticked: boolean = false;
export let accent: string = 'accent-primary-500';
// Props
export let id: string = (Math.random() * 10e15).toString(16); // unique id
export let name: string = id;
// Props: Values
export let min: number = 0;
export let max: number = 10;
export let step: number = 1;
export let value: number = 0;
// Props: Options
export let label: string = '';
export let ticked: boolean = false;
export let accent: string = 'accent-primary-500';
// Base Styles
const cBaseLabel: string = 'm-0';
const cBaseContent: string = 'flex justify-center space-x-4';
const cBaseInput: string = 'w-full h-2';
const cBaseValue: string = 'flex-none min-w-[50px] text-center';
// Tickmarks - generate datalist options based on min/max values
let tickmarks: any[];
if (ticked) { tickmarks = Array.from({length: (max-min)+1}, (v,i) => i); }
// Base Styles
const cBaseLabel: string = 'm-0';
const cBaseContent: string = 'flex justify-center space-x-4';
const cBaseInput: string = 'w-full h-2';
const cBaseValue: string = 'flex-none min-w-[50px] text-center';
// A11y Input Handler
function onKeyDown(event: any): void {
// Arrow Keys
const hotKeys: string[] = ['ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'Home', 'End'];
if (hotKeys.includes(event.code)) {
event.preventDefault();
switch (event.code) {
case ('ArrowRight'): valueIncrease(); break;
case ('ArrowUp'): valueIncrease(); break;
case ('ArrowLeft'): valueDecrease(); break;
case ('ArrowDown'): valueDecrease(); break;
case ('Home'): valueMin(); break;
case ('End'): valueMax(); break;
default: break;
}
}
}
function valueIncrease(): void { if ((value + step) <= max) { value += step }; }
function valueDecrease(): void { if ((value - step) >= min) { value -= step }; }
function valueMin(): void { value = min; }
function valueMax(): void { value = max; }
// Tickmarks - generate datalist options based on min/max values
let tickmarks: any[];
if (ticked) {
tickmarks = Array.from({ length: max - min + 1 }, (v, i) => i);
}
// Reactive Classes
$: classesInput = `${cBaseInput} ${accent}`;
// A11y Input Handler
function onKeyDown(event: any): void {
// Arrow Keys
const hotKeys: string[] = ['ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'Home', 'End'];
if (hotKeys.includes(event.code)) {
event.preventDefault();
switch (event.code) {
case 'ArrowRight':
valueIncrease();
break;
case 'ArrowUp':
valueIncrease();
break;
case 'ArrowLeft':
valueDecrease();
break;
case 'ArrowDown':
valueDecrease();
break;
case 'Home':
valueMin();
break;
case 'End':
valueMax();
break;
default:
break;
}
}
}
function valueIncrease(): void {
if (value + step <= max) {
value += step;
}
}
function valueDecrease(): void {
if (value - step >= min) {
value -= step;
}
}
function valueMin(): void {
value = min;
}
function valueMax(): void {
value = max;
}
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
// Reactive Classes
$: classesInput = `${cBaseInput} ${accent}`;
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
</script>
<div
class="range-slider {$$props.class||''}"
data-testid="range-slider"
on:keydown={onKeyDown}
role="slider"
aria-label={label}
aria-valuenow={value}
aria-valuemin={min}
aria-valuemax={max}
class="range-slider {$$props.class || ''}"
data-testid="range-slider"
on:keydown={onKeyDown}
role="slider"
aria-label={label}
aria-valuenow={value}
aria-valuemin={min}
aria-valuemax={max}
>
<!-- Label -->
<label class="range-label {cBaseLabel}" for={id}>{label}</label>
<!-- Label -->
<label class="range-label {cBaseLabel}" for={id}>{label}</label>
<!-- Content -->
<div class="range-content {cBaseContent}">
<!-- Input -->
<div class="flex-1">
<input
type="range"
{id}
{name}
class="range-input {classesInput}"
list="tickmarks-{id}"
{min}
{max}
{step}
bind:value
on:click
on:change
on:blur
{...prunedRestProps()}
/>
<!-- Content -->
<div class="range-content {cBaseContent}">
<!-- Tickmarks -->
{#if ticked && tickmarks.length}
<datalist id="tickmarks-{id}">
{#each tickmarks as tm}
<option value={tm} label={tm} />
{/each}
</datalist>
{/if}
</div>
<!-- Input -->
<div class="flex-1">
<input
type="range"
{id}
{name}
class="range-input {classesInput}"
list="tickmarks-{id}"
min={min}
max={max}
step={step}
bind:value={value}
on:click
on:change
on:blur
{...prunedRestProps()}
>
<!-- Tickmarks -->
{#if ticked && tickmarks.length}
<datalist id="tickmarks-{id}">
{#each tickmarks as tm}
<option value={tm} label='{tm}'></option>
{/each}
</datalist>
{/if}
</div>
<!-- Value -->
<span class="range-value {cBaseValue}">{value}</span>
</div>
<!-- Value -->
<span class="range-value {cBaseValue}">{value}</span>
</div>
</div>

View File

@@ -8,40 +8,38 @@ import { afterEach, describe, it, expect } from 'vitest';
import RangeSlider from '$lib/RangeSlider/RangeSlider.svelte';
const testProps: any = {
id: 'test1',
name: 'test1',
min: 0,
max: 20,
step: 5,
value: 10,
label: 'Testing',
ticked: true,
accent: 'bg-primary-500',
height: 'h-3'
id: 'test1',
name: 'test1',
min: 0,
max: 20,
step: 5,
value: 10,
label: 'Testing',
ticked: true,
accent: 'bg-primary-500',
height: 'h-3'
};
describe('RangeSlider.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders with props', async () => {
const { getByTestId } = render(RangeSlider, { props: testProps });
expect(getByTestId('range-slider')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(RangeSlider, { props: testProps });
expect(getByTestId('range-slider')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(RangeSlider);
expect(getByTestId('range-slider')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(RangeSlider);
expect(getByTestId('range-slider')).toBeTruthy();
});
it('Ticks added', async () => {
const { getByTestId, container } = render(RangeSlider, { props: { ticked: true } });
expect(getByTestId('range-slider').getElementsByTagName('datalist')).toBeTruthy();
});
it('Ticks added', async () => {
const { getByTestId, container} = render(RangeSlider, {props: {ticked: true}});
expect( getByTestId('range-slider').getElementsByTagName('datalist')).toBeTruthy();
})
it('Disabled state', async () => {
const { getByTestId, container} = render(RangeSlider, {disabled: true});
expect(getByTestId('range-slider').getElementsByTagName('input')[0].getAttribute('disabled'));
})
});
it('Disabled state', async () => {
const { getByTestId, container } = render(RangeSlider, { disabled: true });
expect(getByTestId('range-slider').getElementsByTagName('input')[0].getAttribute('disabled'));
});
});

View File

@@ -1,93 +1,96 @@
<script lang="ts">
import { createEventDispatcher } from "svelte/internal";
import { createEventDispatcher } from 'svelte/internal';
// Event Handler
const dispatch = createEventDispatcher();
// Event Handler
const dispatch = createEventDispatcher();
// Props
export let checked: boolean = false;
export let accent: string = 'bg-accent-500';
export let size: string = 'md';
// A11y
export let label: string = undefined;
// Props
export let checked: boolean = false;
export let accent: string = 'bg-accent-500';
export let size: string = 'md';
// A11y
export let label: string = undefined;
// Base Styles
const cBaseLabel: string = 'inline-block';
const cBaseTrack: string = 'flex rounded-full transition-all duration-[200ms] hover:brightness-90 cursor-pointer';
const cBaseThumb: string = 'w-[50%] h-full scale-[0.7] rounded-full cursor-pointer transition-all duration-[200ms] shadow-lg';
// Set track size
let trackSize: string;
switch(size){
case('sm'): trackSize = 'w-12 h-6'; break;
case('lg'): trackSize = 'w-20 h-10'; break;
default: trackSize = 'w-16 h-8';
}
// Base Styles
const cBaseLabel: string = 'inline-block';
const cBaseTrack: string =
'flex rounded-full transition-all duration-[200ms] hover:brightness-90 cursor-pointer';
const cBaseThumb: string =
'w-[50%] h-full scale-[0.7] rounded-full cursor-pointer transition-all duration-[200ms] shadow-lg';
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space to toggle element
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
dispatch('keyup', event);
event.target.click();
}
}
// Set track size
let trackSize: string;
switch (size) {
case 'sm':
trackSize = 'w-12 h-6';
break;
case 'lg':
trackSize = 'w-20 h-10';
break;
default:
trackSize = 'w-16 h-8';
}
// Interactive
$: cTrackAccent = checked ? accent : 'bg-surface-400 dark:bg-surface-700 cursor-pointer';
$: cThumbBackground = checked ? 'bg-white' : 'bg-white/50';
$: cThumbPos = checked ? 'translate-x-full' : '';
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space to toggle element
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
dispatch('keyup', event);
event.target.click();
}
}
// Reactive Classes
$: classesTrack = `${cBaseTrack} ${trackSize} ${cTrackAccent}`;
$: classesThumb = `${cBaseThumb} ${cThumbBackground} ${cThumbPos}`;
// Interactive
$: cTrackAccent = checked ? accent : 'bg-surface-400 dark:bg-surface-700 cursor-pointer';
$: cThumbBackground = checked ? 'bg-white' : 'bg-white/50';
$: cThumbPos = checked ? 'translate-x-full' : '';
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
// Reactive Classes
$: classesTrack = `${cBaseTrack} ${trackSize} ${cTrackAccent}`;
$: classesThumb = `${cBaseThumb} ${cThumbBackground} ${cThumbPos}`;
// Prune $$restProps to avoid overwriting $$props.class
function prunedRestProps(): any {
delete $$restProps.class;
return $$restProps;
}
</script>
<div
id={label}
class="slide-toggle {cBaseLabel} {$$props.class}"
class:opacity-30={$$props.disabled}
data-testid="slide-toggle"
on:keydown={onKeyDown}
role="switch"
aria-label={label}
aria-checked={checked}
tabindex="0"
id={label}
class="slide-toggle {cBaseLabel} {$$props.class}"
class:opacity-30={$$props.disabled}
data-testid="slide-toggle"
on:keydown={onKeyDown}
role="switch"
aria-label={label}
aria-checked={checked}
tabindex="0"
>
<!-- Keep this, it triggers click/toggle event -->
<label>
<!-- Keep this, it triggers click/toggle event -->
<label>
<!-- Input (Hidden) -->
<input
type="checkbox"
class="hidden"
bind:checked
on:click
on:mouseover
on:focus
on:blur
{...prunedRestProps()}
disabled={$$props.disabled}
/>
<!-- Input (Hidden) -->
<input
type="checkbox"
class="hidden"
bind:checked
on:click
on:mouseover
on:focus
on:blur
{...prunedRestProps()}
disabled={$$props.disabled}
>
<div class="flex items-center space-x-4">
<!-- Slider Track/Thumb -->
<div class="track {classesTrack}" class:cursor-not-allowed={$$props.disabled}>
<div class="thumb {classesThumb}" class:cursor-not-allowed={$$props.disabled} />
</div>
<div class="flex items-center space-x-4">
<!-- Slider Track/Thumb -->
<div class="track {classesTrack}" class:cursor-not-allowed={$$props.disabled}>
<div class="thumb {classesThumb}" class:cursor-not-allowed={$$props.disabled}></div>
</div>
<!-- Label -->
{#if $$slots.default}<div><slot/></div>{/if}
</div>
</label>
<!-- Label -->
{#if $$slots.default}<div><slot /></div>{/if}
</div>
</label>
</div>

View File

@@ -9,17 +9,17 @@ import { afterEach, describe, it, expect } from 'vitest';
import SliderToggle from '$lib/SlideToggle/SlideToggle.svelte';
describe('SliderToggle.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders with props', async () => {
const { getByTestId } = render(SliderToggle, {
props: { color: 'bg-primary-200', size: 'lg' }
});
expect(getByTestId('slide-toggle')).toBeTruthy();
});
it('Renders with props', async () => {
const {getByTestId} = render(SliderToggle, {props: {color: 'bg-primary-200', size: 'lg'}});
expect(getByTestId('slide-toggle')).toBeTruthy();
});
it('Renders without props', async () => {
const {getByTestId} = render(SliderToggle);
expect(getByTestId('slide-toggle')).toBeTruthy();
});
});
it('Renders without props', async () => {
const { getByTestId } = render(SliderToggle);
expect(getByTestId('slide-toggle')).toBeTruthy();
});
});

View File

@@ -1,80 +1,82 @@
<!-- Reference: https://dribbble.com/shots/16221169-Figma-Material-Ui-components-Steppers-and-sliders -->
<script lang="ts">
import { getContext } from 'svelte';
import Button from '$lib/Button/Button.svelte';
<script lang='ts'>
import { getContext } from 'svelte';
import Button from '$lib/Button/Button.svelte';
// Props
export let index: number;
export let disabled: boolean = false;
export let done: boolean = false;
// Props
export let index: number;
export let disabled: boolean = false;
export let done: boolean = false;
// Context
export let dispatch: any = getContext('dispatch');
export let active: any = getContext('active');
export let length: number = getContext('length');
export let accent: string = getContext('accent');
export let background: string = getContext('background');
// Context
export let dispatch: any = getContext('dispatch');
export let active: any = getContext('active');
export let length: number = getContext('length');
export let accent: string = getContext('accent');
export let background: string = getContext('background');
// Base Classes
const cStep: string = 'flex space-x-4';
const cTimeline: string = 'text-center';
const cTimelineCircle: string =
'font-bold w-8 aspect-square flex justify-center items-center rounded-full';
const cTimelineBar: string = 'h-full w-1 mx-auto';
const cInfo: string = 'space-y-4';
const cNav: string = 'flex space-x-4';
// Base Classes
const cStep: string = 'flex space-x-4';
const cTimeline: string = 'text-center';
const cTimelineCircle: string = 'font-bold w-8 aspect-square flex justify-center items-center rounded-full';
const cTimelineBar: string = 'h-full w-1 mx-auto';
const cInfo: string = 'space-y-4';
const cNav: string = 'flex space-x-4';
// Functionality
function stepPrev(): void {
active.set($active - 1);
}
function stepNext(): void {
active.set($active + 1);
}
// Functionality
function stepPrev(): void { active.set($active-1); }
function stepNext(): void { active.set($active+1); }
// On complete, dispatch event
function onComplete() {
dispatch('complete', {});
}
// On complete, dispatch event
function onComplete() { dispatch('complete', {}); }
// Set Active Background Color
$: activeBg = index === $active ? `!text-white ${accent}` : background;
// Set Active Background Color
$: activeBg = index === $active ? `!text-white ${accent}` : background;
// Reactive Classes
$: classesCircle = `${cTimelineCircle} ${activeBg}`;
$: classesBar = `${cTimelineBar} ${background}`;
// Reactive Classes
$: classesCircle = `${cTimelineCircle} ${activeBg}`;
$: classesBar = `${cTimelineBar} ${background}`;
</script>
<section class="step {cStep} {$$props.class}" data-testid="step">
<!-- Timeline -->
<div class="timeline {cTimeline}">
<div class="timeline-circle {classesCircle}">{@html done ? '&check;' : index + 1}</div>
{#if index + 1 < length}<div class="timeline-bar {classesBar}" />{/if}
</div>
<!-- Timeline -->
<div class="timeline {cTimeline}">
<div class="timeline-circle {classesCircle}">{@html done ? '&check;' : index+1}</div>
{#if (index+1) < length}<div class="timeline-bar {classesBar}"></div>{/if}
</div>
<!-- Information -->
<div class="info {cInfo}">
<!-- Header -->
{#if $$slots.title || $$slots.subtitle}
<header class="space-y-1">
<!-- Slot: Title -->
{#if $$slots.title}<div><slot name="title" /></div>{/if}
<!-- Slot: Subtitle -->
{#if $$slots.subtitle}<div><slot name="subtitle" /></div>{/if}
</header>
{/if}
<!-- Information -->
<div class="info {cInfo}">
<!-- Header -->
{#if $$slots.title || $$slots.subtitle}
<header class="space-y-1">
<!-- Slot: Title -->
{#if $$slots.title}<div><slot name="title" /></div>{/if}
<!-- Slot: Subtitle -->
{#if $$slots.subtitle}<div><slot name="subtitle" /></div>{/if}
</header>
{/if}
<!-- Slot: Content -->
{#if $$slots.content && index === $active}<div><slot name="content" /></div>{/if}
<!-- Navigation -->
{#if index === $active}
<nav class="navigation {cNav}">
<Button variant="ring" on:click={stepPrev} disabled={index === 0}>Back</Button>
{#if ($active+1) < length}
<Button variant="filled" on:click={stepNext} {disabled}>Next &darr;</Button>
{:else}
<Button variant="filled-primary" on:click={onComplete} {disabled}>Complete</Button>
{/if}
</nav>
{/if}
</div>
<!-- Slot: Content -->
{#if $$slots.content && index === $active}<div><slot name="content" /></div>{/if}
<!-- Navigation -->
{#if index === $active}
<nav class="navigation {cNav}">
<Button variant="ring" on:click={stepPrev} disabled={index === 0}>Back</Button>
{#if $active + 1 < length}
<Button variant="filled" on:click={stepNext} {disabled}>Next &darr;</Button>
{:else}
<Button variant="filled-primary" on:click={onComplete} {disabled}>Complete</Button>
{/if}
</nav>
{/if}
</div>
</section>

View File

@@ -2,29 +2,27 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte'
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import Step from '$lib/Stepper/Step.svelte';
describe('Step.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Step);
expect(getByTestId('step')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Step);
expect(getByTestId('step')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Step, {
props: {
index: 0,
disabled: true,
done: true,
},
});
expect(getByTestId('step')).toBeTruthy();
})
})
it('Renders with props', () => {
const { getByTestId } = render(Step, {
props: {
index: 0,
disabled: true,
done: true
}
});
expect(getByTestId('step')).toBeTruthy();
});
});

View File

@@ -1,27 +1,27 @@
<script lang="ts">
import { createEventDispatcher, setContext } from 'svelte';
import { writable, type Writable } from 'svelte/store';
import { createEventDispatcher, setContext } from 'svelte';
import { writable, type Writable } from 'svelte/store';
// Event Dispacher
const dispatch = createEventDispatcher();
// Event Dispacher
const dispatch = createEventDispatcher();
// Props
export let active: Writable<number> = writable(0);
export let length: number = 0;
export let accent: string = 'bg-primary-500';
export let background: string = 'bg-surface-300 dark:bg-surface-700';
// Props
export let active: Writable<number> = writable(0);
export let length: number = 0;
export let accent: string = 'bg-primary-500';
export let background: string = 'bg-surface-300 dark:bg-surface-700';
// Context
setContext('dispatch', dispatch);
setContext('active', active);
setContext('length', length);
setContext('accent', accent);
setContext('background', background);
// Context
setContext('dispatch', dispatch);
setContext('active', active);
setContext('length', length);
setContext('accent', accent);
setContext('background', background);
// Base Clssses
const cBaseStepper: string = 'space-y-8';
// Base Clssses
const cBaseStepper: string = 'space-y-8';
</script>
<div class="stepper {cBaseStepper} {$$props.class}" data-testid="stepper">
<slot />
<slot />
</div>

View File

@@ -2,7 +2,7 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte'
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import { writable, type Writable } from 'svelte/store';
@@ -11,24 +11,22 @@ import Stepper from '$lib/Stepper/Stepper.svelte';
export let active: Writable<number> = writable(0);
describe('Stepper.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Stepper);
expect(getByTestId('stepper')).toBeTruthy();
});
it('Renders without props', async () => {
const { getByTestId } = render(Stepper);
expect(getByTestId('stepper')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Stepper, {
props: {
active,
length: 3,
accent: 'bg-green-500',
background: 'bg-surface-500',
},
});
expect(getByTestId('stepper')).toBeTruthy();
})
})
it('Renders with props', () => {
const { getByTestId } = render(Stepper, {
props: {
active,
length: 3,
accent: 'bg-green-500',
background: 'bg-surface-500'
}
});
expect(getByTestId('stepper')).toBeTruthy();
});
});

View File

@@ -1,52 +1,53 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { getContext } from "svelte";
import type { Writable } from 'svelte/store';
import { getContext } from 'svelte';
// Context
export let selected: Writable<any> = getContext('selected');
// Context
export let selected: Writable<any> = getContext('selected');
// Props
export let value: any = $selected.value;
// A11y
export let label: string = 'tab';
// Props
export let value: any = $selected.value;
// A11y
export let label: string = 'tab';
// Base Classes
const cBaseItem: string = 'list-none flex items-center border-b-2 pb-2 px-4 space-x-2 hover:opacity-70 cursor-pointer';
const cBaseLabel: string = 'font-semibold whitespace-nowrap';
// Base Classes
const cBaseItem: string =
'list-none flex items-center border-b-2 pb-2 px-4 space-x-2 hover:opacity-70 cursor-pointer';
const cBaseLabel: string = 'font-semibold whitespace-nowrap';
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space to toggle element
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
event.target.click();
}
}
// A11y Input Handlers
function onKeyDown(event: any): void {
// Enter/Space to toggle element
if (['Enter', 'Space'].includes(event.code)) {
event.preventDefault();
event.target.click();
}
}
// Reactive Classes
$: isSelected = (value == $selected);
$: cHighlight = isSelected ? getContext('highlight') : 'border-transparent';
$: cTextColor = isSelected ? getContext('color') : '';
$: classesItem = `${cBaseItem} ${cTextColor} ${cHighlight}`;
$: classesLabel = `${cBaseLabel}`;
// Reactive Classes
$: isSelected = value == $selected;
$: cHighlight = isSelected ? getContext('highlight') : 'border-transparent';
$: cTextColor = isSelected ? getContext('color') : '';
$: classesItem = `${cBaseItem} ${cTextColor} ${cHighlight}`;
$: classesLabel = `${cBaseLabel}`;
</script>
<!-- REMOVED: aria-checked={isSelected} -->
<li
class="tab
class="tab
${classesItem}
{$$props.class}"
on:click={()=>{selected.set(value)}}
data-testid="tab"
on:keydown={onKeyDown}
role="tab"
tabindex="0"
on:click={() => {
selected.set(value);
}}
data-testid="tab"
on:keydown={onKeyDown}
role="tab"
tabindex="0"
>
<!-- Slot: Lead -->
{#if $$slots.lead}<span><slot name="lead" /></span>{/if}
<!-- Slot: Lead -->
{#if $$slots.lead}<span><slot name="lead"/></span>{/if}
<!-- Label -->
{#if $$slots.default}<span class="{classesLabel}" {label}><slot /></span>{/if}
</li>
<!-- Label -->
{#if $$slots.default}<span class={classesLabel} {label}><slot /></span>{/if}
</li>

View File

@@ -1,4 +1,3 @@
/**
* @vitest-environment jsdom
*/
@@ -6,15 +5,13 @@
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import Tab from '$lib/Tab/Tab.svelte'
import Tab from '$lib/Tab/Tab.svelte';
describe('Tab.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(Tab, {props: {value: 'test'}});
expect(getByTestId('tab')).toBeTruthy();
});
});
it('Renders without props', async () => {
const { getByTestId } = render(Tab, { props: { value: 'test' } });
expect(getByTestId('tab')).toBeTruthy();
});
});

View File

@@ -1,40 +1,52 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import { onMount, setContext } from "svelte";
import type { Writable } from 'svelte/store';
import { onMount, setContext } from 'svelte';
// Props
export let selected: Writable<any>;
export let justify: string = 'justify-start';
export let highlight: string = 'border-primary-500';
export let color: string = 'text-primary-500';
// A11y
export let labeledby: string = undefined;
export let label: string = undefined;
// Props
export let selected: Writable<any>;
export let justify: string = 'justify-start';
export let highlight: string = 'border-primary-500';
export let color: string = 'text-primary-500';
// A11y
export let labeledby: string = undefined;
export let label: string = undefined;
// Set Context
setContext('selected', selected);
setContext('highlight', highlight);
setContext('color', color);
// Set Context
setContext('selected', selected);
setContext('highlight', highlight);
setContext('color', color);
// Classes
const cBaseGroup: string = 'hide-scrollbar overflow-x-auto flex border-b-2 border-surface-300 dark:border-surface-700';
// Classes
const cBaseGroup: string =
'hide-scrollbar overflow-x-auto flex border-b-2 border-surface-300 dark:border-surface-700';
// Handle Home/End Input
let elemTabGroup: HTMLElement;
onMount(() => {
elemTabGroup.addEventListener('keydown', (event: any) => {
if (['Home', 'End'].includes(event.code)) {
event.preventDefault();
if(event.code === 'Home'){ (elemTabGroup.children[0] as HTMLElement).focus(); }
if(event.code === 'End'){ (elemTabGroup.children[elemTabGroup.children.length-1] as HTMLElement).focus(); }
};
});
});
// Handle Home/End Input
let elemTabGroup: HTMLElement;
onMount(() => {
elemTabGroup.addEventListener('keydown', (event: any) => {
if (['Home', 'End'].includes(event.code)) {
event.preventDefault();
if (event.code === 'Home') {
(elemTabGroup.children[0] as HTMLElement).focus();
}
if (event.code === 'End') {
(elemTabGroup.children[elemTabGroup.children.length - 1] as HTMLElement).focus();
}
}
});
});
// Reactive Classes
$: classesGroup = `${cBaseGroup} ${justify} ${$$props.class||''}`;
// Reactive Classes
$: classesGroup = `${cBaseGroup} ${justify} ${$$props.class || ''}`;
</script>
<nav bind:this={elemTabGroup} data-testid="tab-group" class="tab-group {classesGroup}" role="tablist" aria-labelledby={labeledby} aria-label={label}>
<slot />
</nav>
<nav
bind:this={elemTabGroup}
data-testid="tab-group"
class="tab-group {classesGroup}"
role="tablist"
aria-labelledby={labeledby}
aria-label={label}
>
<slot />
</nav>

View File

@@ -2,18 +2,16 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import TabGroup from '$lib/Tab/TabGroup.svelte'
describe('TabGroup.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(TabGroup);
expect(getByTestId('tab-group')).toBeTruthy();
});
});
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import TabGroup from '$lib/Tab/TabGroup.svelte';
describe('TabGroup.svelte', () => {
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(TabGroup);
expect(getByTestId('tab-group')).toBeTruthy();
});
});

View File

@@ -1,234 +1,266 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { sortAscNumber, sortDescNumber, sortAscString, sortDescString } from "./DataTableService";
import { createEventDispatcher } from 'svelte';
import { sortAscNumber, sortDescNumber, sortAscString, sortDescString } from './DataTableService';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
// Props
export let headings: any[] = [];
export let source: any[] = [];
export let search: any = '';
export let sort: string = '';
export let count: number = source.length;
export let interactive: boolean = false;
export let async: boolean = false;
// Props: Design
export let header: string = 'bg-surface-50 dark:bg-surface-700';
export let body: string = 'bg-surface-200 dark:bg-surface-800';
export let text: string = 'text-sm';
export let hover: string = 'hover:bg-primary-500/10';
// A11y
export let labelledby: string = undefined;
export let describedby: string = undefined;
// Props
export let headings: any[] = [];
export let source: any[] = [];
export let search: any = '';
export let sort: string = '';
export let count: number = source.length;
export let interactive: boolean = false;
export let async: boolean = false;
// Props: Design
export let header: string = 'bg-surface-50 dark:bg-surface-700';
export let body: string = 'bg-surface-200 dark:bg-surface-800';
export let text: string = 'text-sm';
export let hover: string = 'hover:bg-primary-500/10';
// A11y
export let labelledby: string = undefined;
export let describedby: string = undefined;
// Local
let elemTable: HTMLElement;
let sourceUnfiltered: any[] = [...source]; // clone
let sorted: any = {by: sort, asc: false};
// Local
let elemTable: HTMLElement;
let sourceUnfiltered: any[] = [...source]; // clone
let sorted: any = { by: sort, asc: false };
// Base Classes
const cBase: string = 'space-y-4';
const cBaseWrapper: string = 'overflow-x-auto w-full rounded-lg border-[1px] border-surface-500/10 border-inset';
const cBaseTable: string = 'w-full overflow-hidden table-auto';
const cBaseEmpty: string = 'p-4 text-center';
// ---
const cBaseHead: string = '';
const cBaseHeadRow: string = 'capitalize font-medium text-left text-surface-900 dark:text-surface-50';
const cBaseHeadCol: string = `p-3 py-4 whitespace-nowrap cursor-pointer`;
// ---
const cBaseBody: string = '';
const cBaseBodyRow: string = 'border-t border-surface-500/10 even:bg-black/[4%]';
const cBaseBodyCol: string = 'p-3 font-medium text-surface-900 whitespace-nowrap md:whitespace-normal dark:text-white';
// Base Classes
const cBase: string = 'space-y-4';
const cBaseWrapper: string =
'overflow-x-auto w-full rounded-lg border-[1px] border-surface-500/10 border-inset';
const cBaseTable: string = 'w-full overflow-hidden table-auto';
const cBaseEmpty: string = 'p-4 text-center';
// ---
const cBaseHead: string = '';
const cBaseHeadRow: string =
'capitalize font-medium text-left text-surface-900 dark:text-surface-50';
const cBaseHeadCol: string = `p-3 py-4 whitespace-nowrap cursor-pointer`;
// ---
const cBaseBody: string = '';
const cBaseBodyRow: string = 'border-t border-surface-500/10 even:bg-black/[4%]';
const cBaseBodyCol: string =
'p-3 font-medium text-surface-900 whitespace-nowrap md:whitespace-normal dark:text-white';
function headKeyByIndex(i: number): string {
return Object.keys(source[0])[i];
}
function headKeyByIndex(i: number): string {
return Object.keys(source[0])[i];
}
function onHeadSelect(i: number) {
const column: string = headKeyByIndex(i);
dispatch('sorted', column);
async ? sorted.by = column : localSort(column);
}
function onHeadSelect(i: number) {
const column: string = headKeyByIndex(i);
dispatch('sorted', column);
async ? (sorted.by = column) : localSort(column);
}
function onRowSelect(r: Object) {
if (interactive === true) { dispatch('selected', r); }
}
function onRowSelect(r: Object) {
if (interactive === true) {
dispatch('selected', r);
}
}
// Sort
function localSort(column: string): void {
if (!source.length) return;
// If same column, toggle asc/desc
if (column === sorted.by) {
sorted.asc = !sorted.asc;
sorted.asc ? sortAsc(column) : sortDesc(column);
// If new column, sort asc
} else {
sorted.asc = true;
sortAsc(column);
}
// Update states
sorted.by = column;
updateRowCount();
}
// Sort
function localSort(column: string): void {
if (!source.length) return;
// If same column, toggle asc/desc
if (column === sorted.by) {
sorted.asc = !sorted.asc;
sorted.asc ? sortAsc(column) : sortDesc(column);
// If new column, sort asc
} else {
sorted.asc = true;
sortAsc(column);
}
// Update states
sorted.by = column;
updateRowCount();
}
function sortAsc(key: string): void {
source = typeof source[0][key] === 'number' ? sortAscNumber(source, key) : sortAscString(source, key);
}
function sortDesc(key: string): void {
source = typeof source[0][key] === 'number' ? sortDescNumber(source, key) : sortDescString(source, key);
}
function sortAsc(key: string): void {
source =
typeof source[0][key] === 'number' ? sortAscNumber(source, key) : sortAscString(source, key);
}
// Search
function localSearch(): void {
if (async) return;
source = sourceUnfiltered.filter(row => {
const match: boolean = JSON.stringify(Object.values(row)).toLowerCase().includes(search.toLowerCase());
if (match) return row;
});
updateRowCount();
}
function sortDesc(key: string): void {
source =
typeof source[0][key] === 'number'
? sortDescNumber(source, key)
: sortDescString(source, key);
}
// Count
function updateRowCount(): void { count = source.length; }
// Search
function localSearch(): void {
if (async) return;
source = sourceUnfiltered.filter((row) => {
const match: boolean = JSON.stringify(Object.values(row))
.toLowerCase()
.includes(search.toLowerCase());
if (match) return row;
});
updateRowCount();
}
// A11y Input Handler
function onKeyDown(event: any): void {
// Arrow Keys
const hotKeys: string[] = ['ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'Home', 'End'];
if (hotKeys.includes(event.code)) {
event.preventDefault();
switch (event.code) {
case ('ArrowUp'): setActiveCell(0, -1); break;
case ('ArrowDown'): setActiveCell(0, 1); break;
case ('ArrowLeft'): setActiveCell(-1, 0); break;
case ('ArrowRight'): setActiveCell(1, 0); break;
case ('Home'): jumpToFirstColumn(); break;
case ('End'): jumpToLastColumn(); break;
default: break;
}
}
}
function setActiveCell(x: number, y: number): void {
// Focused Element
const focusedElem: any = document.activeElement;
const focusedElemRowIndex: number = parseInt(focusedElem.parentElement.ariaRowIndex);
const focusedElemColIndex: number = parseInt(focusedElem.ariaColIndex);
// Target Element
const targetRowElement: HTMLElement = elemTable.querySelector(`[aria-rowindex="${focusedElemRowIndex + y}"]`);
if (targetRowElement !== null) {
const targetColElement: HTMLElement = targetRowElement.querySelector(`[aria-colindex="${focusedElemColIndex + x}"]`);
if (targetColElement !== null) { targetColElement.focus(); }
}
}
function getTargetElem(): any {
// Focused Element
const focusedElem: any = document.activeElement;
const focusedElemRowIndex: number = parseInt(focusedElem.parentElement.ariaRowIndex);
// Return Target Element
return elemTable.querySelector(`[aria-rowindex="${focusedElemRowIndex}"]`);
}
function jumpToFirstColumn(): void {
const targetRowElement: any = getTargetElem();
targetRowElement.firstChild.focus();
}
function jumpToLastColumn(): void {
const targetRowElement: any = getTargetElem();
const lastIndex: number = targetRowElement.children.length - 1;
targetRowElement.children[lastIndex].focus();
}
// Count
function updateRowCount(): void {
count = source.length;
}
// On Prop Change
$: if (sort) { localSort(sort); }
$: if (search || search === '') { localSearch(); }
// A11y Input Handler
function onKeyDown(event: any): void {
// Arrow Keys
const hotKeys: string[] = ['ArrowRight', 'ArrowUp', 'ArrowLeft', 'ArrowDown', 'Home', 'End'];
if (hotKeys.includes(event.code)) {
event.preventDefault();
switch (event.code) {
case 'ArrowUp':
setActiveCell(0, -1);
break;
case 'ArrowDown':
setActiveCell(0, 1);
break;
case 'ArrowLeft':
setActiveCell(-1, 0);
break;
case 'ArrowRight':
setActiveCell(1, 0);
break;
case 'Home':
jumpToFirstColumn();
break;
case 'End':
jumpToLastColumn();
break;
default:
break;
}
}
}
function setActiveCell(x: number, y: number): void {
// Focused Element
const focusedElem: any = document.activeElement;
const focusedElemRowIndex: number = parseInt(focusedElem.parentElement.ariaRowIndex);
const focusedElemColIndex: number = parseInt(focusedElem.ariaColIndex);
// Target Element
const targetRowElement: HTMLElement = elemTable.querySelector(
`[aria-rowindex="${focusedElemRowIndex + y}"]`
);
if (targetRowElement !== null) {
const targetColElement: HTMLElement = targetRowElement.querySelector(
`[aria-colindex="${focusedElemColIndex + x}"]`
);
if (targetColElement !== null) {
targetColElement.focus();
}
}
}
function getTargetElem(): any {
// Focused Element
const focusedElem: any = document.activeElement;
const focusedElemRowIndex: number = parseInt(focusedElem.parentElement.ariaRowIndex);
// Return Target Element
return elemTable.querySelector(`[aria-rowindex="${focusedElemRowIndex}"]`);
}
function jumpToFirstColumn(): void {
const targetRowElement: any = getTargetElem();
targetRowElement.firstChild.focus();
}
function jumpToLastColumn(): void {
const targetRowElement: any = getTargetElem();
const lastIndex: number = targetRowElement.children.length - 1;
targetRowElement.children[lastIndex].focus();
}
// Reactive Classes
$: classesTable = `${cBaseTable} ${text}`;
$: classesHeader = `${cBaseHead} ${header}`;
$: classesHeadCol = `${cBaseHeadCol} ${hover}`;
$: classesBody = `${cBaseBody} ${body}`;
$: cRowInteractive = interactive ? `${hover} cursor-pointer` : '';
$: classesBodyRoll = `${cBaseBodyRow} ${cRowInteractive}`;
// On Prop Change
$: if (sort) {
localSort(sort);
}
$: if (search || search === '') {
localSearch();
}
// Reactive Classes
$: classesTable = `${cBaseTable} ${text}`;
$: classesHeader = `${cBaseHead} ${header}`;
$: classesHeadCol = `${cBaseHeadCol} ${hover}`;
$: classesBody = `${cBaseBody} ${body}`;
$: cRowInteractive = interactive ? `${hover} cursor-pointer` : '';
$: classesBodyRoll = `${cBaseBodyRow} ${cRowInteractive}`;
</script>
<div class="data-table {cBase} {$$props.class}" data-testid="data-table">
<!-- Header -->
{#if $$slots.header}<header class="table-header"><slot name="header" /></header>{/if}
<!-- Header -->
{#if $$slots.header}<header class="table-header"><slot name="header" /></header>{/if}
<!-- Wrapper -->
<div class="table-wrapper {cBaseWrapper}">
<!-- Table -->
<table
bind:this={elemTable}
class="table {classesTable}"
on:keydown={onKeyDown}
role="grid"
aria-labelledby={labelledby}
aria-describedby={describedby}
aria-colcount={headings.length}
aria-rowcount={source.length}
>
<!-- Head -->
<thead class="table-head {classesHeader}">
<!-- Head -->
<tr class="table-head-row {cBaseHeadRow}">
{#each headings as head, i}
<th
class="table-head-col {classesHeadCol}"
scope="col"
on:click={() => {
onHeadSelect(i);
}}
role="columnheader"
>
{@html head}
<span class="inline-block w-3 text-center ml-1 opacity-50">
{#if headKeyByIndex(i) === sorted.by}
{@html sorted.asc ? '&darr;' : '&uarr;'}
{/if}
</span>
</th>
{/each}
</tr>
</thead>
<!-- Wrapper -->
<div class="table-wrapper {cBaseWrapper}">
<!-- Table -->
<table
bind:this={elemTable}
class="table {classesTable}"
on:keydown={onKeyDown}
role="grid"
aria-labelledby={labelledby}
aria-describedby={describedby}
aria-colcount={headings.length}
aria-rowcount={source.length}
>
<!-- Head -->
<thead class="table-head {classesHeader}">
<!-- Head -->
<tr class="table-head-row {cBaseHeadRow}">
{#each headings as head, i}
<th
class="table-head-col {classesHeadCol}"
scope="col"
on:click={() => { onHeadSelect(i) }}
role="columnheader"
>
{@html head}
<span class="inline-block w-3 text-center ml-1 opacity-50">
{#if headKeyByIndex(i) === sorted.by}
{@html sorted.asc ? '&darr;' : '&uarr;'}
{/if}
</span>
</th>
{/each}
</tr>
</thead>
<!-- Body -->
<tbody class="table-body {classesBody}">
{#if source.length > 0}
{#each source as row, rowIndex}
<tr
class="table-body-row {classesBodyRoll}"
on:click={() => { onRowSelect(row) }}
aria-rowindex={rowIndex+1}
>
{#each Object.values(row) as cell, colIndex}
<td
class="table-body-col {cBaseBodyCol}"
role="gridcell"
aria-colindex={colIndex+1}
tabindex={colIndex === 0 ? 0 : -1}
>{@html cell}</td>
{/each}
</tr>
{/each}
{:else}
<!-- Empty -->
<tr><td colspan={headings.length} class="table-empty {cBaseEmpty}">
{#if $$slots.empty}<slot name="empty" />{:else}No results available.{/if}
</td></tr>
{/if}
</tbody>
</table>
</div>
<!-- Footer -->
{#if $$slots.footer}<footer class="table-footer"><slot name="footer" /></footer>{/if}
<!-- Body -->
<tbody class="table-body {classesBody}">
{#if source.length > 0}
{#each source as row, rowIndex}
<tr
class="table-body-row {classesBodyRoll}"
on:click={() => {
onRowSelect(row);
}}
aria-rowindex={rowIndex + 1}
>
{#each Object.values(row) as cell, colIndex}
<td
class="table-body-col {cBaseBodyCol}"
role="gridcell"
aria-colindex={colIndex + 1}
tabindex={colIndex === 0 ? 0 : -1}>{@html cell}</td
>
{/each}
</tr>
{/each}
{:else}
<!-- Empty -->
<tr
><td colspan={headings.length} class="table-empty {cBaseEmpty}">
{#if $$slots.empty}<slot name="empty" />{:else}No results available.{/if}
</td></tr
>
{/if}
</tbody>
</table>
</div>
<!-- Footer -->
{#if $$slots.footer}<footer class="table-footer"><slot name="footer" /></footer>{/if}
</div>

View File

@@ -2,33 +2,33 @@
* @vitest-environment jsdom
*/
import { cleanup, render } from '@testing-library/svelte'
import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, it, expect } from 'vitest';
import DataTable from '$lib/Table/DataTable.svelte';
export let headings: any[] = ['Foo', 'Bar'];
export let source: any[] = [
{foo: 'x', bar: 'y'},
{foo: 'x', bar: 'y'},
{foo: 'x', bar: 'y'},
{ foo: 'x', bar: 'y' },
{ foo: 'x', bar: 'y' },
{ foo: 'x', bar: 'y' }
];
describe('DataTable.svelte', () => {
afterEach(() => cleanup());
afterEach(() => cleanup())
it('Renders with props', async () => {
const { getByTestId } = render(DataTable);
expect(getByTestId('data-table')).toBeTruthy();
});
it('Renders with props', async () => {
const { getByTestId } = render(DataTable);
expect( getByTestId('data-table') ).toBeTruthy();
})
it('Renders with props', async()=>{
const { getByTestId } = render(DataTable, {props: {
headings,
source,
}});
expect( getByTestId('data-table') ).toBeTruthy();
})
})
it('Renders with props', async () => {
const { getByTestId } = render(DataTable, {
props: {
headings,
source
}
});
expect(getByTestId('data-table')).toBeTruthy();
});
});

View File

@@ -3,37 +3,37 @@
// Data Mapping ---
export function mapTableSource(keys: any[], object: any[]): any[] {
return object.map(origRow => {
const mappedRow: any = {};
for (let i = 0; i <keys.length; i++) {
mappedRow[keys[i]] = origRow[keys[i]];
}
return mappedRow;
});
return object.map((origRow) => {
const mappedRow: any = {};
for (let i = 0; i < keys.length; i++) {
mappedRow[keys[i]] = origRow[keys[i]];
}
return mappedRow;
});
}
// Sort Handlers ---
export function sortAscNumber(arr: any[], key: string): any[] {
return arr.sort((x, y) => x[key] - y[key]);
return arr.sort((x, y) => x[key] - y[key]);
}
export function sortDescNumber(arr: any[], key: string): any[] {
return arr.sort((x, y) => y[key] - x[key]);
return arr.sort((x, y) => y[key] - x[key]);
}
export function sortAscString(arr: any[], key: any): any[] {
return arr.sort((x, y) => {
let a = String(x[key]).toUpperCase(),
b = String(y[key]).toUpperCase();
return a == b ? 0 : a > b ? 1 : -1;
});
return arr.sort((x, y) => {
let a = String(x[key]).toUpperCase(),
b = String(y[key]).toUpperCase();
return a == b ? 0 : a > b ? 1 : -1;
});
}
export function sortDescString(arr: any[], key: any): any[] {
return arr.sort((x, y) => {
let a = String(x[key]).toUpperCase(),
b = String(y[key]).toUpperCase();
return a == b ? 0 : a > b ? -1 : 1;
});
}
return arr.sort((x, y) => {
let a = String(x[key]).toUpperCase(),
b = String(y[key]).toUpperCase();
return a == b ? 0 : a > b ? -1 : 1;
});
}

View File

@@ -1,13 +1,18 @@
<script lang="ts">
export let palette: string;
const labels: number[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
function objValuesToArry(obj: any): any[] { return Object.values(obj); }
export let palette: string;
const labels: number[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
function objValuesToArry(obj: any): any[] {
return Object.values(obj);
}
</script>
<div class="mt-2 grid grid-cols-10 gap-[1px]">
{#each objValuesToArry(palette) as shade, i}
<div class="border border-gray-500/30 flex justify-center items-center" style:background={shade.hex ? shade.hex : shade}>
<div class="text-black font-bold text-[8px] md:text-xs">{labels[i]}</div>
</div>
{/each}
</div>
{#each objValuesToArry(palette) as shade, i}
<div
class="border border-gray-500/30 flex justify-center items-center"
style:background={shade.hex ? shade.hex : shade}
>
<div class="text-black font-bold text-[8px] md:text-xs">{labels[i]}</div>
</div>
{/each}
</div>

View File

@@ -1,131 +1,174 @@
<script lang="ts">
// https://github.com/bartbergmans/Palettey
import { createPalleteFromColor } from "palettey";
// https://github.com/bartbergmans/Palettey
import { createPalleteFromColor } from 'palettey';
import Swatches from "./Swatches.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import Swatches from './Swatches.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
const regexHexColor = new RegExp(/^#[0-9a-f]{6}$/i);
const formValues: any = { primary: '#10b981', accent: '#6366F1', warning: '#f43f5e', surface: '#6b7280' };
const hexShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
const rgbShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
let cssSnippet: string = ``;
const regexHexColor = new RegExp(/^#[0-9a-f]{6}$/i);
const formValues: any = {
primary: '#10b981',
accent: '#6366F1',
warning: '#f43f5e',
surface: '#6b7280'
};
const hexShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
const rgbShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
let cssSnippet: string = ``;
// Helpers ---
// Helpers ---
function pruneHash(v: string): string { return v.replace('#',''); }
function pruneHash(v: string): string {
return v.replace('#', '');
}
// Hex -> RGB
// Source: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function hexToRgb(hex): string {
hex = pruneHash(hex);
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
const color = {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
return `${color.r} ${color.g} ${color.b}`;
}
return '(invalid)';
}
// Hex -> RGB
// Source: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function hexToRgb(hex): string {
hex = pruneHash(hex);
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (result) {
const color = {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
return `${color.r} ${color.g} ${color.b}`;
}
return '(invalid)';
}
// Process ---
// Process ---
// Process each input individually if valid hex color string
function processPrimary(): void { if (regexHexColor.test(formValues.primary)) { generateHexShades('primary'); } }
function processAccent(): void { if (regexHexColor.test(formValues.accent)) { generateHexShades('accent'); } }
function processWarning(): void { if (regexHexColor.test(formValues.warning)) { generateHexShades('warning'); } }
function processSurface(): void { if (regexHexColor.test(formValues.surface)) { generateHexShades('surface'); } }
// Process each input individually if valid hex color string
function processPrimary(): void {
if (regexHexColor.test(formValues.primary)) {
generateHexShades('primary');
}
}
function processAccent(): void {
if (regexHexColor.test(formValues.accent)) {
generateHexShades('accent');
}
}
function processWarning(): void {
if (regexHexColor.test(formValues.warning)) {
generateHexShades('warning');
}
}
function processSurface(): void {
if (regexHexColor.test(formValues.surface)) {
generateHexShades('surface');
}
}
// Creates a hex shade palette from a singular hex value
function generateHexShades(key: string): void {
hexShades[key] = createPalleteFromColor(key, pruneHash(formValues[key]), {
useLightness: false,
h: 0, s: 0,
lMin: 5, lMax: 100,
})[key];
// Generate RGB shades
generateRgbShades(key);
}
// Creates a hex shade palette from a singular hex value
function generateHexShades(key: string): void {
hexShades[key] = createPalleteFromColor(key, pruneHash(formValues[key]), {
useLightness: false,
h: 0,
s: 0,
lMin: 5,
lMax: 100
})[key];
// Generate RGB shades
generateRgbShades(key);
}
// Generate a set of RGB shades from Hex shades
function generateRgbShades(key: string): void {
Object.entries(hexShades[key]).forEach((hexShade: any) => {
const [hexKey, hexValue] = hexShade;
rgbShades[key][hexKey] = hexToRgb(hexValue);
});
// Update CSS
generateCssSnippet(key);
}
// Generate a set of RGB shades from Hex shades
function generateRgbShades(key: string): void {
Object.entries(hexShades[key]).forEach((hexShade: any) => {
const [hexKey, hexValue] = hexShade;
rgbShades[key][hexKey] = hexToRgb(hexValue);
});
// Update CSS
generateCssSnippet(key);
}
// Generates the CSS snippet
function generateCssSnippet(key: string): void {
let css: string = '';
Object.entries(rgbShades).forEach((set: any, i: number) => {
const [colorName, shades] = set;
// Add Comment Row
css += `${i === 0 ? `\t/* --- Custom Theme --- */\n` : '\n'}\t/* ${colorName} (${formValues[colorName]}) */`;
// Per each entry, add custom property key/value row
Object.entries(shades).forEach((shade) => {
const [shadeKey, shadeValue] = shade;
css += `\n\t--color-${colorName}-${shadeKey}: ${shadeValue};`;
});
});
// Wrap snippet with `:root{}`
cssSnippet = `:root {\n${css}\n}`;
}
// Generates the CSS snippet
function generateCssSnippet(key: string): void {
let css: string = '';
Object.entries(rgbShades).forEach((set: any, i: number) => {
const [colorName, shades] = set;
// Add Comment Row
css += `${i === 0 ? `\t/* --- Custom Theme --- */\n` : '\n'}\t/* ${colorName} (${
formValues[colorName]
}) */`;
// Per each entry, add custom property key/value row
Object.entries(shades).forEach((shade) => {
const [shadeKey, shadeValue] = shade;
css += `\n\t--color-${colorName}-${shadeKey}: ${shadeValue};`;
});
});
// Wrap snippet with `:root{}`
cssSnippet = `:root {\n${css}\n}`;
}
// On Init ---
// On Init ---
function onInit(): void {
processPrimary();
processAccent();
processWarning();
processSurface();
}
onInit();
function onInit(): void {
processPrimary();
processAccent();
processWarning();
processSurface();
}
onInit();
</script>
<form class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- Color Inputs -->
<fieldset class="space-y-4">
<!-- Primary -->
<label class="flex-1" for="primary">
<span>Primary</span>
<input
type="text"
placeholder="#FFFFFF"
bind:value={formValues.primary}
on:keyup={processPrimary}
/>
<Swatches palette={hexShades.primary} />
</label>
<!-- Color Inputs -->
<fieldset class="space-y-4">
<!-- Accent -->
<label class="flex-1" for="accent">
<span>Accent</span>
<input
type="text"
placeholder="#FFFFFF"
bind:value={formValues.accent}
on:keyup={processAccent}
/>
<Swatches palette={hexShades.accent} />
</label>
<!-- Primary -->
<label class="flex-1" for="primary">
<span>Primary</span>
<input type="text" placeholder="#FFFFFF" bind:value={formValues.primary} on:keyup={processPrimary}>
<Swatches palette={hexShades.primary} />
</label>
<!-- Warning -->
<label class="flex-1" for="warning">
<span>Warning</span>
<input
type="text"
placeholder="#FFFFFF"
bind:value={formValues.warning}
on:keyup={processWarning}
/>
<Swatches palette={hexShades.warning} />
</label>
<!-- Accent -->
<label class="flex-1" for="accent">
<span>Accent</span>
<input type="text" placeholder="#FFFFFF" bind:value={formValues.accent} on:keyup={processAccent}>
<Swatches palette={hexShades.accent} />
</label>
<!-- Surface -->
<label class="flex-1" for="surface">
<span>Surface</span>
<input
type="text"
placeholder="#FFFFFF"
bind:value={formValues.surface}
on:keyup={processSurface}
/>
<Swatches palette={hexShades.surface} />
</label>
</fieldset>
<!-- Warning -->
<label class="flex-1" for="warning">
<span>Warning</span>
<input type="text" placeholder="#FFFFFF" bind:value={formValues.warning} on:keyup={processWarning}>
<Swatches palette={hexShades.warning} />
</label>
<!-- Surface -->
<label class="flex-1" for="surface">
<span>Surface</span>
<input type="text" placeholder="#FFFFFF" bind:value={formValues.surface} on:keyup={processSurface}>
<Swatches palette={hexShades.surface} />
</label>
</fieldset>
<!-- Codeblock -->
<fieldset class="space-y-4">
<CodeBlock language="css" code={`${cssSnippet}`} class="max-h-[440px] overflow-y-auto"></CodeBlock>
</fieldset>
</form>
<!-- Codeblock -->
<fieldset class="space-y-4">
<CodeBlock language="css" code={`${cssSnippet}`} class="max-h-[440px] overflow-y-auto" />
</fieldset>
</form>

View File

@@ -1,107 +1,127 @@
<script lang="ts">
import { tailwindColors } from "./colors";
import { tailwindColors } from './colors';
import Swatches from "./Swatches.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import Swatches from './Swatches.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
const colorLabels: any[] = Object.keys(tailwindColors);
const formValues: any = {
primary: 'emerald',
accent: 'indigo',
warning: 'rose',
surface: 'gray'
};
const rgbShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
let cssSnippet: string = ``;
const colorLabels: any[] = Object.keys(tailwindColors);
const formValues: any = {
primary: 'emerald',
accent: 'indigo',
warning: 'rose',
surface: 'gray'
};
const rgbShades: any = { primary: {}, accent: {}, warning: {}, surface: {} };
let cssSnippet: string = ``;
// Process ---
// Process ---
function processPrimary(): void { setRgbShades('primary', formValues.primary); }
function processAccent(): void { setRgbShades('accent', formValues.accent); }
function processWarning(): void { setRgbShades('warning', formValues.warning); }
function processSurface(): void { setRgbShades('surface', formValues.surface); }
function processPrimary(): void {
setRgbShades('primary', formValues.primary);
}
function processAccent(): void {
setRgbShades('accent', formValues.accent);
}
function processWarning(): void {
setRgbShades('warning', formValues.warning);
}
function processSurface(): void {
setRgbShades('surface', formValues.surface);
}
// Set rgb shades based on selection
function setRgbShades(key: string, selectionKey: string): void {
rgbShades[key] = tailwindColors[selectionKey];
generateCssSnippet(key);
}
// Set rgb shades based on selection
function setRgbShades(key: string, selectionKey: string): void {
rgbShades[key] = tailwindColors[selectionKey];
generateCssSnippet(key);
}
// Generates the CSS snippet
function generateCssSnippet(key: string): void {
let css: string = '';
Object.entries(rgbShades).forEach((set: any, i: number) => {
const [colorName, shades] = set;
// Add Comment Row
css += `${i === 0 ? `\t/* --- Tailwind Theme --- */\n` : '\n'}\t/* ${colorName} (${formValues[colorName]}) */`;
// Per each entry, add custom property key/value row
Object.entries(shades).forEach((shade) => {
const [shadeKey, shadeValue] = shade;
css += `\n\t--color-${colorName}-${shadeKey}: ${shadeValue['rgb']};`;
});
});
// Wrap snippet with `:root{}`
cssSnippet = `:root {\n${css}\n}`;
}
// Generates the CSS snippet
function generateCssSnippet(key: string): void {
let css: string = '';
Object.entries(rgbShades).forEach((set: any, i: number) => {
const [colorName, shades] = set;
// Add Comment Row
css += `${i === 0 ? `\t/* --- Tailwind Theme --- */\n` : '\n'}\t/* ${colorName} (${
formValues[colorName]
}) */`;
// Per each entry, add custom property key/value row
Object.entries(shades).forEach((shade) => {
const [shadeKey, shadeValue] = shade;
css += `\n\t--color-${colorName}-${shadeKey}: ${shadeValue['rgb']};`;
});
});
// Wrap snippet with `:root{}`
cssSnippet = `:root {\n${css}\n}`;
}
// On Init ---
function onInit(): void {
processPrimary();
processAccent();
processWarning();
processSurface();
}
onInit();
// On Init ---
function onInit(): void {
processPrimary();
processAccent();
processWarning();
processSurface();
}
onInit();
</script>
<form class="grid grid-cols-1 lg:grid-cols-2 gap-4">
<!-- Color Selection -->
<fieldset class="space-y-4">
<!-- Primary -->
<label>
<span>Primary</span>
<select
name="primary"
id="primary"
bind:value={formValues.primary}
on:change={processPrimary}
>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.primary} />
</label>
<!-- Color Selection -->
<fieldset class="space-y-4">
<!-- Accent -->
<label>
<span>Accent</span>
<select name="accent" id="accent" bind:value={formValues.accent} on:change={processAccent}>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.accent} />
</label>
<!-- Primary -->
<label>
<span>Primary</span>
<select name="primary" id="primary" bind:value={formValues.primary} on:change={processPrimary}>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.primary} />
</label>
<!-- Warning -->
<label>
<span>Warning</span>
<select
name="warning"
id="warning"
bind:value={formValues.warning}
on:change={processWarning}
>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.warning} />
</label>
<!-- Accent -->
<label>
<span>Accent</span>
<select name="accent" id="accent" bind:value={formValues.accent} on:change={processAccent}>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.accent} />
</label>
<!-- Warning -->
<label>
<span>Warning</span>
<select name="warning" id="warning" bind:value={formValues.warning} on:change={processWarning}>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.warning} />
</label>
<!-- Surface -->
<label>
<span>Surface</span>
<select name="surface" id="surface" bind:value={formValues.surface} on:change={processSurface}>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.surface} />
</label>
</fieldset>
<!-- Codeblock -->
<fieldset class="space-y-4">
<CodeBlock language="css" code={`${cssSnippet}`} class="max-h-[440px] overflow-y-auto"></CodeBlock>
</fieldset>
<!-- Surface -->
<label>
<span>Surface</span>
<select
name="surface"
id="surface"
bind:value={formValues.surface}
on:change={processSurface}
>
{#each colorLabels as l}<option value={l}>{l}</option>{/each}
</select>
<Swatches palette={rgbShades.surface} />
</label>
</fieldset>
<!-- Codeblock -->
<fieldset class="space-y-4">
<CodeBlock language="css" code={`${cssSnippet}`} class="max-h-[440px] overflow-y-auto" />
</fieldset>
</form>

View File

@@ -1,87 +1,110 @@
<script lang="ts">
import { onMount, afterUpdate } from "svelte";
import { fade } from 'svelte/transition';
import { onMount, afterUpdate } from 'svelte';
import { fade } from 'svelte/transition';
// Props
export let visible: boolean = false;
export let position: string = 'top';
export let background: string = 'bg-black dark:bg-white';
export let color: string = 'text-white dark:text-black';
export let width: string = 'w-auto';
export let whitespace: string = 'whitespace-nowrap';
export let rounded: string = 'rounded';
export let duration: number = 100; // ms
// Props
export let visible: boolean = false;
export let position: string = 'top';
export let background: string = 'bg-black dark:bg-white';
export let color: string = 'text-white dark:text-black';
export let width: string = 'w-auto';
export let whitespace: string = 'whitespace-nowrap';
export let rounded: string = 'rounded';
export let duration: number = 100; // ms
// Base Styles
const cBaseTooltip: string = 'relative inline-flex justify-center items-center';
const cBasePopup: string = 'flex absolute z-10 drop-shadow';
const cBaseMessage: string = 'text-xs px-3 py-2';
const cBaseArrow: string = 'h-2 w-2 -rotate-45';
// Base Styles
const cBaseTooltip: string = 'relative inline-flex justify-center items-center';
const cBasePopup: string = 'flex absolute z-10 drop-shadow';
const cBaseMessage: string = 'text-xs px-3 py-2';
const cBaseArrow: string = 'h-2 w-2 -rotate-45';
// Set Position
let cPosition: string;
function setPosition(): void {
switch (position) {
case ('left'): cPosition = 'left-0 -translate-x-full flex-row items-center'; break;
case ('right'): cPosition = 'right-0 translate-x-full flex-row-reverse items-center'; break;
case ('bottom'): cPosition = 'bottom-0 translate-y-full flex-col-reverse items-center'; break;
default: cPosition = 'top-0 -translate-y-full flex-col items-center'; // top
}
}
setPosition(); // init
// Set Position
let cPosition: string;
function setPosition(): void {
switch (position) {
case 'left':
cPosition = 'left-0 -translate-x-full flex-row items-center';
break;
case 'right':
cPosition = 'right-0 translate-x-full flex-row-reverse items-center';
break;
case 'bottom':
cPosition = 'bottom-0 translate-y-full flex-col-reverse items-center';
break;
default:
cPosition = 'top-0 -translate-y-full flex-col items-center'; // top
}
}
setPosition(); // init
// Set Arrow Position
let cArrowPosition: string;
function setArrowPosition(): void {
switch (position) {
case ('left'): cArrowPosition = 'translate-x-[-50%]'; break;
case ('right'): cArrowPosition = 'translate-x-[50%]'; break;
case ('bottom'): cArrowPosition = 'translate-y-[50%]'; break;
default: cArrowPosition = 'translate-y-[-50%]'; // top
}
}
setArrowPosition(); // init
// Set Arrow Position
let cArrowPosition: string;
function setArrowPosition(): void {
switch (position) {
case 'left':
cArrowPosition = 'translate-x-[-50%]';
break;
case 'right':
cArrowPosition = 'translate-x-[50%]';
break;
case 'bottom':
cArrowPosition = 'translate-y-[50%]';
break;
default:
cArrowPosition = 'translate-y-[-50%]'; // top
}
}
setArrowPosition(); // init
// A11y Input Handler
function onKeyDown(event: any): void {
if (open && event.code === 'Escape') { visible = false; }
}
// A11y Input Handler
function onKeyDown(event: any): void {
if (open && event.code === 'Escape') {
visible = false;
}
}
// Handle mouse events
function onMouseEnter(): void { visible = true; } // show
function onMouseLeave(): void { visible = false; } // hide
// Handle mouse events
function onMouseEnter(): void {
visible = true;
} // show
function onMouseLeave(): void {
visible = false;
} // hide
// Lifecycle
onMount(() => {
// Event: Window Keydown (ESC)
window.addEventListener('keydown', onKeyDown);
// Lifecycle
onMount(() => {
// Event: Window Keydown (ESC)
window.addEventListener('keydown', onKeyDown);
});
afterUpdate(() => {
setPosition();
setArrowPosition();
});
afterUpdate(() => {
setPosition();
setArrowPosition();
});
// Reactive Classes
$: classesPopup = `${cBasePopup} ${cPosition}`;
$: classesMessage = `${cBaseMessage} ${background} ${color} ${width} ${whitespace} ${rounded}`;
$: classesArrow = `${cBaseArrow} ${cArrowPosition} ${background}`;
// Reactive Classes
$: classesPopup = `${cBasePopup} ${cPosition}`;
$: classesMessage = `${cBaseMessage} ${background} ${color} ${width} ${whitespace} ${rounded}`;
$: classesArrow = `${cBaseArrow} ${cArrowPosition} ${background}`;
</script>
<div class="tooltip {cBaseTooltip} {$$props.class}" data-testid="tooltip" role="tooltip">
<!-- Popup -->
{#if $$slots.message && visible}
<div
class="popup {classesPopup}"
data-testid="popup"
in:fade={{ duration }}
out:fade={{ duration }}
>
<div class="message {classesMessage}" data-testid="message"><slot name="message" /></div>
<div class="arrow {classesArrow}" data-testid="arrow" />
</div>
{/if}
<!-- Popup -->
{#if $$slots.message && visible}
<div class="popup {classesPopup}" data-testid="popup" in:fade="{{duration}}" out:fade="{{duration}}">
<div class="message {classesMessage}" data-testid="message"><slot name="message" /></div>
<div class="arrow {classesArrow}" data-testid="arrow"></div>
</div>
{/if}
<!-- Slot: Content -->
{#if $$slots.content}
<div data-testid="content" on:mouseenter={onMouseEnter} on:mouseleave={onMouseLeave}>
<slot name="content" />
</div>
{/if}
<!-- Slot: Content -->
{#if $$slots.content}
<div data-testid="content" on:mouseenter={onMouseEnter} on:mouseleave={onMouseLeave}>
<slot name="content" />
</div>
{/if}
</div>

View File

@@ -6,29 +6,27 @@ import { cleanup, render } from '@testing-library/svelte';
import { afterEach, describe, expect, it } from 'vitest';
import Tooltip from '$lib/Tooltip/Tooltip.svelte';
describe('Tooltip.svelte', () => {
afterEach(() => cleanup())
it('Renders without props', async () => {
const { getByTestId } = render(Tooltip);
expect(getByTestId('tooltip')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Tooltip, {
props: {
position: 'bottom',
background: 'bg-green-500',
color: 'text-red-500',
width: 'w-[300px]',
whitespace: 'whitespace-normal',
rounded: 'rounded-xl',
duration: 500,
},
});
expect(getByTestId('tooltip')).toBeTruthy();
})
});
afterEach(() => cleanup());
it('Renders without props', async () => {
const { getByTestId } = render(Tooltip);
expect(getByTestId('tooltip')).toBeTruthy();
});
it('Renders with props', () => {
const { getByTestId } = render(Tooltip, {
props: {
position: 'bottom',
background: 'bg-green-500',
color: 'text-red-500',
width: 'w-[300px]',
whitespace: 'whitespace-normal',
rounded: 'rounded-xl',
duration: 500
}
});
expect(getByTestId('tooltip')).toBeTruthy();
});
});

View File

@@ -1,11 +1,11 @@
export const svg: any = {
// Light Switch Icons
load: `<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"></svg>`,
light: '<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M120.2 154.2c4.672 4.688 10.83 7.031 16.97 7.031S149.5 158.9 154.2 154.2c9.375-9.375 9.375-24.56 0-33.93L108.9 74.97c-9.344-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94L120.2 154.2zM256 112c13.25 0 24-10.75 24-24v-64C280 10.75 269.3 0 256 0S232 10.75 232 24v64C232 101.3 242.8 112 256 112zM112 256c0-13.25-10.75-24-24-24h-64C10.75 232 0 242.8 0 256s10.75 24 24 24h64C101.3 280 112 269.3 112 256zM374.8 161.2c6.141 0 12.3-2.344 16.97-7.031l45.25-45.28c9.375-9.375 9.375-24.56 0-33.94s-24.59-9.375-33.94 0l-45.25 45.28c-9.375 9.375-9.375 24.56 0 33.93C362.5 158.9 368.7 161.2 374.8 161.2zM256 400c-13.25 0-24 10.75-24 24v64C232 501.3 242.8 512 256 512s24-10.75 24-24v-64C280 410.8 269.3 400 256 400zM120.2 357.8l-45.25 45.28c-9.375 9.375-9.375 24.56 0 33.94c4.688 4.688 10.83 7.031 16.97 7.031s12.3-2.344 16.97-7.031l45.25-45.28c9.375-9.375 9.375-24.56 0-33.93S129.6 348.4 120.2 357.8zM488 232h-64c-13.25 0-24 10.75-24 24s10.75 24 24 24h64C501.3 280 512 269.3 512 256S501.3 232 488 232zM391.8 357.8c-9.344-9.375-24.56-9.372-33.94 .0031s-9.375 24.56 0 33.93l45.25 45.28c4.672 4.688 10.83 7.031 16.97 7.031s12.28-2.344 16.97-7.031c9.375-9.375 9.375-24.56 0-33.94L391.8 357.8zM256 144C194.1 144 144 194.1 144 256c0 61.86 50.14 112 112 112s112-50.14 112-112C368 194.1 317.9 144 256 144z"/></svg>',
// Dark Mode
dark: '<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M332.3 426.4c-93.13 17.75-178.5-53.63-178.5-147.6c0-54.25 29-104 76-130.9c7.375-4.125 5.45-15.12-2.8-16.62C108.7 109.4 0 200 0 320c0 106 85.76 192 191.8 192c59.25 0 113.2-26.79 148.9-71.04C346.1 434.5 340.3 424.8 332.3 426.4zM288 63.1l12.42 29.78c.6094 1.225 2.211 2.219 3.578 2.219s2.967-.9941 3.576-2.219l12.42-29.78l29.79-12.42C351 50.97 352 49.36 352 47.1c0-1.365-.9922-2.967-2.211-3.576l-29.79-12.42l-12.42-29.79c-.6094-1.227-2.209-2.217-3.576-2.217s-2.969 .9902-3.578 2.217l-12.42 29.79L258.2 44.42c-1.217 .6094-2.209 2.211-2.209 3.576c0 1.359 .9922 2.971 2.209 3.58L288 63.1zM507.6 216.9L448 192l-24.88-59.63C421.8 129.8 419 127.1 416 127.1s-5.75 1.75-7.125 4.375L384 192l-59.63 24.88C321.8 218.3 320 221 320 224s1.75 5.75 4.375 7.125L384 256l24.88 59.63C410.3 318.3 413 320 416 320s5.75-1.75 7.125-4.375L448 256l59.63-24.88C510.3 229.8 512 227 512 224S510.3 218.3 507.6 216.9z"/></svg>',
// System Mode
system: '<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M528 0h-480C21.5 0 0 21.5 0 48v320C0 394.5 21.5 416 48 416h192L224 464H152C138.8 464 128 474.8 128 488S138.8 512 152 512h272c13.25 0 24-10.75 24-24s-10.75-24-24-24H352L336 416h192c26.5 0 48-21.5 48-48v-320C576 21.5 554.5 0 528 0zM512 352H64V64h448V352z"/></svg>',
}
// Light Switch Icons
load: `<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"></svg>`,
light:
'<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M120.2 154.2c4.672 4.688 10.83 7.031 16.97 7.031S149.5 158.9 154.2 154.2c9.375-9.375 9.375-24.56 0-33.93L108.9 74.97c-9.344-9.375-24.56-9.375-33.94 0s-9.375 24.56 0 33.94L120.2 154.2zM256 112c13.25 0 24-10.75 24-24v-64C280 10.75 269.3 0 256 0S232 10.75 232 24v64C232 101.3 242.8 112 256 112zM112 256c0-13.25-10.75-24-24-24h-64C10.75 232 0 242.8 0 256s10.75 24 24 24h64C101.3 280 112 269.3 112 256zM374.8 161.2c6.141 0 12.3-2.344 16.97-7.031l45.25-45.28c9.375-9.375 9.375-24.56 0-33.94s-24.59-9.375-33.94 0l-45.25 45.28c-9.375 9.375-9.375 24.56 0 33.93C362.5 158.9 368.7 161.2 374.8 161.2zM256 400c-13.25 0-24 10.75-24 24v64C232 501.3 242.8 512 256 512s24-10.75 24-24v-64C280 410.8 269.3 400 256 400zM120.2 357.8l-45.25 45.28c-9.375 9.375-9.375 24.56 0 33.94c4.688 4.688 10.83 7.031 16.97 7.031s12.3-2.344 16.97-7.031l45.25-45.28c9.375-9.375 9.375-24.56 0-33.93S129.6 348.4 120.2 357.8zM488 232h-64c-13.25 0-24 10.75-24 24s10.75 24 24 24h64C501.3 280 512 269.3 512 256S501.3 232 488 232zM391.8 357.8c-9.344-9.375-24.56-9.372-33.94 .0031s-9.375 24.56 0 33.93l45.25 45.28c4.672 4.688 10.83 7.031 16.97 7.031s12.28-2.344 16.97-7.031c9.375-9.375 9.375-24.56 0-33.94L391.8 357.8zM256 144C194.1 144 144 194.1 144 256c0 61.86 50.14 112 112 112s112-50.14 112-112C368 194.1 317.9 144 256 144z"/></svg>',
// Dark Mode
dark: '<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M332.3 426.4c-93.13 17.75-178.5-53.63-178.5-147.6c0-54.25 29-104 76-130.9c7.375-4.125 5.45-15.12-2.8-16.62C108.7 109.4 0 200 0 320c0 106 85.76 192 191.8 192c59.25 0 113.2-26.79 148.9-71.04C346.1 434.5 340.3 424.8 332.3 426.4zM288 63.1l12.42 29.78c.6094 1.225 2.211 2.219 3.578 2.219s2.967-.9941 3.576-2.219l12.42-29.78l29.79-12.42C351 50.97 352 49.36 352 47.1c0-1.365-.9922-2.967-2.211-3.576l-29.79-12.42l-12.42-29.79c-.6094-1.227-2.209-2.217-3.576-2.217s-2.969 .9902-3.578 2.217l-12.42 29.79L258.2 44.42c-1.217 .6094-2.209 2.211-2.209 3.576c0 1.359 .9922 2.971 2.209 3.58L288 63.1zM507.6 216.9L448 192l-24.88-59.63C421.8 129.8 419 127.1 416 127.1s-5.75 1.75-7.125 4.375L384 192l-59.63 24.88C321.8 218.3 320 221 320 224s1.75 5.75 4.375 7.125L384 256l24.88 59.63C410.3 318.3 413 320 416 320s5.75-1.75 7.125-4.375L448 256l59.63-24.88C510.3 229.8 512 227 512 224S510.3 218.3 507.6 216.9z"/></svg>',
// System Mode
system:
'<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M528 0h-480C21.5 0 0 21.5 0 48v320C0 394.5 21.5 416 48 416h192L224 464H152C138.8 464 128 474.8 128 488S138.8 512 152 512h272c13.25 0 24-10.75 24-24s-10.75-24-24-24H352L336 416h192c26.5 0 48-21.5 48-48v-320C576 21.5 554.5 0 528 0zM512 352H64V64h448V352z"/></svg>'
};

View File

@@ -2,58 +2,58 @@
// Components ---
export { default as AccordionGroup } from "./Accordion/AccordionGroup.svelte";
export { default as AccordionItem } from "./Accordion/AccordionItem.svelte";
export { default as Alert } from "./Alert/Alert.svelte";
export { default as Avatar } from "./Avatar/Avatar.svelte";
export { default as Badge } from "./Badge/Badge.svelte";
export { default as Breadcrumb } from "./Breadcrumb/Breadcrumb.svelte";
export { default as Crumb } from "./Breadcrumb/Crumb.svelte";
export { default as Button } from "./Button/Button.svelte";
export { default as Card } from "./Card/Card.svelte";
export { default as AccordionGroup } from './Accordion/AccordionGroup.svelte';
export { default as AccordionItem } from './Accordion/AccordionItem.svelte';
export { default as Alert } from './Alert/Alert.svelte';
export { default as Avatar } from './Avatar/Avatar.svelte';
export { default as Badge } from './Badge/Badge.svelte';
export { default as Breadcrumb } from './Breadcrumb/Breadcrumb.svelte';
export { default as Crumb } from './Breadcrumb/Crumb.svelte';
export { default as Button } from './Button/Button.svelte';
export { default as Card } from './Card/Card.svelte';
// export { default as CodeBlock } from "./CodeBlock/CodeBlock.svelte"; // keep disabled until further notice
export { default as Divider } from "./Divider/Divider.svelte";
export { default as Drawer } from "./Drawer/Drawer.svelte";
export { default as GradientHeading } from "./GradientHeading/GradientHeading.svelte";
export { default as LightSwitch } from "./LightSwitch/LightSwitch.svelte";
export { default as List } from "./List/List.svelte";
export { default as ListItem } from "./List/ListItem.svelte";
export { default as LogoCloud } from "./LogoCloud/LogoCloud.svelte";
export { default as Logo } from "./LogoCloud/Logo.svelte";
export { default as Menu } from "./Menu/Menu.svelte";
export { default as Paginator } from "./Paginator/Paginator.svelte";
export { default as ProgressBar } from "./Progress/ProgressBar.svelte";
export { default as ProgressRadial } from "./Progress/ProgressRadial.svelte";
export { default as RadioGroup } from "./Radio/RadioGroup.svelte";
export { default as RadioItem } from "./Radio/RadioItem.svelte";
export { default as RangeSlider } from "./RangeSlider/RangeSlider.svelte";
export { default as SlideToggle } from "./SlideToggle/SlideToggle.svelte";
export { default as Stepper } from "./Stepper/Stepper.svelte";
export { default as TabGroup } from "./Tab/TabGroup.svelte";
export { default as Tab } from "./Tab/Tab.svelte";
export { default as DataTable } from "./Table/DataTable.svelte";
export { default as Tooltip } from "./Tooltip/Tooltip.svelte";
export { default as Divider } from './Divider/Divider.svelte';
export { default as Drawer } from './Drawer/Drawer.svelte';
export { default as GradientHeading } from './GradientHeading/GradientHeading.svelte';
export { default as LightSwitch } from './LightSwitch/LightSwitch.svelte';
export { default as List } from './List/List.svelte';
export { default as ListItem } from './List/ListItem.svelte';
export { default as LogoCloud } from './LogoCloud/LogoCloud.svelte';
export { default as Logo } from './LogoCloud/Logo.svelte';
export { default as Menu } from './Menu/Menu.svelte';
export { default as Paginator } from './Paginator/Paginator.svelte';
export { default as ProgressBar } from './Progress/ProgressBar.svelte';
export { default as ProgressRadial } from './Progress/ProgressRadial.svelte';
export { default as RadioGroup } from './Radio/RadioGroup.svelte';
export { default as RadioItem } from './Radio/RadioItem.svelte';
export { default as RangeSlider } from './RangeSlider/RangeSlider.svelte';
export { default as SlideToggle } from './SlideToggle/SlideToggle.svelte';
export { default as Stepper } from './Stepper/Stepper.svelte';
export { default as TabGroup } from './Tab/TabGroup.svelte';
export { default as Tab } from './Tab/Tab.svelte';
export { default as DataTable } from './Table/DataTable.svelte';
export { default as Tooltip } from './Tooltip/Tooltip.svelte';
// Utilities ---
// Filters
export { filter } from "./Filters/filter";
export { default as Apollo } from "./Filters/svg/Apollo.svelte";
export { default as BlueNight } from "./Filters/svg/BlueNight.svelte";
export { default as Emerald } from "./Filters/svg/Emerald.svelte";
export { default as GreenFall } from "./Filters/svg/GreenFall.svelte";
export { default as Noir } from "./Filters/svg/Noir.svelte";
export { default as NoirLight } from "./Filters/svg/NoirLight.svelte";
export { default as Rustic } from "./Filters/svg/Rustic.svelte";
export { default as Summer84 } from "./Filters/svg/Summer84.svelte";
export { default as XPro } from "./Filters/svg/XPro.svelte";
export { filter } from './Filters/filter';
export { default as Apollo } from './Filters/svg/Apollo.svelte';
export { default as BlueNight } from './Filters/svg/BlueNight.svelte';
export { default as Emerald } from './Filters/svg/Emerald.svelte';
export { default as GreenFall } from './Filters/svg/GreenFall.svelte';
export { default as Noir } from './Filters/svg/Noir.svelte';
export { default as NoirLight } from './Filters/svg/NoirLight.svelte';
export { default as Rustic } from './Filters/svg/Rustic.svelte';
export { default as Summer84 } from './Filters/svg/Summer84.svelte';
export { default as XPro } from './Filters/svg/XPro.svelte';
// Notifications
export { dialogStore } from "./Notifications/Stores";
export { default as Dialog } from "./Notifications/Dialog.svelte";
export { toastStore } from "./Notifications/Stores";
export { default as Toast } from "./Notifications/Toast.svelte";
export { dialogStore } from './Notifications/Stores';
export { default as Dialog } from './Notifications/Dialog.svelte';
export { toastStore } from './Notifications/Stores';
export { default as Toast } from './Notifications/Toast.svelte';
// Typescript Interfaces (read: custom types)
export { type DialogAlert, type DialogConfirm, type DialogPrompt } from "./Notifications/Stores";
export { type ToastMessage } from "./Notifications/Stores";
export { type DialogAlert, type DialogConfirm, type DialogPrompt } from './Notifications/Stores';
export { type ToastMessage } from './Notifications/Stores';

View File

@@ -7,10 +7,10 @@ const plugin = require('tailwindcss/plugin');
function rgbAppendOpacity(variable) {
return ({ opacityValue }) => {
if (opacityValue === undefined) {
return `rgb(var(${variable}))`
return `rgb(var(${variable}))`;
}
return `rgb(var(${variable}) / ${opacityValue})`
}
return `rgb(var(${variable}) / ${opacityValue})`;
};
}
function createColorSet(colorName) {
@@ -29,16 +29,16 @@ function createColorSet(colorName) {
}
module.exports = plugin(() => {}, {
theme: {
theme: {
extend: {
// Extend the colors with the CSS variable values
// NOTE: Must be RGB to allow for TW opacity value
colors: {
'primary': createColorSet('primary'),
'accent': createColorSet('accent'),
'warning': createColorSet('warning'),
'surface': createColorSet('surface'),
},
primary: createColorSet('primary'),
accent: createColorSet('accent'),
warning: createColorSet('warning'),
surface: createColorSet('surface')
}
}
},
})
}
});

View File

@@ -12,8 +12,18 @@
import Badge from '$lib/Badge/Badge.svelte';
import Dialog from '$lib/Notifications/Dialog.svelte';
import Toast from '$lib/Notifications/Toast.svelte';
import { Apollo, BlueNight, Emerald, GreenFall, Noir, NoirLight, Rustic, Summer84, XPro } from '$lib/Filters/filter';
import {
Apollo,
BlueNight,
Emerald,
GreenFall,
Noir,
NoirLight,
Rustic,
Summer84,
XPro
} from '$lib/Filters/filter';
// Import CSS
import 'highlight.js/styles/github-dark.css'; // Highlight.js
import '../themes/theme-skeleton.css'; // skeleton|rocket|modern|seafoam|vintage|sahara|test
@@ -22,68 +32,73 @@
const currentPageStore: Writable<string> = writable($page.url.pathname);
const drawer: Writable<boolean> = writable(false);
const navigation: any = [
{
title: 'Guides',
list: [
{href: '/', label: 'Get Started'},
{href: '/guides/tailwind', label: 'Tailwind Settings'},
{href: '/guides/themes', label: 'Themes'},
{href: '/guides/styling', label: 'Styling'},
{href: '/guides/forms', label: 'Forms'},
{href: '/guides/astro', label: 'Astro'},
],
},
{
title: 'Docs',
list: [
{href: '/docs/why', label: 'Why Skeleton'},
{href: '/docs/contributions', label: 'Contributions'},
],
},
{
title: 'Components',
list: [
{href: '/components/accordions', label: 'Accordions'},
{href: '/components/alerts', label: 'Alerts'},
{href: '/components/avatars', label: 'Avatars'},
{href: '/components/badges', label: 'Badges'},
{href: '/components/breadcrumbs', label: 'Breadcrumbs'},
{href: '/components/buttons', label: 'Buttons'},
{href: '/components/cards', label: 'Cards'},
{
title: 'Guides',
list: [
{ href: '/', label: 'Get Started' },
{ href: '/guides/tailwind', label: 'Tailwind Settings' },
{ href: '/guides/themes', label: 'Themes' },
{ href: '/guides/styling', label: 'Styling' },
{ href: '/guides/forms', label: 'Forms' },
{ href: '/guides/astro', label: 'Astro' }
]
},
{
title: 'Docs',
list: [
{ href: '/docs/why', label: 'Why Skeleton' },
{ href: '/docs/contributions', label: 'Contributions' }
]
},
{
title: 'Components',
list: [
{ href: '/components/accordions', label: 'Accordions' },
{ href: '/components/alerts', label: 'Alerts' },
{ href: '/components/avatars', label: 'Avatars' },
{ href: '/components/badges', label: 'Badges' },
{ href: '/components/breadcrumbs', label: 'Breadcrumbs' },
{ href: '/components/buttons', label: 'Buttons' },
{ href: '/components/cards', label: 'Cards' },
// {href: '/components/conic-gradients', label: 'Conic Gradients'}, // keep disabled until further notice
{href: '/components/data-tables', label: 'Data Tables'},
{href: '/components/dividers', label: 'Dividers'},
{href: '/components/drawers', label: 'Drawers'},
{href: '/components/gradient-headings', label: 'Gradient Headings'},
{href: '/components/lists', label: 'Lists'},
{href: '/components/logo-clouds', label: 'Logo Clouds'},
{href: '/components/menus', label: 'Menus'},
{href: '/components/paginators', label: 'Paginators'},
{href: '/components/progress-bars', label: 'Progress Bars'},
{href: '/components/progress-radials', label: 'Progress Radials'},
{href: '/components/radio-groups', label: 'Radio Groups'},
{href: '/components/range-sliders', label: 'Range Sliders'},
{href: '/components/slide-toggles', label: 'Slide Toggles'},
{href: '/components/steppers', label: 'Steppers'},
{href: '/components/tabs', label: 'Tabs'},
{href: '/components/tooltips', label: 'Tooltips'},
],
},
{
{ href: '/components/data-tables', label: 'Data Tables' },
{ href: '/components/dividers', label: 'Dividers' },
{ href: '/components/drawers', label: 'Drawers' },
{ href: '/components/gradient-headings', label: 'Gradient Headings' },
{ href: '/components/lists', label: 'Lists' },
{ href: '/components/logo-clouds', label: 'Logo Clouds' },
{ href: '/components/menus', label: 'Menus' },
{ href: '/components/paginators', label: 'Paginators' },
{ href: '/components/progress-bars', label: 'Progress Bars' },
{ href: '/components/progress-radials', label: 'Progress Radials' },
{ href: '/components/radio-groups', label: 'Radio Groups' },
{ href: '/components/range-sliders', label: 'Range Sliders' },
{ href: '/components/slide-toggles', label: 'Slide Toggles' },
{ href: '/components/steppers', label: 'Steppers' },
{ href: '/components/tabs', label: 'Tabs' },
{ href: '/components/tooltips', label: 'Tooltips' }
]
},
{
title: 'Utilities',
list: [
list: [
// {href: '/utilities/codeblocks', label: 'Code Blocks'}, // keep disabled until further notice
{href: '/utilities/dialogs', label: 'Dialogs'},
{href: '/utilities/toasts', label: 'Toasts'},
{href: '/utilities/lightswitches', label: 'Lightswitch'},
{href: '/utilities/filters', label: 'Filters', badge: 'Experimental'},
],
}
];
{ href: '/utilities/dialogs', label: 'Dialogs' },
{ href: '/utilities/toasts', label: 'Toasts' },
{ href: '/utilities/lightswitches', label: 'Lightswitch' },
{ href: '/utilities/filters', label: 'Filters', badge: 'Experimental' }
]
}
];
// Drawer
const drawerOpen = () => { console.log('open triggered'); drawer.set(true); }
const drawerClose = () => { drawer.set(false); }
const drawerOpen = () => {
console.log('open triggered');
drawer.set(true);
};
const drawerClose = () => {
drawer.set(false);
};
afterNavigate(() => {
// Scroll to top
@@ -110,18 +125,32 @@
<!-- Page Layout -->
<main class="flex flex-row">
<!-- Drawer -->
<Drawer visible={drawer} fixed="left">
<!-- Header -->
<svelte:fragment slot="header">
<div class="flex justify-between items-center p-4">
<a href="/" class="text-[26px] font-bold uppercase">Skeleton</a>
<div class="flex justify-between items-center space-x-4">
<!-- Github -->
<a href="https://github.com/Brain-Bones/skeleton" target="_blank" class="fill-black dark:fill-white" aria-label="GitHub">
<svg class="w-[20px] h-[20px]" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="20" height="20" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
<a
href="https://github.com/Brain-Bones/skeleton"
target="_blank"
class="fill-black dark:fill-white"
aria-label="GitHub"
>
<svg
class="w-[20px] h-[20px]"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
width="20"
height="20"
viewBox="0 0 496 512"
><path
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
/></svg
>
</a>
<!-- Light Switch -->
<LightSwitch origin="tr" />
@@ -133,20 +162,21 @@
<!-- Main -->
<svelte:fragment slot="main">
<div class="space-y-6 mb-10">
{#each navigation as {title,list}, i }
<div class="text-sm text-primary-500 m-4">{title}</div>
<List tag="nav" selected={currentPageStore} title={title} label={title}>
{#each list as {href,label, badge} }
<ListItem {href} value={href} on:click={drawerClose}>
<div class="flex justify-between">
<span>{label}</span>
{#if badge}<Badge background="bg-accent-500 dark/bg-accent-500/30">{badge}</Badge>{/if}
</div>
</ListItem>
{/each}
</List>
{#if i+1 < navigation.length}<Divider />{/if}
{/each}
{#each navigation as { title, list }, i}
<div class="text-sm text-primary-500 m-4">{title}</div>
<List tag="nav" selected={currentPageStore} {title} label={title}>
{#each list as { href, label, badge }}
<ListItem {href} value={href} on:click={drawerClose}>
<div class="flex justify-between">
<span>{label}</span>
{#if badge}<Badge background="bg-accent-500 dark/bg-accent-500/30">{badge}</Badge
>{/if}
</div>
</ListItem>
{/each}
</List>
{#if i + 1 < navigation.length}<Divider />{/if}
{/each}
</div>
</svelte:fragment>
@@ -155,36 +185,37 @@
<Divider class="opacity-60" />
<div class="text-xs opacity-50 p-4 flex justify-between">
<a href="https://www.brainandbonesllc.com/" target="_blank">Brain & Bones</a>
<a href="https://github.com/Brain-Bones/skeleton/blob/master/LICENSE" target="_blank">MIT License</a>
<a href="https://github.com/Brain-Bones/skeleton/blob/master/LICENSE" target="_blank"
>MIT License</a
>
</div>
</svelte:fragment>
</Drawer>
<!-- Page Content -->
<div id="main" class="w-screen h-screen overflow-y-auto">
<!-- Page Content -->
<div id="main" class="w-screen h-screen overflow-y-auto">
<header class="lg:hidden flex p-8 space-x-4">
<!-- Hamburger Menu -->
<Button variant="minimal" on:click={drawerOpen}>
<svelte:fragment slot="lead">
<svg class="fill-surface-500 -translate-y-[2px]" viewBox="0 0 100 60" width="24" height="24">
<rect width="100" height="10"></rect>
<rect y="30" width="100" height="10"></rect>
<rect y="60" width="100" height="10"></rect>
<svg
class="fill-surface-500 -translate-y-[2px]"
viewBox="0 0 100 60"
width="24"
height="24"
>
<rect width="100" height="10" />
<rect y="30" width="100" height="10" />
<rect y="60" width="100" height="10" />
</svg>
</svelte:fragment>
<span class="text-surface-500 font-bold">Menu</span>
</Button>
</header>
<!-- Page Slot -->
<div class="container mx-auto p-8">
<slot />
</div>
</div>
</div>
</main>

View File

@@ -1,143 +1,242 @@
<script lang="ts">
import { storeFramework } from "./stores";
import { storeFramework } from './stores';
import Card from "$lib/Card/Card.svelte";
import Button from "$lib/Button/Button.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import Divider from "$lib/Divider/Divider.svelte";
import LogoCloud from "$lib/LogoCloud/LogoCloud.svelte";
import Logo from "$lib/LogoCloud/Logo.svelte";
import TabGroup from "$lib/Tab/TabGroup.svelte";
import Tab from "$lib/Tab/Tab.svelte";
import Card from '$lib/Card/Card.svelte';
import Button from '$lib/Button/Button.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Divider from '$lib/Divider/Divider.svelte';
import LogoCloud from '$lib/LogoCloud/LogoCloud.svelte';
import Logo from '$lib/LogoCloud/Logo.svelte';
import TabGroup from '$lib/Tab/TabGroup.svelte';
import Tab from '$lib/Tab/Tab.svelte';
</script>
<div class="space-y-8 lg:text-left">
<!-- Early Access Message -->
<section
class="bg-accent-500/10 border-l-2 border-l-green-500 p-4 flex justify-between items-center space-x-4"
>
<p class="!text-white">
<span class="mr-2">🚧</span>
Skeleton is available as a public beta. Expect breaking changes prior to v1.0. If you encounter
issues please report them on GitHub.
</p>
<Button variant="ghost" href="https://github.com/Brain-Bones/skeleton/issues"
>Report Issue</Button
>
</section>
<!-- Early Access Message -->
<section class="bg-accent-500/10 border-l-2 border-l-green-500 p-4 flex justify-between items-center space-x-4">
<p class="!text-white">
<span class="mr-2">🚧</span>
Skeleton is available as a public beta. Expect breaking changes prior to v1.0. If you encounter issues please report them on GitHub.
</p>
<Button variant="ghost" href="https://github.com/Brain-Bones/skeleton/issues">Report Issue</Button>
</section>
<!-- Hero -->
<header>
<div class="max-w-[90%] lg:max-w-[80%] mx-auto space-y-6 py-10 md:py-20">
<h1 class="text-3xl md:text-5xl lg:text-6xl">
<span
class="font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-primary-500 to-accent-500"
>Skeleton</span
>
</h1>
<h2 class="text-4xl md:text-6xl lg:text-7xl">A fully featured Svelte component library.</h2>
<p class="text-xl">
Skeleton allows you to build fast and reactive web UI using the power of <a
href="https://svelte.dev/"
target="_blank">Svelte</a
>
+ <a href="https://tailwindcss.com/" target="_blank">Tailwind</a>.
</p>
<nav class="flex space-x-4">
<Button variant="filled-primary" href="#getStarted">Get Started</Button>
<Button variant="ghost" href="/docs/why">Why Skeleton</Button>
</nav>
</div>
</header>
<!-- Hero -->
<header>
<div class="max-w-[90%] lg:max-w-[80%] mx-auto space-y-6 py-10 md:py-20">
<h1 class="text-3xl md:text-5xl lg:text-6xl">
<span class="font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-primary-500 to-accent-500">Skeleton</span>
</h1>
<h2 class="text-4xl md:text-6xl lg:text-7xl">A fully featured Svelte component library.</h2>
<p class="text-xl">Skeleton allows you to build fast and reactive web UI using the power of <a href="https://svelte.dev/" target="_blank">Svelte</a> + <a href="https://tailwindcss.com/" target="_blank">Tailwind</a>.</p>
<nav class="flex space-x-4">
<Button variant="filled-primary" href="#getStarted">Get Started</Button>
<Button variant="ghost" href="/docs/why">Why Skeleton</Button>
</nav>
</div>
</header>
<LogoCloud>
<Logo href="https://github.com/Brain-Bones/skeleton" target="_blank">
<svelte:fragment slot="lead">
<svg class="inline fill-surface-500" x="0px" y="0px" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
</svelte:fragment>
<svelte:fragment slot="label">Github</svelte:fragment>
</Logo>
<Logo href="https://discord.gg/EXqV7W8MtY" target="_blank">
<svelte:fragment slot="lead">
<svg class="inline fill-surface-500" x="0px" y="0px" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/></svg>
</svelte:fragment>
<svelte:fragment slot="label">Discord</svelte:fragment>
</Logo>
<Logo href="https://twitter.com/SkeletonUI" target="_blank">
<svelte:fragment slot="lead">
<svg class="inline fill-surface-500" x="0px" y="0px" width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg>
</svelte:fragment>
<svelte:fragment slot="label">Twitter</svelte:fragment>
</Logo>
</LogoCloud>
<LogoCloud>
<Logo href="https://github.com/Brain-Bones/skeleton" target="_blank">
<svelte:fragment slot="lead">
<svg
class="inline fill-surface-500"
x="0px"
y="0px"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
><path
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
/></svg
>
</svelte:fragment>
<svelte:fragment slot="label">Github</svelte:fragment>
</Logo>
<Logo href="https://discord.gg/EXqV7W8MtY" target="_blank">
<svelte:fragment slot="lead">
<svg
class="inline fill-surface-500"
x="0px"
y="0px"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 512"
><path
d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"
/></svg
>
</svelte:fragment>
<svelte:fragment slot="label">Discord</svelte:fragment>
</Logo>
<Logo href="https://twitter.com/SkeletonUI" target="_blank">
<svelte:fragment slot="lead">
<svg
class="inline fill-surface-500"
x="0px"
y="0px"
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
><path
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
/></svg
>
</svelte:fragment>
<svelte:fragment slot="label">Twitter</svelte:fragment>
</Logo>
</LogoCloud>
<Divider />
<Divider />
<!-- Install -->
<section id="getStarted" class="space-y-4">
<h2>Get Started</h2>
<p>Select your app framework of choice, then follow steps below. During this process you'll scaffold a project, install the Skeleton package, install and configure Tailwind, as well as implement a custom theme. Make sure to follow each guide carefully.</p>
<!-- Install -->
<section id="getStarted" class="space-y-4">
<h2>Get Started</h2>
<p>
Select your app framework of choice, then follow steps below. During this process you'll
scaffold a project, install the Skeleton package, install and configure Tailwind, as well as
implement a custom theme. Make sure to follow each guide carefully.
</p>
<TabGroup selected={storeFramework} class="pt-4">
<Tab value="sveltekit">SvelteKit</Tab>
<Tab value="vite">Vite (Svelte)</Tab>
<Tab value="astro">Astro</Tab>
</TabGroup>
<TabGroup selected={storeFramework} class="pt-4">
<Tab value="sveltekit">SvelteKit</Tab>
<Tab value="vite">Vite (Svelte)</Tab>
<Tab value="astro">Astro</Tab>
</TabGroup>
<!-- Framework: SvelteKit -->
{#if $storeFramework === 'sveltekit'}
<p>View the <a href="https://kit.svelte.dev/docs/introduction#getting-started" target="_blank">official documentation</a></p>
<CodeBlock language="console" code={`
<!-- Framework: SvelteKit -->
{#if $storeFramework === 'sveltekit'}
<p>
View the <a href="https://kit.svelte.dev/docs/introduction#getting-started" target="_blank"
>official documentation</a
>
</p>
<CodeBlock
language="console"
code={`
npm create svelte@latest sveltekit-skeleton-app
- Create a barebones project
- Enable Typescript
cd sveltekit-skeleton-app
npm install
npm run dev
`.trim()}></CodeBlock>
<!-- Framework: Vite (Svelte) -->
{:else if $storeFramework === 'vite'}
<p>View the <a href="https://vitejs.dev/guide/#scaffolding-your-first-vite-project" target="_blank">official documentation</a>.</p>
<CodeBlock language="console" code={`
`.trim()}
/>
<!-- Framework: Vite (Svelte) -->
{:else if $storeFramework === 'vite'}
<p>
View the <a
href="https://vitejs.dev/guide/#scaffolding-your-first-vite-project"
target="_blank">official documentation</a
>.
</p>
<CodeBlock
language="console"
code={`
# npm 6.x
npm create vite@latest vite-skeleton-app --template svelte-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest vite-skeleton-app -- --template svelte-ts
`.trim()}></CodeBlock>
<CodeBlock language="console" code={`cd vite-skeleton-app\nnpm install\nnpm run dev`}></CodeBlock>
<!-- Framework: Astro -->
{:else if $storeFramework === 'astro'}
<p>View the <a href="https://docs.astro.build/en/install/auto/" target="_blank">official documentation</a>.</p>
<CodeBlock language="console" code={`
`.trim()}
/>
<CodeBlock language="console" code={`cd vite-skeleton-app\nnpm install\nnpm run dev`} />
<!-- Framework: Astro -->
{:else if $storeFramework === 'astro'}
<p>
View the <a href="https://docs.astro.build/en/install/auto/" target="_blank"
>official documentation</a
>.
</p>
<CodeBlock
language="console"
code={`
npm create astro@latest astro-skeleton-app
- Create an empty project
- Install all depedencies
- Use 'Typescript: Strict'
cd astro-skeleton-app
npm run dev
`.trim()}></CodeBlock>
<!-- Integrations -->
<h3>Integrations</h3>
<p>Svelte via <a href="https://docs.astro.build/en/guides/integrations-guide/svelte/" target="_blank">@astrojs/svelte</a></p>
<CodeBlock language="console" code={`npx astro add svelte`}></CodeBlock>
<p>Tailwind via <a href="https://docs.astro.build/en/guides/integrations-guide/tailwind/" target="_blank">@astrojs/tailwind</a></p>
<CodeBlock language="console" code={`npx astro add tailwind`}></CodeBlock>
<!-- More -->
<h3>Astro Guide</h3>
<p>Once setup of Skeleton is complete, we recommend trying our the dedicated <a href="/guides/astro">Astro guide</a>. This guide provides a walkthrough for scaffolding a simple Astro application, as well as explaining how to take advantage of Skeleton components within Astro's <a href="https://docs.astro.build/en/concepts/islands/" target="_blank">islands architecture</a>.</p>
{/if}
</section>
`.trim()}
/>
<!-- Integrations -->
<h3>Integrations</h3>
<p>
Svelte via <a
href="https://docs.astro.build/en/guides/integrations-guide/svelte/"
target="_blank">@astrojs/svelte</a
>
</p>
<CodeBlock language="console" code={`npx astro add svelte`} />
<p>
Tailwind via <a
href="https://docs.astro.build/en/guides/integrations-guide/tailwind/"
target="_blank">@astrojs/tailwind</a
>
</p>
<CodeBlock language="console" code={`npx astro add tailwind`} />
<!-- More -->
<h3>Astro Guide</h3>
<p>
Once setup of Skeleton is complete, we recommend trying our the dedicated <a
href="/guides/astro">Astro guide</a
>. This guide provides a walkthrough for scaffolding a simple Astro application, as well as
explaining how to take advantage of Skeleton components within Astro's
<a href="https://docs.astro.build/en/concepts/islands/" target="_blank"
>islands architecture</a
>.
</p>
{/if}
</section>
<!-- Install Tailwind -->
{#if ['sveltekit', 'vite'].includes($storeFramework)}
<section class="space-y-4">
<h3>Install Tailwind</h3>
<p><a href="https://github.com/svelte-add/tailwindcss" target="_blank">Svelte-Add</a> makes installation a trivia process.</p>
<CodeBlock language="console" code={`npx svelte-add@latest tailwindcss\nnpm install`}></CodeBlock>
</section>
{/if}
<!-- Install Tailwind -->
{#if ['sveltekit', 'vite'].includes($storeFramework)}
<section class="space-y-4">
<h3>Install Tailwind</h3>
<p>
<a href="https://github.com/svelte-add/tailwindcss" target="_blank">Svelte-Add</a> makes installation
a trivia process.
</p>
<CodeBlock language="console" code={`npx svelte-add@latest tailwindcss\nnpm install`} />
</section>
{/if}
<!-- Install Skeleton -->
<section class="space-y-4">
<h3>Install Skeleton</h3>
<p>Install the core Skeleton package from <a href="https://www.npmjs.com/package/@brainandbones/skeleton" target="_blank">NPM</a>.</p>
<CodeBlock language="console" code={`npm i @brainandbones/skeleton --save-dev`}></CodeBlock>
</section>
<!-- Install Skeleton -->
<section class="space-y-4">
<h3>Install Skeleton</h3>
<p>
Install the core Skeleton package from <a
href="https://www.npmjs.com/package/@brainandbones/skeleton"
target="_blank">NPM</a
>.
</p>
<CodeBlock language="console" code={`npm i @brainandbones/skeleton --save-dev`} />
</section>
<Divider />
<!-- Next Steps -->
<Card class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0 md:space-x-4">
<p>Next, let's configure Tailwind to work with Skeleton.</p>
<Button variant="filled-accent" href="/guides/tailwind">Configure Tailwind</Button>
</Card>
<Divider />
<!-- Next Steps -->
<Card
class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0 md:space-x-4"
>
<p>Next, let's configure Tailwind to work with Skeleton.</p>
<Button variant="filled-accent" href="/guides/tailwind">Configure Tailwind</Button>
</Card>
</div>

View File

@@ -1,52 +1,49 @@
<script lang="ts">
import Card from "$lib/Card/Card.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from "$lib/Table/DataTable.svelte";
import Card from '$lib/Card/Card.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Required', 'Description'],
source: [
['name', '-', '-', '-', '-', '...']
],
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['name', '...']
],
};
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Required', 'Description'],
source: [['name', '-', '-', '-', '-', '...']]
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [['name', '...']]
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Template</h1>
<p>Describe the component here.</p>
<CodeBlock
language="javascript"
code={`import { Component } from '@brainandbones/skeleton';`}
/>
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Template</h1>
<p>Describe the component here.</p>
<CodeBlock language="javascript" code={`import { Component } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Examples -->
<Card class="space-y-4">
<p>(ExamplesHere)</p>
</Card>
<!-- Examples -->
<Card class="space-y-4">
<p>(ExamplesHere)</p>
</Card>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<div>UsageExample</div>`.trim()} />
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<div>UsageExample</div>`.trim()}></CodeBlock>
</section>
<!-- Properties -->
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
</div>

View File

@@ -1,124 +1,162 @@
<script lang="ts">
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from "$lib/Table/DataTable.svelte";
import Card from "$lib/Card/Card.svelte";
import Avatar from '$lib/Avatar/Avatar.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import Card from '$lib/Card/Card.svelte';
import Avatar from '$lib/Avatar/Avatar.svelte';
import AccordionGroup from '$lib/Accordion/AccordionGroup.svelte';
import AccordionItem from '$lib/Accordion/AccordionItem.svelte';
import AccordionGroup from '$lib/Accordion/AccordionGroup.svelte';
import AccordionItem from '$lib/Accordion/AccordionItem.svelte';
const tablePropsGroup: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['collapse', 'boolean', 'true', '-', 'When TRUE, only one item will show at a time.'],
['spacing', 'class', 'spacing-y-2', '-', 'Provide a class to set spacing between item rows.'],
],
};
const tablePropsItem: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['open', 'boolean', 'false', '-', `Use this to define which item is open on page load.`],
['hover', 'string', 'hover:bg-primary-500/10', '-', 'Provide a class to set the hover background color.'],
['spacing', 'string', 'space-y-0', '-', 'Provide a class to set spacing between title and description elements.'],
],
};
const tableSlots: any = {
headings: ['Name', 'Required', 'Description'],
source: [
['lead', '-', 'Allows for an optional leading element, such as an icon.'],
['summary', '&check;', 'Provide the summary details of each item.'],
['content', '&check;', 'Provide the content details of each item.'],
],
};
const tableA11yItem: any = {
headings: ['Prop', 'Required', 'Description'],
source: [
['summaryId', '-', `Provide a semantic ID for for the items summary element.`],
['contentId', '-', `Provide a semantic ID for for the items content element.`],
],
};
const tablePropsGroup: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['collapse', 'boolean', 'true', '-', 'When TRUE, only one item will show at a time.'],
['spacing', 'class', 'spacing-y-2', '-', 'Provide a class to set spacing between item rows.']
]
};
const tablePropsItem: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['open', 'boolean', 'false', '-', `Use this to define which item is open on page load.`],
[
'hover',
'string',
'hover:bg-primary-500/10',
'-',
'Provide a class to set the hover background color.'
],
[
'spacing',
'string',
'space-y-0',
'-',
'Provide a class to set spacing between title and description elements.'
]
]
};
const tableSlots: any = {
headings: ['Name', 'Required', 'Description'],
source: [
['lead', '-', 'Allows for an optional leading element, such as an icon.'],
['summary', '&check;', 'Provide the summary details of each item.'],
['content', '&check;', 'Provide the content details of each item.']
]
};
const tableA11yItem: any = {
headings: ['Prop', 'Required', 'Description'],
source: [
['summaryId', '-', `Provide a semantic ID for for the items summary element.`],
['contentId', '-', `Provide a semantic ID for for the items content element.`]
]
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Accordions</h1>
<p>Divide content into collapsible sections.</p>
<CodeBlock
language="javascript"
code={`import { AccordionGroup, AccordionItem } from '@brainandbones/skeleton';`}
/>
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Accordions</h1>
<p>Divide content into collapsible sections.</p>
<CodeBlock language="javascript" code={`import { AccordionGroup, AccordionItem } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Examples -->
<section class="space-y-4">
<Card class="space-y-4">
<AccordionGroup>
<AccordionItem open>
<svelte:fragment slot="summary"
>Does Skeleton come with an Accordion component?</svelte:fragment
>
<svelte:fragment slot="content">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam
deleniti rem incidunt.
</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary"
>What else do I need to know to build awesome web apps?</svelte:fragment
>
<svelte:fragment slot="content">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam
deleniti rem incidunt.
</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">What is the weather like today?</svelte:fragment>
<svelte:fragment slot="content">
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam
deleniti rem incidunt.
</p>
</svelte:fragment>
</AccordionItem>
</AccordionGroup>
</Card>
<!-- Customized -->
<h3>Customized</h3>
<section class="border border-surface-500/10 p-2 rounded-xl space-y-4">
<AccordionGroup>
<AccordionItem spacing="space-y-4" open>
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"
><path
class="fill-primary-500"
d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"
/></svg
>
</svelte:fragment>
<svelte:fragment slot="summary">
<h3>Icon and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the first element.</p></Card>
</svelte:fragment>
</AccordionItem>
<AccordionItem spacing="space-y-4">
<svelte:fragment slot="lead"
><Avatar size="sm" src="https://i.pravatar.cc/" /></svelte:fragment
>
<svelte:fragment slot="summary">
<h3>Avatar and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the second element.</p></Card>
</svelte:fragment>
</AccordionItem>
<AccordionItem spacing="space-y-4">
<svelte:fragment slot="lead">
<Avatar initials="1" size="sm" background="bg-accent-500" />
</svelte:fragment>
<svelte:fragment slot="summary">
<h3>Numeral and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the third element.</p></Card>
</svelte:fragment>
</AccordionItem>
</AccordionGroup>
</section>
</section>
<!-- Examples -->
<section class="space-y-4">
<Card class="space-y-4">
<AccordionGroup>
<AccordionItem open>
<svelte:fragment slot="summary">Does Skeleton come with an Accordion component?</svelte:fragment>
<svelte:fragment slot="content">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam deleniti rem incidunt.</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem>
<svelte:fragment slot="summary">What else do I need to know to build awesome web apps?</svelte:fragment>
<svelte:fragment slot="content">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam deleniti rem incidunt.</p>
</svelte:fragment>
</AccordionItem>
<AccordionItem >
<svelte:fragment slot="summary">What is the weather like today?</svelte:fragment>
<svelte:fragment slot="content">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Perspiciatis eaque nam deleniti rem incidunt.</p>
</svelte:fragment>
</AccordionItem>
</AccordionGroup>
</Card>
<!-- Customized -->
<h3>Customized</h3>
<section class="border border-surface-500/10 p-2 rounded-xl space-y-4">
<AccordionGroup>
<AccordionItem spacing="space-y-4" open>
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path class="fill-primary-500" d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"/></svg>
</svelte:fragment>
<svelte:fragment slot="summary">
<h3>Icon and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the first element.</p></Card>
</svelte:fragment>
</AccordionItem>
<AccordionItem spacing="space-y-4">
<svelte:fragment slot="lead"><Avatar size="sm" src="https://i.pravatar.cc/" /></svelte:fragment>
<svelte:fragment slot="summary">
<h3>Avatar and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the second element.</p></Card>
</svelte:fragment>
</AccordionItem>
<AccordionItem spacing="space-y-4">
<svelte:fragment slot="lead">
<Avatar initials="1" size="sm" background="bg-accent-500" />
</svelte:fragment>
<svelte:fragment slot="summary">
<h3>Numeral and Heading</h3>
</svelte:fragment>
<svelte:fragment slot="content">
<Card><p>The content for the third element.</p></Card>
</svelte:fragment>
</AccordionItem>
</AccordionGroup>
</section>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="typescript" code={`
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock
language="typescript"
code={`
import { writable, type Writable } from "svelte/store";
let storeAccordion: Writable<number[]> = writable([1]);`
.trim()}></CodeBlock>
<CodeBlock language="html" code={`
let storeAccordion: Writable<number[]> = writable([1]);`.trim()}
/>
<CodeBlock
language="html"
code={`
<AccordionGroup>
<AccordionItem open>
<svelte:fragment slot="summary">Summary 1</svelte:fragment>
@@ -133,32 +171,34 @@ let storeAccordion: Writable<number[]> = writable([1]);`
<svelte:fragment slot="content"><p>Content 3</p></svelte:fragment>
</AccordionItem>
</AccordionGroup>
`.trim()}></CodeBlock>
</section>
`.trim()}
/>
</section>
<!-- Properties -->
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<h3>Group</h3>
<DataTable headings="{tablePropsGroup.headings}" source="{tablePropsGroup.source}"></DataTable>
<h3>Item</h3>
<DataTable headings="{tablePropsItem.headings}" source="{tablePropsItem.source}"></DataTable>
<h3>Group</h3>
<DataTable headings={tablePropsGroup.headings} source={tablePropsGroup.source} />
<h3>Item</h3>
<DataTable headings={tablePropsItem.headings} source={tablePropsItem.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<div class="flex justify-between items-center">
<h3>Item</h3>
<a href="https://www.w3.org/WAI/ARIA/apg/example-index/accordion/accordion" target="_blank">ARIA Guidelines</a>
</div>
<DataTable headings="{tableA11yItem.headings}" source="{tableA11yItem.source}"></DataTable>
<div class="flex justify-between items-center">
<h3>Item</h3>
<a href="https://www.w3.org/WAI/ARIA/apg/example-index/accordion/accordion" target="_blank"
>ARIA Guidelines</a
>
</div>
<DataTable headings={tableA11yItem.headings} source={tableA11yItem.source} />
</section>
</div>
</div>

View File

@@ -1,91 +1,101 @@
<script lang='ts'>
import Card from "$lib/Card/Card.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte"
import Button from "$lib/Button/Button.svelte";
import Alert from "$lib/Alert/Alert.svelte";
<script lang="ts">
import Card from '$lib/Card/Card.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Button from '$lib/Button/Button.svelte';
import Alert from '$lib/Alert/Alert.svelte';
let icon = '<svg class="fill-white w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 400c-18 0-32-14-32-32s13.1-32 32-32c17.1 0 32 14 32 32S273.1 400 256 400zM325.1 258L280 286V288c0 13-11 24-24 24S232 301 232 288V272c0-8 4-16 12-21l57-34C308 213 312 206 312 198C312 186 301.1 176 289.1 176h-51.1C225.1 176 216 186 216 198c0 13-11 24-24 24s-24-11-24-24C168 159 199 128 237.1 128h51.1C329 128 360 159 360 198C360 222 347 245 325.1 258z"/></svg>';
let title = `What's New in Skeleton?`;
let message = 'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eligendi, cupiditate eveniet in neque magnam quos ad cumque quae numquam voluptatum magni atque vitae dolore voluptatibus aliquam tempora! Animi, nihil quo.';
let icon =
'<svg class="fill-white w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 400c-18 0-32-14-32-32s13.1-32 32-32c17.1 0 32 14 32 32S273.1 400 256 400zM325.1 258L280 286V288c0 13-11 24-24 24S232 301 232 288V272c0-8 4-16 12-21l57-34C308 213 312 206 312 198C312 186 301.1 176 289.1 176h-51.1C225.1 176 216 186 216 198c0 13-11 24-24 24s-24-11-24-24C168 159 199 128 237.1 128h51.1C329 128 360 159 360 198C360 222 347 245 325.1 258z"/></svg>';
let title = `What's New in Skeleton?`;
let message =
'Lorem ipsum dolor sit, amet consectetur adipisicing elit. Eligendi, cupiditate eveniet in neque magnam quos ad cumque quae numquam voluptatum magni atque vitae dolore voluptatibus aliquam tempora! Animi, nihil quo.';
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['visible', 'boolean', 'true', 'Control visibility of the alert.'],
['duration', 'number', '200', 'Set fade in/out animation speed. Set 0 (zero) to disable.'],
['background', 'string', 'bg-surface-500', 'Provide a class to set background color.'],
['color', 'string', 'text-white', 'Provide a class to set text color.'],
['radius', 'string', 'rounded-lg', 'Provide a class to set border radius.'],
],
};
const tableSlots: any = {
headings: ['Slot', 'Required', 'Description'],
source: [
['lead', '-', 'Provide a leading element, such as an icon.'],
['title','&check;', 'Provide the title of the alert.'],
['message', '-', 'Provide the message of the alert.'],
['trail', '-', 'Provide a trailing elements, such as buttons.']
]
}
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['visible', 'boolean', 'true', 'Control visibility of the alert.'],
['duration', 'number', '200', 'Set fade in/out animation speed. Set 0 (zero) to disable.'],
['background', 'string', 'bg-surface-500', 'Provide a class to set background color.'],
['color', 'string', 'text-white', 'Provide a class to set text color.'],
['radius', 'string', 'rounded-lg', 'Provide a class to set border radius.']
]
};
const tableSlots: any = {
headings: ['Slot', 'Required', 'Description'],
source: [
['lead', '-', 'Provide a leading element, such as an icon.'],
['title', '&check;', 'Provide the title of the alert.'],
['message', '-', 'Provide the message of the alert.'],
['trail', '-', 'Provide a trailing elements, such as buttons.']
]
};
function toggleVisible(): void { visible = !visible; }
function actionExample(): void { alert('Action button was triggered!'); }
function toggleVisible(): void {
visible = !visible;
}
function actionExample(): void {
alert('Action button was triggered!');
}
let visible: boolean = true;
let visible: boolean = true;
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Alerts</h1>
<p>Display customizable alerts to garner attention and provide critical actions.</p>
<CodeBlock language="javascript" code={`import { Alert } from '@brainandbones/skeleton';`} />
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Alerts</h1>
<p>Display customizable alerts to garner attention and provide critical actions.</p>
<CodeBlock language="javascript" code="{`import { Alert } from '@brainandbones/skeleton';`}"></CodeBlock>
</header>
<!-- Examples -->
<Card class="space-y-4">
{#if !visible}<Button variant="ghost" on:click={toggleVisible}>Enable Alerts</Button>{/if}
<Alert {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
<Button variant="ring" on:click={toggleVisible}>&#10005;</Button>
</svelte:fragment>
</Alert>
{#if visible}<h3>Simple</h3>{/if}
<Alert background="bg-primary-500" {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
</svelte:fragment>
</Alert>
{#if visible}<h3>Rounding</h3>{/if}
<Alert background="bg-warning-500" rounded="rounded-3xl" {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
</Alert>
{#if visible}<h3>Fully Featured</h3>{/if}
<Alert background="bg-accent-500" {visible}>
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
<Button variant="ring" on:click={toggleVisible}>&#10005;</Button>
</svelte:fragment>
</Alert>
</Card>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="typescript" code="{`
<!-- Examples -->
<Card class="space-y-4">
{#if !visible}<Button variant="ghost" on:click={toggleVisible}>Enable Alerts</Button>{/if}
<Alert {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
<Button variant="ring" on:click={toggleVisible}>&#10005;</Button>
</svelte:fragment>
</Alert>
{#if visible}<h3>Simple</h3>{/if}
<Alert background="bg-primary-500" {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
</svelte:fragment>
</Alert>
{#if visible}<h3>Rounding</h3>{/if}
<Alert background="bg-warning-500" rounded="rounded-3xl" {visible}>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
</Alert>
{#if visible}<h3>Fully Featured</h3>{/if}
<Alert background="bg-accent-500" {visible}>
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
<svelte:fragment slot="title">{title}</svelte:fragment>
<svelte:fragment slot="message">{message}</svelte:fragment>
<svelte:fragment slot="trail">
<Button variant="filled" on:click={actionExample}>Show Me</Button>
<Button variant="ring" on:click={toggleVisible}>&#10005;</Button>
</svelte:fragment>
</Alert>
</Card>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock
language="typescript"
code={`
let visible: boolean = true;
function actionExample(): void { alert('Action button was triggered!'); }`}"></CodeBlock>
<CodeBlock language="html" code="{`
function actionExample(): void { alert('Action button was triggered!'); }`}
/>
<CodeBlock
language="html"
code={`
<Alert {visible}>
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
<svelte:fragment slot="title">Hello, Skeleton</svelte:fragment>
@@ -94,25 +104,25 @@ function actionExample(): void { alert('Action button was triggered!'); }`}"></C
<Button variant="filled" on:click={actionExample}>Show Me</Button>
</svelte:fragment>
</Alert>
`.trim()}"></CodeBlock>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
</section>
`.trim()}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>Makes use of <code>role="alert"</code> and <code>aria-live="polite"</code>.</p>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
</div>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>Makes use of <code>role="alert"</code> and <code>aria-live="polite"</code>.</p>
</section>
</div>

View File

@@ -1,61 +1,84 @@
<script lang="ts">
import { writable, type Writable } from "svelte/store";
import { writable, type Writable } from 'svelte/store';
import Card from "$lib/Card/Card.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import RadioGroup from "$lib/Radio/RadioGroup.svelte";
import RadioItem from "$lib/Radio/RadioItem.svelte";
import SlideToggle from "$lib/SlideToggle/SlideToggle.svelte";
import Avatar from "$lib/Avatar/Avatar.svelte";
import Card from '$lib/Card/Card.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import RadioGroup from '$lib/Radio/RadioGroup.svelte';
import RadioItem from '$lib/Radio/RadioItem.svelte';
import SlideToggle from '$lib/SlideToggle/SlideToggle.svelte';
import Avatar from '$lib/Avatar/Avatar.svelte';
const storeSrc: Writable<string> = writable(undefined);
let placeholder: string = 'https://i.pravatar.cc/';
const storeSrc: Writable<string> = writable(undefined);
let placeholder: string = 'https://i.pravatar.cc/';
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
['initials', 'string', 'A', 'text', 'Provide up to two text characters.'],
['src', 'string', 'url', '-', 'The image source to display.'],
['size', 'string', 'full', 'sm | md | lg | xl | 2xl | 3xl | full', 'Sets the circle and text sizing.'],
['background', 'string', 'bg-surface-500', 'class', 'Provide a class to set background color. Only works with initials'],
['color', 'string', 'text-white', 'class', 'Provide a class to set text color.'],
['outlined', 'boolean', 'false', 'true | false', 'Displays an outline of the primary color.'],
['hover', 'boolean', 'false', 'true | false', 'Displays and outline when hovering the avatar.'],
['filter', 'string', 'false', 'filter reference', 'Enables a visual <a href="/utilities/filters">Filter</a>. Only works with src.'],
],
};
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
['initials', 'string', 'A', 'text', 'Provide up to two text characters.'],
['src', 'string', 'url', '-', 'The image source to display.'],
[
'size',
'string',
'full',
'sm | md | lg | xl | 2xl | 3xl | full',
'Sets the circle and text sizing.'
],
[
'background',
'string',
'bg-surface-500',
'class',
'Provide a class to set background color. Only works with initials'
],
['color', 'string', 'text-white', 'class', 'Provide a class to set text color.'],
['outlined', 'boolean', 'false', 'true | false', 'Displays an outline of the primary color.'],
[
'hover',
'boolean',
'false',
'true | false',
'Displays and outline when hovering the avatar.'
],
[
'filter',
'string',
'false',
'filter reference',
'Enables a visual <a href="/utilities/filters">Filter</a>. Only works with src.'
]
]
};
function updateImage(): void {
props.src = undefined;
setTimeout(() => {
props.src = placeholder;
}, 1)
}
function updateImage(): void {
props.src = undefined;
setTimeout(() => {
props.src = placeholder;
}, 1);
}
$:props = {
$: props = {
initials: 'SK',
src: $storeSrc,
size: '3xl',
background: 'bg-surface-500',
color: undefined,
outlined: false,
hover: false,
filter: ''
size: '3xl',
background: 'bg-surface-500',
color: undefined,
outlined: false,
hover: false,
filter: ''
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Avatars</h1>
<p>Choose from a variety for avatar sizes and styles, using either initials or images.</p>
<CodeBlock language="js" code={`import { Avatar } from '@brainandbones/skeleton';`} />
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Avatars</h1>
<p>Choose from a variety for avatar sizes and styles, using either initials or images.</p>
<CodeBlock language="js" code={`import { Avatar } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Sandbox -->
<section class="space-y-4">
<!-- Sandbox -->
<section class="space-y-4">
<div class="space-y-4 xl:space-y-0 xl:grid grid-cols-[2fr,1fr] gap-2">
<!-- Example -->
<Card class="space-y-4 flex justify-center items-center">
@@ -68,88 +91,101 @@
color={props.color}
outlined={props.outlined}
hover={props.hover}
filter={props.filter}
></svelte:component>
</Card>
filter={props.filter}
/>
</Card>
<!-- Options -->
<Card class="space-y-4">
<!-- Size -->
<!-- Size -->
<label>
<span>Size</span>
<select name="size" id="size" bind:value={props.size}>
<option value="sm">sm</option>
<option value="md">md</option>
<option value="lg">lg</option>
<option value="xl">xl</option>
<option value="2xl">2xl</option>
<option value="3xl">3xl</option>
<option value="full">full</option>
</select>
</label>
<!-- Source -->
<div>
<legend>Source</legend>
<RadioGroup selected={storeSrc} background="bg-accent-500" color="text-white" width="w-full">
<RadioItem value={undefined}>Initials</RadioItem>
<RadioItem value={placeholder}>Image</RadioItem>
</RadioGroup>
</div>
{#if $storeSrc === undefined}
<!-- Initials -->
<label>
<span>Initials</span>
<input type="text" bind:value={props.initials} maxlength="2">
</label>
<!-- Background -->
<label>
<span>Background</span>
<select name="background" id="background" bind:value={props.background}>
<option value="bg-surface-500">Default</option>
<option value="bg-primary-500">bg-primary-500</option>
<option value="bg-accent-500">bg-accent-500</option>
<option value="bg-warning-500">bg-warning-500</option>
</select>
</label>
{/if}
<!-- Filter -->
{#if $storeSrc !== undefined}
<label>
<span>Filter</span>
<select name="filter" id="filter" bind:value={props.filter} on:change={updateImage}>
<option value="">None</option>
<option value="Apollo">Apollo</option>
<option value="BlueNight">BlueNight</option>
<option value="Emerald">Emerald</option>
<option value="GreenFall">GreenFall</option>
<option value="Noir">Noir</option>
<option value="NoirLight">NoirLight</option>
<option value="Rustic">Rustic</option>
<option value="Summer84">Summer84</option>
<option value="XPro">XPro</option>
</select>
</label>
{/if}
<div class="flex space-x-4">
<div class="flex-1"><SlideToggle bind:checked={props.outlined} accent="bg-accent-500">Outlined</SlideToggle></div>
<div class="flex-1"><SlideToggle bind:checked={props.hover} accent="bg-accent-500">Hover</SlideToggle></div>
</div>
<span>Size</span>
<select name="size" id="size" bind:value={props.size}>
<option value="sm">sm</option>
<option value="md">md</option>
<option value="lg">lg</option>
<option value="xl">xl</option>
<option value="2xl">2xl</option>
<option value="3xl">3xl</option>
<option value="full">full</option>
</select>
</label>
<!-- Source -->
<div>
<legend>Source</legend>
<RadioGroup
selected={storeSrc}
background="bg-accent-500"
color="text-white"
width="w-full"
>
<RadioItem value={undefined}>Initials</RadioItem>
<RadioItem value={placeholder}>Image</RadioItem>
</RadioGroup>
</div>
{#if $storeSrc === undefined}
<!-- Initials -->
<label>
<span>Initials</span>
<input type="text" bind:value={props.initials} maxlength="2" />
</label>
<!-- Background -->
<label>
<span>Background</span>
<select name="background" id="background" bind:value={props.background}>
<option value="bg-surface-500">Default</option>
<option value="bg-primary-500">bg-primary-500</option>
<option value="bg-accent-500">bg-accent-500</option>
<option value="bg-warning-500">bg-warning-500</option>
</select>
</label>
{/if}
<!-- Filter -->
{#if $storeSrc !== undefined}
<label>
<span>Filter</span>
<select name="filter" id="filter" bind:value={props.filter} on:change={updateImage}>
<option value="">None</option>
<option value="Apollo">Apollo</option>
<option value="BlueNight">BlueNight</option>
<option value="Emerald">Emerald</option>
<option value="GreenFall">GreenFall</option>
<option value="Noir">Noir</option>
<option value="NoirLight">NoirLight</option>
<option value="Rustic">Rustic</option>
<option value="Summer84">Summer84</option>
<option value="XPro">XPro</option>
</select>
</label>
{/if}
<div class="flex space-x-4">
<div class="flex-1">
<SlideToggle bind:checked={props.outlined} accent="bg-accent-500">Outlined</SlideToggle>
</div>
<div class="flex-1">
<SlideToggle bind:checked={props.hover} accent="bg-accent-500">Hover</SlideToggle>
</div>
</div>
</Card>
</div>
<CodeBlock
language="html"
code={`<Avatar initials="${props.initials || 'A'}" ${props.src ? `src="${props.src}"` : ''} size="${props.size}" background="${props.background}" outlined={${props.outlined}} hover={${props.hover}} ${props.filter ? `filter="${props.filter}"` : ''}/>`.trim()}></CodeBlock>
code={`<Avatar initials="${props.initials || 'A'}" ${
props.src ? `src="${props.src}"` : ''
} size="${props.size}" background="${props.background}" outlined={${props.outlined}} hover={${
props.hover
}} ${props.filter ? `filter="${props.filter}"` : ''}/>`.trim()}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
</section>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>You many apply an <code>alt</code> tag, which is appended to the image element.</p>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
</div>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>You many apply an <code>alt</code> tag, which is appended to the image element.</p>
</section>
</div>

View File

@@ -1,133 +1,141 @@
<script lang="ts">
import Card from "$lib/Card/Card.svelte";
import Avatar from "$lib/Avatar/Avatar.svelte";
import Button from "$lib/Button/Button.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import Badge from "$lib/Badge/Badge.svelte";
import Card from '$lib/Card/Card.svelte';
import Avatar from '$lib/Avatar/Avatar.svelte';
import Button from '$lib/Button/Button.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import Badge from '$lib/Badge/Badge.svelte';
let icon = '<svg class="w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM371.8 211.8C382.7 200.9 382.7 183.1 371.8 172.2C360.9 161.3 343.1 161.3 332.2 172.2L224 280.4L179.8 236.2C168.9 225.3 151.1 225.3 140.2 236.2C129.3 247.1 129.3 264.9 140.2 275.8L204.2 339.8C215.1 350.7 232.9 350.7 243.8 339.8L371.8 211.8z"/></svg>';
let icon =
'<svg class="w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256zM371.8 211.8C382.7 200.9 382.7 183.1 371.8 172.2C360.9 161.3 343.1 161.3 332.2 172.2L224 280.4L179.8 236.2C168.9 225.3 151.1 225.3 140.2 236.2C129.3 247.1 129.3 264.9 140.2 275.8L204.2 339.8C215.1 350.7 232.9 350.7 243.8 339.8L371.8 211.8z"/></svg>';
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['icon', 'boolean', 'false', 'Adjusts styling to accommodate icons.'],
['background', 'class', 'bg-surface-500' , 'Provide a class to set the background color.'],
['color', 'class', 'text-white', 'Provide a class to set the text color.'],
['fill', 'class', 'fill-white', 'Provide a class to set the fill color for SVG icons.'],
['rounded', 'class', 'rounded-lg', 'Provide a class to set rounding style.'],
],
};
// Slots
const tableSlots: any = {
headings: ['Slot', 'Description'],
source: [
['lead', 'Define a leading element, such as an icon.'],
['trail', 'Define a trailing element, such as an icon.'],
],
};
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['icon', 'boolean', 'false', 'Adjusts styling to accommodate icons.'],
['background', 'class', 'bg-surface-500', 'Provide a class to set the background color.'],
['color', 'class', 'text-white', 'Provide a class to set the text color.'],
['fill', 'class', 'fill-white', 'Provide a class to set the fill color for SVG icons.'],
['rounded', 'class', 'rounded-lg', 'Provide a class to set rounding style.']
]
};
// Slots
const tableSlots: any = {
headings: ['Slot', 'Description'],
source: [
['lead', 'Define a leading element, such as an icon.'],
['trail', 'Define a trailing element, such as an icon.']
]
};
</script>
<div class="space-y-8">
<!-- Heading -->
<heading class="space-y-4">
<h1>Badges</h1>
<p>Useful for displaying a small badge, pill, or label tag.</p>
<CodeBlock language="javascript" code={`import { Badge } from '@brainandbones/skeleton';`}></CodeBlock>
</heading>
<!-- Heading -->
<heading class="space-y-4">
<h1>Badges</h1>
<p>Useful for displaying a small badge, pill, or label tag.</p>
<CodeBlock language="javascript" code={`import { Badge } from '@brainandbones/skeleton';`} />
</heading>
<!-- Examples -->
<section class="space-y-4">
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-2 md:gap-4">
<Card class="space-y-4">
<div class="flex justify-center space-x-2">
<Badge>Skeleton</Badge>
<Badge background="bg-primary-500" color="text-primary-100">Skeleton</Badge>
<Badge background="bg-accent-500" color="text-accent-100">Skeleton</Badge>
<Badge background="bg-warning-500" color="text-primary-100">Skeleton</Badge>
</div>
</Card>
<Card>
<div class="flex justify-center space-x-2">
<Badge>
Complete
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
</Badge>
<Badge background="bg-yellow-500" color="text-yellow-900" fill="fill-yellow-900">
Complete
<svelte:fragment slot="trail">{@html icon}</svelte:fragment>
</Badge>
<Badge background="bg-pink-300" color="text-pink-900" rounded="rounded-full">
Favorite
<svelte:fragment slot="lead">❤️</svelte:fragment>
</Badge>
</div>
</Card>
</section>
<h3>Positioning</h3>
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-3 md:gap-4">
<Card>
<div class="flex justify-center items-center h-full space-x-2">
<p>Skeleton</p>
<sup><Badge class="-ml-0" background="bg-primary-500">Super</Badge></sup>
</div>
</Card>
<Card>
<div class="flex justify-center items-center h-full space-x-2">
<p>Skeleton</p>
<sub><Badge class="-ml-0" background="bg-accent-500">Subscript</Badge></sub>
</div>
</Card>
<Card >
<div class="flex justify-center items-center space-x-4">
<div class="relative inline-block">
<Badge icon background="bg-warning-500" class="absolute top-0 right-0 shadow-xl">2</Badge>
<Avatar size="md" />
</div>
<div class="relative inline-block">
<Badge icon background="bg-primary-500" class="absolute top-0 right-0 shadow-xl">{@html icon}</Badge>
<Avatar size="md" />
</div>
<div class="relative inline-block">
<Badge class="absolute -top-2 -right-4 shadow-xl z-10">5k</Badge>
<Button size="sm" variant="filled-accent">Button</Button>
</div>
</div>
</Card>
</section>
</section>
<!-- Examples -->
<section class="space-y-4">
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-2 md:gap-4">
<Card class="space-y-4">
<div class="flex justify-center space-x-2">
<Badge>Skeleton</Badge>
<Badge background="bg-primary-500" color="text-primary-100">Skeleton</Badge>
<Badge background="bg-accent-500" color="text-accent-100">Skeleton</Badge>
<Badge background="bg-warning-500" color="text-primary-100">Skeleton</Badge>
</div>
</Card>
<Card>
<div class="flex justify-center space-x-2">
<Badge>
Complete
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
</Badge>
<Badge background="bg-yellow-500" color="text-yellow-900" fill="fill-yellow-900">
Complete
<svelte:fragment slot="trail">{@html icon}</svelte:fragment>
</Badge>
<Badge background="bg-pink-300" color="text-pink-900" rounded="rounded-full">
Favorite
<svelte:fragment slot="lead">❤️</svelte:fragment>
</Badge>
</div>
</Card>
</section>
<h3>Positioning</h3>
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-3 md:gap-4">
<Card>
<div class="flex justify-center items-center h-full space-x-2">
<p>Skeleton</p>
<sup><Badge class="-ml-0" background="bg-primary-500">Super</Badge></sup>
</div>
</Card>
<Card>
<div class="flex justify-center items-center h-full space-x-2">
<p>Skeleton</p>
<sub><Badge class="-ml-0" background="bg-accent-500">Subscript</Badge></sub>
</div>
</Card>
<Card>
<div class="flex justify-center items-center space-x-4">
<div class="relative inline-block">
<Badge icon background="bg-warning-500" class="absolute top-0 right-0 shadow-xl"
>2</Badge
>
<Avatar size="md" />
</div>
<div class="relative inline-block">
<Badge icon background="bg-primary-500" class="absolute top-0 right-0 shadow-xl"
>{@html icon}</Badge
>
<Avatar size="md" />
</div>
<div class="relative inline-block">
<Badge class="absolute -top-2 -right-4 shadow-xl z-10">5k</Badge>
<Button size="sm" variant="filled-accent">Button</Button>
</div>
</div>
</Card>
</section>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<Badge background="bg-primary-500">{label}</Badge>`}></CodeBlock>
<CodeBlock language="html" code={`
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<Badge background="bg-primary-500">{label}</Badge>`} />
<CodeBlock
language="html"
code={`
<Badge background="bg-pink-300" color="text-pink-900" rounded="rounded-full">
Favorite
<svelte:fragment slot="lead">❤️</svelte:fragment>
</Badge>
`.trim()}></CodeBlock>
<CodeBlock language="html" code={`
`.trim()}
/>
<CodeBlock
language="html"
code={`
<div class="relative inline-block">
<Badge icon background="bg-warning-500" class="absolute top-0 right-0 shadow-xl">2</Badge>
<Avatar size="md" />
</div>
`.trim()}></CodeBlock>
</section>
`.trim()}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
</div>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
</div>

View File

@@ -1,134 +1,151 @@
<script lang="ts">
import Card from "$lib/Card/Card.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Breadcrumb from "$lib/Breadcrumb/Breadcrumb.svelte";
import Crumb from "$lib/Breadcrumb/Crumb.svelte";
import Card from '$lib/Card/Card.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Breadcrumb from '$lib/Breadcrumb/Breadcrumb.svelte';
import Crumb from '$lib/Breadcrumb/Crumb.svelte';
// Examples
const customSeparator: string = `<span class="text-surface-500">/</span>`;
// Examples
const customSeparator: string = `<span class="text-surface-500">/</span>`;
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['separator', 'string', '&rsaquo (unicode)', 'Defines the crumb seperator. Supports SVG icons.']
],
};
const tablePropsCrumb: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['href', 'string', '-', 'Optionally provide an anchor href value.'],
['current', 'boolean ', 'false', 'Sets the crumb to a disabled mode.'],
],
};
const tableSlotsCrumb: any = {
headings: ['Slot', 'Description'],
source: [
['lead', 'A leading slot intended for icons.'],
],
};
const tableA11y: any = {
headings: ['Prop', 'Default', 'Description'],
source: [
['label', '-', `A semantic ARIA label.`],
],
};
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
[
'separator',
'string',
'&rsaquo (unicode)',
'Defines the crumb seperator. Supports SVG icons.'
]
]
};
const tablePropsCrumb: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['href', 'string', '-', 'Optionally provide an anchor href value.'],
['current', 'boolean ', 'false', 'Sets the crumb to a disabled mode.']
]
};
const tableSlotsCrumb: any = {
headings: ['Slot', 'Description'],
source: [['lead', 'A leading slot intended for icons.']]
};
const tableA11y: any = {
headings: ['Prop', 'Default', 'Description'],
source: [['label', '-', `A semantic ARIA label.`]]
};
</script>
<div class="space-y-8">
<!-- Heading -->
<heading class="space-y-4">
<h1>Breadcrumbs</h1>
<p>Allows display of navigation hierarchy.</p>
<CodeBlock language="javascript" code={`import { Breadcrumb, Crumb } from '@brainandbones/skeleton';`}></CodeBlock>
</heading>
<!-- Heading -->
<heading class="space-y-4">
<h1>Breadcrumbs</h1>
<p>Allows display of navigation hierarchy.</p>
<CodeBlock
language="javascript"
code={`import { Breadcrumb, Crumb } from '@brainandbones/skeleton';`}
/>
</heading>
<!-- Examples -->
<section class="space-y-4">
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div class="space-y-4">
<h3>Basic</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">Home</Crumb>
<Crumb href="/">Articles</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Separator</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb separator={customSeparator}>
<Crumb href="/">Home</Crumb>
<Crumb href="/">Blog</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Icons</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Homepage</title>
<path class="fill-primary-500" d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"/>
</svg>
</Crumb>
<Crumb href="/">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Cruise</title>
<path class="fill-primary-500" d="M191.1 32C191.1 14.33 206.3 0 223.1 0H352C369.7 0 384 14.33 384 32V64H424C454.9 64 480 89.07 480 120V238.4L505.9 248.1C544 262.4 554.6 311.3 525.8 340.1L456.1 408.1C447.6 418.3 432.4 418.3 423 408.1C413.7 399.6 413.7 384.4 423 375L491.9 306.2C496 302.1 494.5 295.1 489 293L290.8 218.7C288.1 218 287 218 285.2 218.7L86.96 293C81.51 295.1 79.1 302.1 84.11 306.2L152.1 375C162.3 384.4 162.3 399.6 152.1 408.1C143.6 418.3 128.4 418.3 119 408.1L50.17 340.1C21.37 311.3 31.97 262.4 70.1 248.1L95.1 238.4V119.1C95.1 89.07 121.1 63.1 151.1 63.1H191.1L191.1 32zM143.1 220.4L268.3 173.7C281 168.1 294.1 168.1 307.7 173.7L432 220.4V120C432 115.6 428.4 112 424 112H151.1C147.6 112 143.1 115.6 143.1 120L143.1 220.4zM191.1 464C220.8 464 250.5 448.7 272 430.1C281.1 421.1 294.8 421.1 303.1 430.1C325.5 448.7 355.1 464 383.1 464C412.8 464 442.5 448.7 464 430.1C473.1 421.1 486.8 421.1 495.9 430.1C512.9 445.1 535.2 456.9 557.2 461.8C570.1 464.7 578.3 477.5 575.4 490.5C572.5 503.4 559.6 511.5 546.7 508.6C518 502.2 494.4 488.2 479.1 478.2C451.9 497.7 418.6 512 383.1 512C349.4 512 316.1 497.7 287.1 478.2C259.9 497.7 226.6 512 191.1 512C157.4 512 124.1 497.7 95.98 478.2C81.61 488.2 57.99 502.2 29.3 508.6C16.37 511.5 3.534 503.4 .633 490.5C-2.268 477.5 5.864 464.7 18.8 461.8C41 456.8 62.76 444.1 79.1 430.1C89.09 421.1 102.8 421.1 111.9 430.1C133.5 448.7 163.1 464 191.1 464L191.1 464z"/>
</svg>
</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Mixed</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Homepage</title>
<path class="fill-primary-500" d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"/>
</svg>
</svelte:fragment>
<span>Home</span>
</Crumb>
<Crumb href="/">
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Cruise</title>
<path class="fill-primary-500" d="M191.1 32C191.1 14.33 206.3 0 223.1 0H352C369.7 0 384 14.33 384 32V64H424C454.9 64 480 89.07 480 120V238.4L505.9 248.1C544 262.4 554.6 311.3 525.8 340.1L456.1 408.1C447.6 418.3 432.4 418.3 423 408.1C413.7 399.6 413.7 384.4 423 375L491.9 306.2C496 302.1 494.5 295.1 489 293L290.8 218.7C288.1 218 287 218 285.2 218.7L86.96 293C81.51 295.1 79.1 302.1 84.11 306.2L152.1 375C162.3 384.4 162.3 399.6 152.1 408.1C143.6 418.3 128.4 418.3 119 408.1L50.17 340.1C21.37 311.3 31.97 262.4 70.1 248.1L95.1 238.4V119.1C95.1 89.07 121.1 63.1 151.1 63.1H191.1L191.1 32zM143.1 220.4L268.3 173.7C281 168.1 294.1 168.1 307.7 173.7L432 220.4V120C432 115.6 428.4 112 424 112H151.1C147.6 112 143.1 115.6 143.1 120L143.1 220.4zM191.1 464C220.8 464 250.5 448.7 272 430.1C281.1 421.1 294.8 421.1 303.1 430.1C325.5 448.7 355.1 464 383.1 464C412.8 464 442.5 448.7 464 430.1C473.1 421.1 486.8 421.1 495.9 430.1C512.9 445.1 535.2 456.9 557.2 461.8C570.1 464.7 578.3 477.5 575.4 490.5C572.5 503.4 559.6 511.5 546.7 508.6C518 502.2 494.4 488.2 479.1 478.2C451.9 497.7 418.6 512 383.1 512C349.4 512 316.1 497.7 287.1 478.2C259.9 497.7 226.6 512 191.1 512C157.4 512 124.1 497.7 95.98 478.2C81.61 488.2 57.99 502.2 29.3 508.6C16.37 511.5 3.534 503.4 .633 490.5C-2.268 477.5 5.864 464.7 18.8 461.8C41 456.8 62.76 444.1 79.1 430.1C89.09 421.1 102.8 421.1 111.9 430.1C133.5 448.7 163.1 464 191.1 464L191.1 464z"/>
</svg>
</svelte:fragment>
<span>Subpage</span>
</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
</div>
</section>
<!-- Examples -->
<section class="space-y-4">
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div class="space-y-4">
<h3>Basic</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">Home</Crumb>
<Crumb href="/">Articles</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Separator</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb separator={customSeparator}>
<Crumb href="/">Home</Crumb>
<Crumb href="/">Blog</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Icons</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Homepage</title>
<path
class="fill-primary-500"
d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"
/>
</svg>
</Crumb>
<Crumb href="/">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Cruise</title>
<path
class="fill-primary-500"
d="M191.1 32C191.1 14.33 206.3 0 223.1 0H352C369.7 0 384 14.33 384 32V64H424C454.9 64 480 89.07 480 120V238.4L505.9 248.1C544 262.4 554.6 311.3 525.8 340.1L456.1 408.1C447.6 418.3 432.4 418.3 423 408.1C413.7 399.6 413.7 384.4 423 375L491.9 306.2C496 302.1 494.5 295.1 489 293L290.8 218.7C288.1 218 287 218 285.2 218.7L86.96 293C81.51 295.1 79.1 302.1 84.11 306.2L152.1 375C162.3 384.4 162.3 399.6 152.1 408.1C143.6 418.3 128.4 418.3 119 408.1L50.17 340.1C21.37 311.3 31.97 262.4 70.1 248.1L95.1 238.4V119.1C95.1 89.07 121.1 63.1 151.1 63.1H191.1L191.1 32zM143.1 220.4L268.3 173.7C281 168.1 294.1 168.1 307.7 173.7L432 220.4V120C432 115.6 428.4 112 424 112H151.1C147.6 112 143.1 115.6 143.1 120L143.1 220.4zM191.1 464C220.8 464 250.5 448.7 272 430.1C281.1 421.1 294.8 421.1 303.1 430.1C325.5 448.7 355.1 464 383.1 464C412.8 464 442.5 448.7 464 430.1C473.1 421.1 486.8 421.1 495.9 430.1C512.9 445.1 535.2 456.9 557.2 461.8C570.1 464.7 578.3 477.5 575.4 490.5C572.5 503.4 559.6 511.5 546.7 508.6C518 502.2 494.4 488.2 479.1 478.2C451.9 497.7 418.6 512 383.1 512C349.4 512 316.1 497.7 287.1 478.2C259.9 497.7 226.6 512 191.1 512C157.4 512 124.1 497.7 95.98 478.2C81.61 488.2 57.99 502.2 29.3 508.6C16.37 511.5 3.534 503.4 .633 490.5C-2.268 477.5 5.864 464.7 18.8 461.8C41 456.8 62.76 444.1 79.1 430.1C89.09 421.1 102.8 421.1 111.9 430.1C133.5 448.7 163.1 464 191.1 464L191.1 464z"
/>
</svg>
</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
<div class="space-y-4">
<h3>Mixed</h3>
<Card class="overflow-x-auto">
<div class="w-full overflow-x-auto min-w-[400px]">
<Breadcrumb>
<Crumb href="/">
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Homepage</title>
<path
class="fill-primary-500"
d="M567.5 229.7C577.6 238.3 578.9 253.4 570.3 263.5C561.7 273.6 546.6 274.9 536.5 266.3L512 245.5V432C512 476.2 476.2 512 432 512H144C99.82 512 64 476.2 64 432V245.5L39.53 266.3C29.42 274.9 14.28 273.6 5.7 263.5C-2.875 253.4-1.634 238.3 8.473 229.7L272.5 5.7C281.4-1.9 294.6-1.9 303.5 5.7L567.5 229.7zM144 464H192V312C192 289.9 209.9 272 232 272H344C366.1 272 384 289.9 384 312V464H432C449.7 464 464 449.7 464 432V204.8L288 55.47L112 204.8V432C112 449.7 126.3 464 144 464V464zM240 464H336V320H240V464z"
/>
</svg>
</svelte:fragment>
<span>Home</span>
</Crumb>
<Crumb href="/">
<svelte:fragment slot="lead">
<svg class="w-[24px]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<title>Cruise</title>
<path
class="fill-primary-500"
d="M191.1 32C191.1 14.33 206.3 0 223.1 0H352C369.7 0 384 14.33 384 32V64H424C454.9 64 480 89.07 480 120V238.4L505.9 248.1C544 262.4 554.6 311.3 525.8 340.1L456.1 408.1C447.6 418.3 432.4 418.3 423 408.1C413.7 399.6 413.7 384.4 423 375L491.9 306.2C496 302.1 494.5 295.1 489 293L290.8 218.7C288.1 218 287 218 285.2 218.7L86.96 293C81.51 295.1 79.1 302.1 84.11 306.2L152.1 375C162.3 384.4 162.3 399.6 152.1 408.1C143.6 418.3 128.4 418.3 119 408.1L50.17 340.1C21.37 311.3 31.97 262.4 70.1 248.1L95.1 238.4V119.1C95.1 89.07 121.1 63.1 151.1 63.1H191.1L191.1 32zM143.1 220.4L268.3 173.7C281 168.1 294.1 168.1 307.7 173.7L432 220.4V120C432 115.6 428.4 112 424 112H151.1C147.6 112 143.1 115.6 143.1 120L143.1 220.4zM191.1 464C220.8 464 250.5 448.7 272 430.1C281.1 421.1 294.8 421.1 303.1 430.1C325.5 448.7 355.1 464 383.1 464C412.8 464 442.5 448.7 464 430.1C473.1 421.1 486.8 421.1 495.9 430.1C512.9 445.1 535.2 456.9 557.2 461.8C570.1 464.7 578.3 477.5 575.4 490.5C572.5 503.4 559.6 511.5 546.7 508.6C518 502.2 494.4 488.2 479.1 478.2C451.9 497.7 418.6 512 383.1 512C349.4 512 316.1 497.7 287.1 478.2C259.9 497.7 226.6 512 191.1 512C157.4 512 124.1 497.7 95.98 478.2C81.61 488.2 57.99 502.2 29.3 508.6C16.37 511.5 3.534 503.4 .633 490.5C-2.268 477.5 5.864 464.7 18.8 461.8C41 456.8 62.76 444.1 79.1 430.1C89.09 421.1 102.8 421.1 111.9 430.1C133.5 448.7 163.1 464 191.1 464L191.1 464z"
/>
</svg>
</svelte:fragment>
<span>Subpage</span>
</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
</div>
</Card>
</div>
</div>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock
language="html"
code={`
<Breadcrumb>
<Crumb href='/'>
<svelte:fragment slot="lead">{@html icon}</svelte:fragment>
@@ -137,33 +154,35 @@
<Crumb href='/'>Subpage</Crumb>
<Crumb current>Current</Crumb>
</Breadcrumb>
`.trim()}></CodeBlock>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<h3>Breadcrumb</h3>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
<h3>Crumb</h3>
<DataTable headings="{tablePropsCrumb.headings}" source="{tablePropsCrumb.source}"></DataTable>
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<h3>Crumb</h3>
<DataTable headings="{tableSlotsCrumb.headings}" source="{tableSlotsCrumb.source}"></DataTable>
</section>
<!-- Accessibility -->
<section class="space-y-4">
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/" target="_blank">ARIA Guidelines</a>
</div>
<h3>Breadcrumb</h3>
<DataTable headings="{tableA11y.headings}" source="{tableA11y.source}"></DataTable>
`.trim()}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<h3>Breadcrumb</h3>
<DataTable headings={tableProps.headings} source={tableProps.source} />
<h3>Crumb</h3>
<DataTable headings={tablePropsCrumb.headings} source={tablePropsCrumb.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<h3>Crumb</h3>
<DataTable headings={tableSlotsCrumb.headings} source={tableSlotsCrumb.source} />
</section>
<!-- Accessibility -->
<section class="space-y-4">
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/" target="_blank"
>ARIA Guidelines</a
>
</div>
<h3>Breadcrumb</h3>
<DataTable headings={tableA11y.headings} source={tableA11y.source} />
</section>
</div>

View File

@@ -1,56 +1,89 @@
<script lang="ts">
import Card from '$lib/Card/Card.svelte';
import DataTable from "$lib/Table/DataTable.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import SlideToggle from '$lib/SlideToggle/SlideToggle.svelte';
import Button from '$lib/Button/Button.svelte';
function onClickHandler(): void { console.log('Button was clicked!'); }
function onClickHandler(): void {
console.log('Button was clicked!');
}
// SVG Icon
const svgIconSkull: string = '<svg class="w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 400V464C416 490.5 394.5 512 368 512H320V464C320 455.2 312.8 448 304 448C295.2 448 288 455.2 288 464V512H224V464C224 455.2 216.8 448 208 448C199.2 448 192 455.2 192 464V512H144C117.5 512 96 490.5 96 464V400C96 399.6 96 399.3 96.01 398.9C37.48 357.8 0 294.7 0 224C0 100.3 114.6 0 256 0C397.4 0 512 100.3 512 224C512 294.7 474.5 357.8 415.1 398.9C415.1 399.3 416 399.6 416 400V400zM160 192C124.7 192 96 220.7 96 256C96 291.3 124.7 320 160 320C195.3 320 224 291.3 224 256C224 220.7 195.3 192 160 192zM352 320C387.3 320 416 291.3 416 256C416 220.7 387.3 192 352 192C316.7 192 288 220.7 288 256C288 291.3 316.7 320 352 320z"/></svg>';
const svgIconSkull: string =
'<svg class="w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 400V464C416 490.5 394.5 512 368 512H320V464C320 455.2 312.8 448 304 448C295.2 448 288 455.2 288 464V512H224V464C224 455.2 216.8 448 208 448C199.2 448 192 455.2 192 464V512H144C117.5 512 96 490.5 96 464V400C96 399.6 96 399.3 96.01 398.9C37.48 357.8 0 294.7 0 224C0 100.3 114.6 0 256 0C397.4 0 512 100.3 512 224C512 294.7 474.5 357.8 415.1 398.9C415.1 399.3 416 399.6 416 400V400zM160 192C124.7 192 96 220.7 96 256C96 291.3 124.7 320 160 320C195.3 320 224 291.3 224 256C224 220.7 195.3 192 160 192zM352 320C387.3 320 416 291.3 416 256C416 220.7 387.3 192 352 192C316.7 192 288 220.7 288 256C288 291.3 316.7 320 352 320z"/></svg>';
// Variants
const variantExamples: any[] = [
{label: 'Text', variants: ['text', 'text-primary', 'text-accent', 'text-warning']},
{label: 'Filled', variants: ['filled', 'filled-primary', 'filled-accent', 'filled-warning']},
{label: 'Ring', variants: ['ring', 'ring-primary', 'ring-accent', 'ring-warning']},
{label: 'Ghost', variants: ['ghost', 'ghost-primary', 'ghost-accent', 'ghost-warning']},
{ label: 'Text', variants: ['text', 'text-primary', 'text-accent', 'text-warning'] },
{ label: 'Filled', variants: ['filled', 'filled-primary', 'filled-accent', 'filled-warning'] },
{ label: 'Ring', variants: ['ring', 'ring-primary', 'ring-accent', 'ring-warning'] },
{ label: 'Ghost', variants: ['ghost', 'ghost-primary', 'ghost-accent', 'ghost-warning'] }
];
// Props & Slots
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
['variant', 'string', '-', '(see above)', 'Provides preset prop values. Overwrites all props but width, rounded, and href.'],
['size', 'string', 'base', 'none | sm | base | lg | xl', 'Scales the button to various sizes.'],
['background', 'string', 'bg-black dark:bg-white', 'class', 'Provide a class to define background.'],
['color', 'string', 'text-white dark:text-black', 'class', 'Provide a class to define text color.'],
['fill', 'string', 'fill-white dark:fill-black', 'class', 'Provide a class to define SVG fill color.'],
['ring', 'string', 'ring-transparent', 'class', 'Provide a class to define ring color.'],
['weight', 'string', 'ring-1', 'class', 'Provide a class to define ring weight.'],
['width', 'string', 'w-auto', 'class', 'Provide a class to set the button width.'],
['rounded', 'string', 'rounded-lg', 'class', 'Provide a class to define border radius.'],
['href', 'string', '-', 'link', 'Converts to an anchor element and sets click through value.'],
],
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['lead', 'A leading slot position left of the content, which can be used for icons.'],
['trail', 'A leading slot position right of the content, which can be used for icons.'],
],
};
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
[
'variant',
'string',
'-',
'(see above)',
'Provides preset prop values. Overwrites all props but width, rounded, and href.'
],
[
'size',
'string',
'base',
'none | sm | base | lg | xl',
'Scales the button to various sizes.'
],
[
'background',
'string',
'bg-black dark:bg-white',
'class',
'Provide a class to define background.'
],
[
'color',
'string',
'text-white dark:text-black',
'class',
'Provide a class to define text color.'
],
[
'fill',
'string',
'fill-white dark:fill-black',
'class',
'Provide a class to define SVG fill color.'
],
['ring', 'string', 'ring-transparent', 'class', 'Provide a class to define ring color.'],
['weight', 'string', 'ring-1', 'class', 'Provide a class to define ring weight.'],
['width', 'string', 'w-auto', 'class', 'Provide a class to set the button width.'],
['rounded', 'string', 'rounded-lg', 'class', 'Provide a class to define border radius.'],
['href', 'string', '-', 'link', 'Converts to an anchor element and sets click through value.']
]
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['lead', 'A leading slot position left of the content, which can be used for icons.'],
['trail', 'A leading slot position right of the content, which can be used for icons.']
]
};
const tableA11y: any = {
headings: ['Prop', 'Required', 'Description'],
source: [
['label', '-', `A semantic ARIA label.`],
['describedby', '-', `Provide the ID of the element describing the button.`],
],
};
headings: ['Prop', 'Required', 'Description'],
source: [
['label', '-', `A semantic ARIA label.`],
['describedby', '-', `Provide the ID of the element describing the button.`]
]
};
// Interactive Example Props
$:props = {
$: props = {
size: 'base',
background: 'bg-primary-500',
color: 'text-white',
@@ -64,12 +97,11 @@
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Buttons</h1>
<p>Buttons allow users to take actions and make choices with a single tap.</p>
<CodeBlock language="js" code={`import { Button } from '@brainandbones/skeleton';`}></CodeBlock>
<CodeBlock language="js" code={`import { Button } from '@brainandbones/skeleton';`} />
</header>
<!-- Sandbox -->
@@ -94,7 +126,7 @@
<svelte:fragment slot="lead">{@html svgIconSkull}</svelte:fragment>
Skeleton
</svelte:component>
</Card>
</Card>
<!-- Options -->
<Card>
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-4">
@@ -208,57 +240,62 @@
Skeleton
</Button>
`.trim()}
></CodeBlock>
/>
</section>
<!-- Variants -->
<section class="space-y-4">
<h3>Variants</h3>
<p>Skeleton provides variants to quickly and easily create buttons with predefined styles. You may still set rounded and width properties with variants.</p>
<p>
Skeleton provides variants to quickly and easily create buttons with predefined styles. You
may still set rounded and width properties with variants.
</p>
<Card>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{#each variantExamples as ve}
<section>
<h3 class="text-center mb-4">{ve.label}</h3>
<div class="flex flex-col space-y-4">
{#each ve.variants as v}
<Button variant={v}>
<svelte:fragment slot="lead">{@html svgIconSkull}</svelte:fragment>
{v}
</Button>
{/each}
</div>
</section>
<section>
<h3 class="text-center mb-4">{ve.label}</h3>
<div class="flex flex-col space-y-4">
{#each ve.variants as v}
<Button variant={v}>
<svelte:fragment slot="lead">{@html svgIconSkull}</svelte:fragment>
{v}
</Button>
{/each}
</div>
</section>
{/each}
</div>
</Card>
<CodeBlock language="html" code={`
<CodeBlock
language="html"
code={`
<Button variant="text">Skeleton</Button>
<Button variant="filled-primary">Skeleton</Button>
<Button variant="ring-accent">Skeleton</Button>
<Button variant="ghost-warning">Skeleton</Button>
`.trim()}></CodeBlock>
`.trim()}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
<!-- Accessibility -->
<section class="space-y-4">
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/button/" target="_blank">ARIA Guidelines</a>
</div>
<DataTable headings="{tableA11y.headings}" source="{tableA11y.source}"></DataTable>
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/button/" target="_blank">ARIA Guidelines</a>
</div>
<DataTable headings={tableA11y.headings} source={tableA11y.source} />
</section>
</div>

View File

@@ -1,90 +1,118 @@
<script lang="ts">
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from "$lib/Table/DataTable.svelte";
import Avatar from "$lib/Avatar/Avatar.svelte";
import Card from "$lib/Card/Card.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import Avatar from '$lib/Avatar/Avatar.svelte';
import Card from '$lib/Card/Card.svelte';
// Props and Slots
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
['background', 'string', 'bg-surface-200 dark:bg-surface-800', 'Provided a class to set background color.'],
['color', 'string', '-', 'Provide a class to set text color.'],
],
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['header', 'Provide header content, such as an image.'],
['footer', 'Provide footer content, such as a byline.'],
],
};
// Props and Slots
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
[
'background',
'string',
'bg-surface-200 dark:bg-surface-800',
'Provided a class to set background color.'
],
['color', 'string', '-', 'Provide a class to set text color.']
]
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['header', 'Provide header content, such as an image.'],
['footer', 'Provide footer content, such as a byline.']
]
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Cards</h1>
<p>Container elements that wrap and separate your content.</p>
<CodeBlock language="javascript" code={`import { Card } from '@brainandbones/skeleton';`} />
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Cards</h1>
<p>Container elements that wrap and separate your content.</p>
<CodeBlock language="javascript" code={`import { Card } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Examples -->
<section class="space-y-4">
<!-- Colors -->
<div class="grid md:grid-cols-3 gap-4">
<Card
background="bg-primary-500"
color="text-white"
class="flex-1 flex justify-center items-center min-h-[100px]">Primary</Card
>
<Card
background="bg-accent-500"
color="text-white"
class="flex-1 flex justify-center items-center min-h-[100px]">Accent</Card
>
<Card
background="bg-warning-500"
color="text-white"
class="flex-1 flex justify-center items-center min-h-[100px]">Warning</Card
>
</div>
<!-- Columns -->
<div class="grid md:grid-cols-2 gap-4">
<!-- Minimal -->
<Card class="flex justify-center items-center min-h-[100px]">Minimal</Card>
<!-- Detailed -->
<Card
class="cursor-pointer transition-transform hover:-translate-y-1 hover:shadow-xl overflow-hidden"
>
<svelte:fragment slot="header">
<div class="-mt-4 -mx-4">
<img
src="https://source.unsplash.com/random/1280x540?skeleton"
class="bg-black/50 w-full aspect-[21/9]"
alt="Post"
/>
</div>
</svelte:fragment>
<div class="space-y-4">
<h6 class="text-primary-500">Announcements</h6>
<h3>Welcome to Skeleton</h3>
<article class="text-surface-400">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam aspernatur provident
eveniet eligendi cumque consequatur tempore sint nisi sapiente. Iste beatae laboriosam
iure molestias cum expedita architecto itaque quae rem.
</article>
</div>
<svelte:fragment slot="footer">
<div class="flex justify-start items-center space-x-4">
<Avatar src="https://i.pravatar.cc/160?img=5" size="sm" outlined />
<div>
<h6 class="font-bold">By Alex</h6>
<p>On {new Date().toLocaleDateString()}</p>
</div>
</div>
</svelte:fragment>
</Card>
</div>
</section>
<!-- Examples -->
<section class="space-y-4">
<!-- Colors -->
<div class="grid md:grid-cols-3 gap-4">
<Card background="bg-primary-500" color="text-white" class="flex-1 flex justify-center items-center min-h-[100px]">Primary</Card>
<Card background="bg-accent-500" color="text-white" class="flex-1 flex justify-center items-center min-h-[100px]">Accent</Card>
<Card background="bg-warning-500" color="text-white" class="flex-1 flex justify-center items-center min-h-[100px]">Warning</Card>
</div>
<!-- Columns -->
<div class="grid md:grid-cols-2 gap-4">
<!-- Minimal -->
<Card class="flex justify-center items-center min-h-[100px]">Minimal</Card>
<!-- Detailed -->
<Card class="cursor-pointer transition-transform hover:-translate-y-1 hover:shadow-xl overflow-hidden">
<svelte:fragment slot="header">
<div class="-mt-4 -mx-4">
<img src="https://source.unsplash.com/random/1280x540?skeleton" class="bg-black/50 w-full aspect-[21/9]" alt="Post">
</div>
</svelte:fragment>
<div class="space-y-4">
<h6 class="text-primary-500">Announcements</h6>
<h3>Welcome to Skeleton</h3>
<article class="text-surface-400">Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam aspernatur provident eveniet eligendi cumque consequatur tempore sint nisi sapiente. Iste beatae laboriosam iure molestias cum expedita architecto itaque quae rem.</article>
</div>
<svelte:fragment slot="footer">
<div class="flex justify-start items-center space-x-4">
<Avatar src="https://i.pravatar.cc/160?img=5" size="sm" outlined />
<div>
<h6 class="font-bold">By Alex</h6>
<p>On {new Date().toLocaleDateString()}</p>
</div>
</div>
</svelte:fragment>
</Card>
</div>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<Card>Minimal</Card>`} />
<CodeBlock
language="html"
code={`<Card background="bg-primary-500" color="text-white">Colored</Card>`}
/>
<CodeBlock language="html" code={`<Card class="hover:shadow-xl">Styled</Card>`} />
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<CodeBlock language="html" code={`<Card>Minimal</Card>`}></CodeBlock>
<CodeBlock language="html" code={`<Card background="bg-primary-500" color="text-white">Colored</Card>`}></CodeBlock>
<CodeBlock language="html" code={`<Card class="hover:shadow-xl">Styled</Card>`}></CodeBlock>
</section>
<!-- Properties -->
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
</div>

View File

@@ -1,103 +1,119 @@
<script lang="ts">
import Card from "$lib/Card/Card.svelte";
import CodeBlock from "$lib/CodeBlock/CodeBlock.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import ConicGradient from "$lib/ConicGradient/ConicGradient.svelte";
import Card from '$lib/Card/Card.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import ConicGradient from '$lib/ConicGradient/ConicGradient.svelte';
// Examples
const dataOne: any[] = [
{label: 'Red', swathe: {color: 'red', weight: 500}, start: 0, end: 35},
{label: 'Green', swathe: {color: 'green', weight: 500}, start: 35, end: 60},
{label: 'Blue', swathe: {color: 'blue', weight: 500}, start: 60, end: 100},
];
let dataTwo: any[] = [
{label: 'Orange', swathe: {color: 'orange', weight: 500}, start: 0, end: 10},
{label: 'Yellow', swathe: {color: 'yellow', weight: 500}, start: 10, end: 35},
{label: 'Red', swathe: {color: 'red', weight: 500}, start: 35, end: 100},
];
const dataThree: any[] = [
{swathe: {color: 'transparent'}, start: 0, end: 25},
{swathe: {color: 'white', weight: 500}, start: 75, end: 100},
];
// Examples
const dataOne: any[] = [
{ label: 'Red', swathe: { color: 'red', weight: 500 }, start: 0, end: 35 },
{ label: 'Green', swathe: { color: 'green', weight: 500 }, start: 35, end: 60 },
{ label: 'Blue', swathe: { color: 'blue', weight: 500 }, start: 60, end: 100 }
];
let dataTwo: any[] = [
{ label: 'Orange', swathe: { color: 'orange', weight: 500 }, start: 0, end: 10 },
{ label: 'Yellow', swathe: { color: 'yellow', weight: 500 }, start: 10, end: 35 },
{ label: 'Red', swathe: { color: 'red', weight: 500 }, start: 35, end: 100 }
];
const dataThree: any[] = [
{ swathe: { color: 'transparent' }, start: 0, end: 25 },
{ swathe: { color: 'white', weight: 500 }, start: 75, end: 100 }
];
// Props & Slots
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['data', 'any[]', '(filled 100%)', 'true', 'Provide your data set.'],
['legend', 'boolean', 'false', 'false', 'Enables a simple pie chart legend.'],
['width', 'string', 'w-full', 'false', 'Provided a class to define the width.'],
],
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['default', 'Allows you to define a label, description, or other supplementary information.'],
],
};
// Props & Slots
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
['data', 'any[]', '(filled 100%)', 'true', 'Provide your data set.'],
['legend', 'boolean', 'false', 'false', 'Enables a simple pie chart legend.'],
['width', 'string', 'w-full', 'false', 'Provided a class to define the width.']
]
};
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
['default', 'Allows you to define a label, description, or other supplementary information.']
]
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Conic Gradient</h1>
<p>Create conic gradient visualizations for pie charts, loading spinners, and more.</p>
<CodeBlock
language="javascript"
code={`import { ConicGradient } from '@brainandbones/skeleton';`}
/>
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Conic Gradient</h1>
<p>Create conic gradient visualizations for pie charts, loading spinners, and more.</p>
<CodeBlock language="javascript" code={`import { ConicGradient } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Examples -->
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-3 md:gap-8">
<Card><ConicGradient data={dataOne} legend={true} /></Card>
<Card>
<ConicGradient data={dataTwo} legend={true}>
<h3>Heat Map</h3>
</ConicGradient>
</Card>
<Card class="flex justify-center items-center">
<ConicGradient data={dataThree} width="w-8" class="animate-spin">
<small class="opacity-50">Loading</small>
</ConicGradient>
</Card>
</section>
<!-- Examples -->
<section class="space-y-4 md:space-y-0 md:grid md:grid-cols-3 md:gap-8">
<Card><ConicGradient data={dataOne} legend={true}></ConicGradient></Card>
<Card>
<ConicGradient data={dataTwo} legend={true}>
<h3>Heat Map</h3>
</ConicGradient>
</Card>
<Card class="flex justify-center items-center">
<ConicGradient data={dataThree} width="w-8" class="animate-spin">
<small class="opacity-50">Loading</small>
</ConicGradient>
</Card>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<p>Please note that only default Tailwind color values are currently supported, with the exception of 'white', 'black', and 'transparent'. Weight is optional for these three values.</p>
<h3>Pie Chart</h3>
<CodeBlock language="html" code={`<ConicGradient data={dataSet} legend={true}></ConicGradient>`}></CodeBlock>
<CodeBlock language="js" code={`
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<p>
Please note that only default Tailwind color values are currently supported, with the
exception of 'white', 'black', and 'transparent'. Weight is optional for these three values.
</p>
<h3>Pie Chart</h3>
<CodeBlock
language="html"
code={`<ConicGradient data={dataSet} legend={true}></ConicGradient>`}
/>
<CodeBlock
language="js"
code={`
const dataSet: any[] = [
{label: 'Emerald', swathe: {color: 'emerald', weight: 500}, start: 0, end: 35},
{label: 'Indigo', swathe: {color: 'indigo', weight: 500}, start: 35, end: 60},
{label: 'Rose', swathe: {color: 'rose', weight: 500}, start: 60, end: 100},
];
`.trim()}></CodeBlock>
<h3>Spinner</h3>
<CodeBlock language="html" code={`
`.trim()}
/>
<h3>Spinner</h3>
<CodeBlock
language="html"
code={`
<ConicGradient data={dataSet} width="w-8" class="animate-spin">
<small>Loading</small>
</ConicGradient>
`.trim()}></CodeBlock>
<CodeBlock language="js" code={`
`.trim()}
/>
<CodeBlock
language="js"
code={`
const dataSet: any[] = [
{swathe: {color: 'transparent'}, start: 0, end: 25},
{swathe: {color: 'slate', weight: 500}, start: 75, end: 100},
];
`.trim()}></CodeBlock>
</section>
`.trim()}
/>
</section>
<!-- Properties -->
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings="{tableSlots.headings}" source="{tableSlots.source}"></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
</div>
</div>

View File

@@ -1,184 +1,297 @@
<script lang="ts">
import { mapTableSource } from '$lib/Table/DataTableService';
import { writable, type Writable } from 'svelte/store';
import { mapTableSource } from '$lib/Table/DataTableService';
import { writable, type Writable } from 'svelte/store';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Card from "$lib/Card/Card.svelte";
import TabGroup from '$lib/Tab/TabGroup.svelte';
import Tab from '$lib/Tab/Tab.svelte';
import DataTable from "$lib/Table/DataTable.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Card from '$lib/Card/Card.svelte';
import TabGroup from '$lib/Tab/TabGroup.svelte';
import Tab from '$lib/Tab/Tab.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
let tabExample: Writable<string> = writable('local');
let tabExample: Writable<string> = writable('local');
// Local Table
const staticArr: any = [
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
];
const tableLocal: any = {
search: undefined,
sort: 'position',
headings: ['Positions', 'Name', 'Weight', 'Symbol'],
source: mapTableSource(['position', 'name', 'weight', 'symbol'], staticArr)
};
// Local Table
const staticArr: any = [
{ position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
{ position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be' },
{ position: 5, name: 'Boron', weight: 10.811, symbol: 'B' },
{ position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C' },
{ position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N' },
{ position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O' },
{ position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F' },
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' }
];
const tableLocal: any = {
search: undefined,
sort: 'position',
headings: ['Positions', 'Name', 'Weight', 'Symbol'],
source: mapTableSource(['position', 'name', 'weight', 'symbol'], staticArr)
};
// Server Table
const tableServer: any = { search: undefined, sort: undefined, headings: undefined, count: 0 };
async function getTableSource(): Promise<any> {
// Server Table
const tableServer: any = { search: undefined, sort: undefined, headings: undefined, count: 0 };
async function getTableSource(): Promise<any> {
const http = await fetch('https://jsonplaceholder.typicode.com/user/1/posts');
const res = await http.json();
tableServer.headings = Object.keys(res[0]);
tableServer.sort = 'userId';
if (http.ok) { return res; } else { throw new Error(res); }
tableServer.headings = Object.keys(res[0]);
tableServer.sort = 'userId';
if (http.ok) {
return res;
} else {
throw new Error(res);
}
}
let tablePromise: Promise<any> = getTableSource();
let tablePromise: Promise<any> = getTableSource();
// Selections
function onSort(event: any): void { console.log('event:onSort', event.detail); }
function onSelect(event: any): void { console.log('event:onSelect', event.detail); }
// Selections
function onSort(event: any): void {
console.log('event:onSort', event.detail);
}
function onSelect(event: any): void {
console.log('event:onSelect', event.detail);
}
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
{prop: 'headings', type: 'string[]', default: '[]', req: '&check;', desc: 'Provide a list of table headings.'},
{prop: 'source', type: 'any[]', default: '[]', req: '&check;', desc: 'Provide the table body content.'},
{prop: 'async', type: 'boolean', default: 'false', req: '-', desc: 'Disables search/sort within the component, allowing for server-side pagination.'},
{prop: 'search', type: 'any', default: '-', req: '-', desc: 'Provide a term for local fuzzy search within the compoonent.'},
{prop: 'sort', type: 'string', default: '-', req: '-', desc: 'Defines the sort key value.'},
{prop: 'count', type: 'number', default: '(source length)', req: '-', desc: 'When using async mode, use this to get a count of rows.'},
{prop: 'interactive', type: 'boolean', default: 'false', req: '-', desc: 'Enables row hover and selection features.'},
],
}
const tablePropStyles: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
{prop: 'header', type: 'string', default: 'bg-surface-50 dark:bg-surface-700', desc: 'Provide a class to set the table header background color.'},
{prop: 'body', type: 'string', default: 'bg-surface-200 dark:bg-surface-800', desc: 'Provide a class to set the table body background color.'},
{prop: 'text', type: 'string', default: 'text-sm', desc: 'Provide a class to set the table text size.'},
{prop: 'hover', type: 'string', default: 'hover:bg-primary-500/10', desc: 'Provide a class to set the hover background color.'},
],
}
// Props
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Required', 'Description'],
source: [
{
prop: 'headings',
type: 'string[]',
default: '[]',
req: '&check;',
desc: 'Provide a list of table headings.'
},
{
prop: 'source',
type: 'any[]',
default: '[]',
req: '&check;',
desc: 'Provide the table body content.'
},
{
prop: 'async',
type: 'boolean',
default: 'false',
req: '-',
desc: 'Disables search/sort within the component, allowing for server-side pagination.'
},
{
prop: 'search',
type: 'any',
default: '-',
req: '-',
desc: 'Provide a term for local fuzzy search within the compoonent.'
},
{ prop: 'sort', type: 'string', default: '-', req: '-', desc: 'Defines the sort key value.' },
{
prop: 'count',
type: 'number',
default: '(source length)',
req: '-',
desc: 'When using async mode, use this to get a count of rows.'
},
{
prop: 'interactive',
type: 'boolean',
default: 'false',
req: '-',
desc: 'Enables row hover and selection features.'
}
]
};
const tablePropStyles: any = {
headings: ['Prop', 'Type', 'Default', 'Description'],
source: [
{
prop: 'header',
type: 'string',
default: 'bg-surface-50 dark:bg-surface-700',
desc: 'Provide a class to set the table header background color.'
},
{
prop: 'body',
type: 'string',
default: 'bg-surface-200 dark:bg-surface-800',
desc: 'Provide a class to set the table body background color.'
},
{
prop: 'text',
type: 'string',
default: 'text-sm',
desc: 'Provide a class to set the table text size.'
},
{
prop: 'hover',
type: 'string',
default: 'hover:bg-primary-500/10',
desc: 'Provide a class to set the hover background color.'
}
]
};
// Events
const tableEvents: any = {
headings: ['Name', 'Description'],
source: [
{name: 'sorted', desc: 'Fires when a table heading is selected for sorting. Contains a key name reference.'},
{name: 'selected', desc: 'If interactive enabled, fires when a row is selected. Contains the complete row data.'},
],
}
// Events
const tableEvents: any = {
headings: ['Name', 'Description'],
source: [
{
name: 'sorted',
desc: 'Fires when a table heading is selected for sorting. Contains a key name reference.'
},
{
name: 'selected',
desc: 'If interactive enabled, fires when a row is selected. Contains the complete row data.'
}
]
};
// Slots
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
{name: 'header', desc: 'Dislays above the table. Useful for embedding search and filter inputs.'},
{name: 'empty', desc: 'Overrides the default "no results found" message when the table is empty.'},
{name: 'footer', desc: 'Displays below the table. Useful for embedding pagination.'},
],
}
// Slots
const tableSlots: any = {
headings: ['Name', 'Description'],
source: [
{
name: 'header',
desc: 'Dislays above the table. Useful for embedding search and filter inputs.'
},
{
name: 'empty',
desc: 'Overrides the default "no results found" message when the table is empty.'
},
{ name: 'footer', desc: 'Displays below the table. Useful for embedding pagination.' }
]
};
// A11y
const tableA11y: any = {
headings: ['Prop', 'Required', 'Description'],
source: [
['labelledby', '-', `Provide the ID of the element that labels the table.`],
['describedby', '-', `Provide the ID of the element that describes the table.`],
],
};
// A11y
const tableA11y: any = {
headings: ['Prop', 'Required', 'Description'],
source: [
['labelledby', '-', `Provide the ID of the element that labels the table.`],
['describedby', '-', `Provide the ID of the element that describes the table.`]
]
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Data Tables</h1>
<p>Interactive table with support for search, sort, and pagination.</p>
<CodeBlock
language="javascript"
code={`import { DataTable } from '@brainandbones/skeleton';`}
/>
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Data Tables</h1>
<p>Interactive table with support for search, sort, and pagination.</p>
<CodeBlock language="javascript" code={`import { DataTable } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Examples -->
<section class="space-y-4">
<TabGroup selected={tabExample}>
<Tab value="local">Local</Tab>
<Tab value="async">Async</Tab>
</TabGroup>
{#if $tabExample === 'local'}<p>Render a table using data fully available client-side.</p>{/if}
{#if $tabExample === 'async'}<p>Render a table using asycronous data, such as an HTTP call to an API. The example below fetches data from <a href="https://jsonplaceholder.typicode.com/" target="_blank">JSON Placeholder</a></p>{/if}
<Card class="space-y-4">
<!-- Tab: Local -->
{#if $tabExample === 'local'}
<DataTable
headings={tableLocal.headings}
bind:source={tableLocal.source}
search={tableLocal.search}
sort={tableLocal.sort}
interactive
on:sorted={onSort}
on:selected={onSelect}
>
<svelte:fragment slot="header"><input type="search" placeholder="Search..." bind:value={tableLocal.search}></svelte:fragment>
<svelte:fragment slot="footer"><pre class="text-center">Count: {tableLocal.source.length} Items</pre></svelte:fragment>
</DataTable>
{/if}
<!-- Tab: Async -->
{#if $tabExample === 'async'}
{#await tablePromise}
<p class="text-center">Loading...</p>
{:then response}
<DataTable
headings={tableServer.headings}
source={response}
search={tableServer.search}
sort={tableServer.sort}
bind:count={tableServer.count}
interactive
on:sorted={onSort}
on:selected={onSelect}
>
<svelte:fragment slot="header"><input type="search" placeholder="Search..." bind:value={tableServer.search}></svelte:fragment>
<svelte:fragment slot="footer"><pre class="text-center">Count: {tableServer.count} Posts</pre></svelte:fragment>
</DataTable>
{:catch error}
<p style="text-center text-warning-500">{error.message}</p>
{/await}
{/if}
</Card>
</section>
<!-- Examples -->
<section class="space-y-4">
<TabGroup selected={tabExample}>
<Tab value="local">Local</Tab>
<Tab value="async">Async</Tab>
</TabGroup>
{#if $tabExample === 'local'}<p>Render a table using data fully available client-side.</p>{/if}
{#if $tabExample === 'async'}<p>
Render a table using asycronous data, such as an HTTP call to an API. The example below
fetches data from <a href="https://jsonplaceholder.typicode.com/" target="_blank"
>JSON Placeholder</a
>
</p>{/if}
<Card class="space-y-4">
<!-- Tab: Local -->
{#if $tabExample === 'local'}
<DataTable
headings={tableLocal.headings}
bind:source={tableLocal.source}
search={tableLocal.search}
sort={tableLocal.sort}
interactive
on:sorted={onSort}
on:selected={onSelect}
>
<svelte:fragment slot="header"
><input
type="search"
placeholder="Search..."
bind:value={tableLocal.search}
/></svelte:fragment
>
<svelte:fragment slot="footer"
><pre class="text-center">Count: {tableLocal.source.length} Items</pre></svelte:fragment
>
</DataTable>
{/if}
<!-- Tab: Async -->
{#if $tabExample === 'async'}
{#await tablePromise}
<p class="text-center">Loading...</p>
{:then response}
<DataTable
headings={tableServer.headings}
source={response}
search={tableServer.search}
sort={tableServer.sort}
bind:count={tableServer.count}
interactive
on:sorted={onSort}
on:selected={onSelect}
>
<svelte:fragment slot="header"
><input
type="search"
placeholder="Search..."
bind:value={tableServer.search}
/></svelte:fragment
>
<svelte:fragment slot="footer"
><pre class="text-center">Count: {tableServer.count} Posts</pre></svelte:fragment
>
</DataTable>
{:catch error}
<p style="text-center text-warning-500">{error.message}</p>
{/await}
{/if}
</Card>
</section>
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<TabGroup selected={tabExample}>
<Tab value="local">Local</Tab>
<Tab value="async">Async</Tab>
</TabGroup>
<!-- Tab: Local -->
{#if $tabExample === 'local'}
<p>Ensure your heading and source keys are defined in the same order left-to-right. Note that source values support stringified HTML.</p>
<CodeBlock language="typescript" code={`
<!-- Usage -->
<section class="space-y-4">
<h2>Usage</h2>
<TabGroup selected={tabExample}>
<Tab value="local">Local</Tab>
<Tab value="async">Async</Tab>
</TabGroup>
<!-- Tab: Local -->
{#if $tabExample === 'local'}
<p>
Ensure your heading and source keys are defined in the same order left-to-right. Note that
source values support stringified HTML.
</p>
<CodeBlock
language="typescript"
code={`
const headings: string[] = ['Positions', 'Name', 'Weight', 'Symbol'];
const source: any[] = [
{ position: 1, name: '<strong class="text-red">Hydrogen</strong>', weight: 1.0079, symbol: 'H' },
{ position: 2, name: 'Helium', weight: 4.0026, symbol: 'He' },
{ position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li' },
];
`.trim()}></CodeBlock>
<CodeBlock language="html" code={`
`.trim()}
/>
<CodeBlock
language="html"
code={`
<DataTable {headings} {source}></DataTable>
`.trim()}></CodeBlock>
<h3>Fully Featured</h3>
<p>The example below includes search, sort, and item count. Note that source is binding to provide item count.</p>
<CodeBlock language="typescript" code={`
`.trim()}
/>
<h3>Fully Featured</h3>
<p>
The example below includes search, sort, and item count. Note that source is binding to
provide item count.
</p>
<CodeBlock
language="typescript"
code={`
const tableLocal: any = {
search: undefined,
sort: 'position',
@@ -196,8 +309,11 @@ const tableLocal: any = {
{ position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne' },
]
};
`.trim()}></CodeBlock>
<CodeBlock language="html" code={`
`.trim()}
/>
<CodeBlock
language="html"
code={`
<DataTable
headings={tableLocal.headings}
bind:source={tableLocal.source}
@@ -210,16 +326,22 @@ const tableLocal: any = {
<svelte:fragment slot="header"><input type="search" placeholder="Search..." bind:value={tableLocal.search}></svelte:fragment>
<svelte:fragment slot="footer">{tableLocal.source.length} Items</svelte:fragment>
</DataTable>
`.trim()}></CodeBlock>
{/if}
<!-- Tab: Async -->
{#if $tabExample === 'async'}
<p>Scaffold the table data similar to a local table.</p>
<CodeBlock language="typescript" code={`
`.trim()}
/>
{/if}
<!-- Tab: Async -->
{#if $tabExample === 'async'}
<p>Scaffold the table data similar to a local table.</p>
<CodeBlock
language="typescript"
code={`
const tableServer: any = { search: undefined, sort: undefined, headings: undefined, count: 0 };
`.trim()}></CodeBlock>
<p>Fetch API data from a server, then map headings and the default sort value.</p>
<CodeBlock language="typescript" code={`
`.trim()}
/>
<p>Fetch API data from a server, then map headings and the default sort value.</p>
<CodeBlock
language="typescript"
code={`
async function getTableSource(): Promise<any> {
const http = await fetch('https://jsonplaceholder.typicode.com/user/1/posts');
const res = await http.json();
@@ -228,9 +350,15 @@ async function getTableSource(): Promise<any> {
if (http.ok) { return res; } else { throw new Error(res); }
}
let tablePromise: Promise<any> = getTableSource();
`.trim()}></CodeBlock>
<p>Use Svelte await blocks to handle loading, complete, and error states. Please ensure you bind 'count' to handle item count.</p>
<CodeBlock language="html" code={`
`.trim()}
/>
<p>
Use Svelte await blocks to handle loading, complete, and error states. Please ensure you
bind 'count' to handle item count.
</p>
<CodeBlock
language="html"
code={`
{#await tablePromise}
<p class="text-center">Loading...</p>
{:then response}
@@ -250,9 +378,15 @@ let tablePromise: Promise<any> = getTableSource();
{:catch error}
<p style="text-center text-warning-500">{error.message}</p>
{/await}
`.trim()}></CodeBlock>
<p>If you prefer to use server-side search and sort, enable the 'async' property. This disables local search and sort within the component.</p>
<CodeBlock language="html" code={`
`.trim()}
/>
<p>
If you prefer to use server-side search and sort, enable the 'async' property. This disables
local search and sort within the component.
</p>
<CodeBlock
language="html"
code={`
<!-- (await/then) -->
<DataTable
headings={tableServer.headings}
@@ -261,44 +395,50 @@ let tablePromise: Promise<any> = getTableSource();
async
></DataTable>
<!-- (error) -->
`.trim()}></CodeBlock>
{/if}
<p>Handle events for sort and row selection. These are enabled for the demos at the top of the page. View your browser's console log during interaction.</p>
<CodeBlock language="typescript" code={`
`.trim()}
/>
{/if}
<p>
Handle events for sort and row selection. These are enabled for the demos at the top of the
page. View your browser's console log during interaction.
</p>
<CodeBlock
language="typescript"
code={`
function onSort(event: any): void { console.log('event:onSort', event.detail); }
function onSelect(event: any): void { console.log('event:onSelect', event.detail); }
`.trim()}></CodeBlock>
</section>
`.trim()}
/>
</section>
<!-- Extras -->
<!-- Extras -->
<!-- TODO: document the table service -->
<!-- Properties -->
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings={tableProps.headings} source={tableProps.source} ></DataTable>
<DataTable headings={tablePropStyles.headings} source={tablePropStyles.source} ></DataTable>
<DataTable headings={tableProps.headings} source={tableProps.source} />
<DataTable headings={tablePropStyles.headings} source={tablePropStyles.source} />
</section>
<!-- Events -->
<!-- Events -->
<section class="space-y-4">
<h2>Events</h2>
<DataTable headings={tableEvents.headings} source={tableEvents.source} ></DataTable>
<DataTable headings={tableEvents.headings} source={tableEvents.source} />
</section>
<!-- Slots -->
<!-- Slots -->
<section class="space-y-4">
<h2>Slots</h2>
<DataTable headings={tableSlots.headings} source={tableSlots.source} ></DataTable>
<DataTable headings={tableSlots.headings} source={tableSlots.source} />
</section>
<!-- Accessibility -->
<!-- Accessibility -->
<section class="space-y-4">
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/grid/" target="_blank">ARIA Guidelines</a>
</div>
<DataTable headings="{tableA11y.headings}" source="{tableA11y.source}"></DataTable>
<div class="flex justify-between items-center">
<h2>Accessibility</h2>
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/grid/" target="_blank">ARIA Guidelines</a>
</div>
<DataTable headings={tableA11y.headings} source={tableA11y.source} />
</section>
</div>

View File

@@ -1,100 +1,125 @@
<script lang="ts">
import { writable, type Writable } from "svelte/store";
import { writable, type Writable } from 'svelte/store';
import Card from "$lib/Card/Card.svelte";
import DataTable from "$lib/Table/DataTable.svelte";
import RadioGroup from "$lib/Radio/RadioGroup.svelte";
import RadioItem from "$lib/Radio/RadioItem.svelte";
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Divider from "$lib/Divider/Divider.svelte";
import Card from '$lib/Card/Card.svelte';
import DataTable from '$lib/Table/DataTable.svelte';
import RadioGroup from '$lib/Radio/RadioGroup.svelte';
import RadioItem from '$lib/Radio/RadioItem.svelte';
import CodeBlock from '$lib/CodeBlock/CodeBlock.svelte';
import Divider from '$lib/Divider/Divider.svelte';
const storeVariant: Writable<string> = writable('solid');
const storeWeight: Writable<number> = writable(2);
const storeOrientation: Writable<string> = writable('h');
const storeVariant: Writable<string> = writable('solid');
const storeWeight: Writable<number> = writable(2);
const storeOrientation: Writable<string> = writable('h');
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
['variant', 'string', 'solid', 'solid | dashed | dotted', 'Defines the visual display styling.'],
['weight', 'number', '1', '1 | 2 | 4 | 8', 'Defines the thickness of the divider.'],
['orientation', 'string', 'h', 'h | v', 'Switches between horizontal/vertical layout.'],
],
};
const tableProps: any = {
headings: ['Prop', 'Type', 'Default', 'Values', 'Description'],
source: [
[
'variant',
'string',
'solid',
'solid | dashed | dotted',
'Defines the visual display styling.'
],
['weight', 'number', '1', '1 | 2 | 4 | 8', 'Defines the thickness of the divider.'],
['orientation', 'string', 'h', 'h | v', 'Switches between horizontal/vertical layout.']
]
};
$: props = {
variant: $storeVariant,
weight: $storeWeight,
orientation: $storeOrientation
};
$: props = {
variant: $storeVariant,
weight: $storeWeight,
orientation: $storeOrientation
};
</script>
<div class="space-y-8">
<!-- Header -->
<header class="space-y-4">
<h1>Dividers</h1>
<p>Horizontal or vertical rules for sectioning your content.</p>
<CodeBlock language="javascript" code={`import { Divider } from '@brainandbones/skeleton';`} />
</header>
<!-- Header -->
<header class="space-y-4">
<h1>Dividers</h1>
<p>Horizontal or vertical rules for sectioning your content.</p>
<CodeBlock language="javascript" code={`import { Divider } from '@brainandbones/skeleton';`}></CodeBlock>
</header>
<!-- Sandbox -->
<section class="space-y-4">
<div class="space-y-4 xl:space-y-0 xl:grid grid-cols-[2fr,1fr] gap-2">
<!-- Example -->
<!-- Sandbox -->
<section class="space-y-4">
<div class="space-y-4 xl:space-y-0 xl:grid grid-cols-[2fr,1fr] gap-2">
<!-- Example -->
<Card class="space-y-4 flex justify-center items-center">
<div class="w-[75%] h-[100px] flex justify-evenly items-center">
<svelte:component
this={Divider}
variant={props.variant}
weight={props.weight}
orientation={props.orientation}
/>
</div>
</Card>
<!-- Options -->
<Card class="space-y-4">
<!-- Variant -->
<div>
<legend>Variant</legend>
<RadioGroup selected={storeVariant} background="bg-accent-500" color="text-white" width="w-full">
<RadioItem value="solid">Solid</RadioItem>
<RadioItem value="dashed">Dashed</RadioItem>
<RadioItem value="dotted">Dotted</RadioItem>
</RadioGroup>
</div>
<!-- Weight -->
<div>
<legend>Weight</legend>
<RadioGroup selected={storeWeight} background="bg-accent-500" color="text-white" width="w-full">
<RadioItem value={1}>1</RadioItem>
<RadioItem value={2}>2</RadioItem>
<RadioItem value={4}>4</RadioItem>
<RadioItem value={8}>8</RadioItem>
</RadioGroup>
</div>
<!-- Orientation -->
<div>
<legend>Orientation</legend>
<RadioGroup selected={storeOrientation} background="bg-accent-500" color="text-white" width="w-full">
<RadioItem value="h">Horizontal</RadioItem>
<RadioItem value="v">Vertical</RadioItem>
</RadioGroup>
</div>
</Card>
</div>
<CodeBlock language="html" code={`<Divider variant="${props.variant}" weight={${props.weight}} orientation="${props.orientation}" />`}></CodeBlock>
</section>
<div class="w-[75%] h-[100px] flex justify-evenly items-center">
<svelte:component
this={Divider}
variant={props.variant}
weight={props.weight}
orientation={props.orientation}
/>
</div>
</Card>
<!-- Options -->
<Card class="space-y-4">
<!-- Variant -->
<div>
<legend>Variant</legend>
<RadioGroup
selected={storeVariant}
background="bg-accent-500"
color="text-white"
width="w-full"
>
<RadioItem value="solid">Solid</RadioItem>
<RadioItem value="dashed">Dashed</RadioItem>
<RadioItem value="dotted">Dotted</RadioItem>
</RadioGroup>
</div>
<!-- Weight -->
<div>
<legend>Weight</legend>
<RadioGroup
selected={storeWeight}
background="bg-accent-500"
color="text-white"
width="w-full"
>
<RadioItem value={1}>1</RadioItem>
<RadioItem value={2}>2</RadioItem>
<RadioItem value={4}>4</RadioItem>
<RadioItem value={8}>8</RadioItem>
</RadioGroup>
</div>
<!-- Orientation -->
<div>
<legend>Orientation</legend>
<RadioGroup
selected={storeOrientation}
background="bg-accent-500"
color="text-white"
width="w-full"
>
<RadioItem value="h">Horizontal</RadioItem>
<RadioItem value="v">Vertical</RadioItem>
</RadioGroup>
</div>
</Card>
</div>
<CodeBlock
language="html"
code={`<Divider variant="${props.variant}" weight={${props.weight}} orientation="${props.orientation}" />`}
/>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings="{tableProps.headings}" source="{tableProps.source}"></DataTable>
</section>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>Uses a horizontal rule <code>hr</code> tag, which has an inherit <code>role="seperator"</code>.</p>
</section>
<!-- Properties -->
<section class="space-y-4">
<h2>Properties</h2>
<DataTable headings={tableProps.headings} source={tableProps.source} />
</section>
<!-- Accessibility -->
<section class="space-y-4">
<h2>Accessibility</h2>
<p>
Uses a horizontal rule <code>hr</code> tag, which has an inherit
<code>role="seperator"</code>.
</p>
</section>
</div>

Some files were not shown because too many files have changed in this diff Show More