post format
20
README.md
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
149
src/app.css
@@ -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;
|
||||
}
|
||||
|
||||
23
src/app.html
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<script lang="ts">
|
||||
import {setContext} from 'svelte';
|
||||
|
||||
// Props
|
||||
export let separator: string = `›`;
|
||||
// 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 = `›`;
|
||||
// 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>
|
||||
|
||||
@@ -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();
|
||||
// })
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -14,7 +14,5 @@ describe('Card.svelte', () => {
|
||||
render(Card);
|
||||
});
|
||||
|
||||
it('Renders with props', async () => {
|
||||
|
||||
});
|
||||
it('Renders with props', async () => {});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
// })
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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")'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}")`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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}`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -14,5 +14,4 @@ describe('LogoCloud.svelte', () => {
|
||||
const { getByTestId } = render(LogoCloud);
|
||||
expect(getByTestId('logo-cloud')).toBeTruthy();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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 ? '✕' : '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 ? '✕' : 'Dismiss'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -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}>←</Button>
|
||||
<Button {variant} {rounded} on:click={onNext} disabled={(offset+1)*limit >= size}>→</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- Arrows -->
|
||||
<div class="space-x-2">
|
||||
<Button {variant} {rounded} on:click={onPrev} disabled={offset === 0}>←</Button>
|
||||
<Button {variant} {rounded} on:click={onNext} disabled={(offset + 1) * limit >= size}
|
||||
>→</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 ? '✓' : 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 ? '✓' : 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 ↓</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 ↓</Button>
|
||||
{:else}
|
||||
<Button variant="filled-primary" on:click={onComplete} {disabled}>Complete</Button>
|
||||
{/if}
|
||||
</nav>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 ? '↓' : '↑'}
|
||||
{/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 ? '↓' : '↑'}
|
||||
{/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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>'
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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', '✓', 'Provide the summary details of each item.'],
|
||||
['content', '✓', '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', '✓', 'Provide the summary details of each item.'],
|
||||
['content', '✓', '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>
|
||||
|
||||
@@ -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','✓', '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', '✓', '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}>✕</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}>✕</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}>✕</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}>✕</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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: '✓', desc: 'Provide a list of table headings.'},
|
||||
{prop: 'source', type: 'any[]', default: '[]', req: '✓', 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: '✓',
|
||||
desc: 'Provide a list of table headings.'
|
||||
},
|
||||
{
|
||||
prop: 'source',
|
||||
type: 'any[]',
|
||||
default: '[]',
|
||||
req: '✓',
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||