Skip to content

Commit b37c43d

Browse files
committed
add list
1 parent 86398ff commit b37c43d

File tree

17 files changed

+1027
-60
lines changed

17 files changed

+1027
-60
lines changed

src/app.postcss

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,3 @@
1-
html,
2-
body {
3-
@apply h-full overflow-hidden;
4-
}
5-
6-
/* Page Styles */
7-
/* Use these to define common page styles. */
8-
.page-container {
9-
@apply container mx-auto page-padding space-y-10;
10-
}
11-
.page-container-wide {
12-
@apply w-full max-w-7xl mx-auto space-y-10;
13-
}
14-
.page-container-aside {
15-
@apply w-full max-w-4xl mx-auto space-y-10;
16-
}
17-
.page-padding {
18-
@apply p-4 md:p-10;
19-
}
20-
.page-section {
21-
@apply space-y-4;
22-
}
23-
24-
/* Label Text Override */
25-
label:not(.unstyled) span:first-of-type:not(.unstyled):is(:not(.input-chip *)) {
26-
@apply text-surface-700-200-token text-sm;
27-
}
28-
29-
/* Heading Permalinks */
30-
.permalink {
31-
@apply w-0 invisible inline-block text-base pl-2 -translate-y-1;
32-
}
33-
h1:hover .permalink,
34-
h2:hover .permalink,
35-
h3:hover .permalink,
36-
h4:hover .permalink,
37-
h5:hover .permalink,
38-
h6:hover .permalink {
39-
@apply visible w-auto;
40-
}
1+
@import '$lib/styles/all.css';
2+
@import './styles/app.css';
3+
@import './styles//docs-list.css';
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script lang="ts">
2+
import { setContext } from 'svelte';
3+
4+
// Types
5+
import type { CssClasses } from '$lib';
6+
7+
// Props
8+
/** Enable selection of multiple items. */
9+
export let multiple: boolean = false;
10+
11+
// Props (styles)
12+
/** Provide classs to set the vertical spacing style. */
13+
export let spacing: CssClasses = 'space-y-1';
14+
/** Provide classes to set the listbox box radius styles. */
15+
export let rounded: CssClasses = 'rounded-token';
16+
17+
// Props (styles) - Item Only
18+
/** Provide classes to set the listbox item active background styles. */
19+
export let active: CssClasses = 'variant-filled';
20+
/** Provide classes to set the listbox item hover background styles. */
21+
export let hover: CssClasses = 'hover:variant-soft';
22+
/** Provide classes to set the listbox item padding styles. */
23+
export let padding: CssClasses = 'px-4 py-2';
24+
25+
// Props (a11y)
26+
/** Provide the ARIA labelledby value. */
27+
export let labelledby: string = '';
28+
29+
// Context
30+
setContext('multiple', multiple);
31+
setContext('rounded', rounded);
32+
setContext('active', active);
33+
setContext('hover', hover);
34+
setContext('padding', padding);
35+
36+
// Classes
37+
const cBase: string = 'cursor-pointer -outline-offset-[3px]';
38+
39+
// Reactive
40+
$: classesBase = `${cBase} ${spacing} ${rounded} ${$$props.class ?? ''}`;
41+
</script>
42+
43+
<div
44+
class="bcu-list {classesBase}"
45+
role="listbox"
46+
aria-labelledby={labelledby}
47+
data-testid="bcu-list"
48+
>
49+
<slot />
50+
</div>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { render } from '@testing-library/svelte';
2+
import { describe, it, expect } from 'vitest';
3+
4+
import { writable } from 'svelte/store';
5+
6+
import ListBox from '$lib/components/DataDisplay/List/List.svelte';
7+
8+
describe('ListBox.svelte', () => {
9+
it('Renders with minimal props', async () => {
10+
const { getByTestId } = render(ListBox);
11+
expect(getByTestId('listbox')).toBeTruthy();
12+
});
13+
14+
it('Renders with all props', async () => {
15+
const { getByTestId } = render(ListBox, {
16+
props: {
17+
selected: writable('foobar'),
18+
space: 'space-y-1',
19+
height: 'h-auto',
20+
// Props (Item)
21+
accent: '!bg-primary-500', // '!' recommended
22+
padding: 'px-4 py-3',
23+
rounded: 'rounded',
24+
// Props (regions)
25+
regionLabel: 'bg-red-500',
26+
regionList: 'bg-green-500',
27+
// Props (a11y)
28+
label: 'testList1',
29+
labelId: 'testListId1'
30+
}
31+
});
32+
expect(getByTestId('bcu-list')).toBeTruthy();
33+
});
34+
35+
it('Renders List, single value', async () => {
36+
const { getByTestId } = render(ListBox, {
37+
props: { tag: 'nav', selected: writable('foobar') }
38+
});
39+
const element = getByTestId('bcu-list');
40+
expect(element).toBeTruthy();
41+
expect(element.tagName).eq('DIV');
42+
});
43+
44+
it('Renders List, multiple values', async () => {
45+
const { getByTestId } = render(ListBox, {
46+
props: { tag: 'nav', selected: writable(['foo', 'bar']) }
47+
});
48+
const element = getByTestId('bcu-list');
49+
expect(element).toBeTruthy();
50+
expect(element.tagName).eq('DIV');
51+
});
52+
});
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script lang="ts">
2+
// Slots
3+
/**
4+
* @slot lead - Positioned on the left of each row item.
5+
* @slot trail - Positioned on the right of each row item.
6+
*/
7+
8+
import { getContext } from 'svelte';
9+
10+
// Types
11+
import type { CssClasses } from '$lib';
12+
13+
// Props
14+
/** Set the radio group binding value. */
15+
export let group: any;
16+
/** Set a unique name value for the input. */
17+
export let name: string;
18+
/** Set the input's value. */
19+
export let value: any;
20+
21+
// Context
22+
export let multiple: string = getContext('multiple');
23+
export let rounded: CssClasses = getContext('rounded');
24+
export let active: CssClasses = getContext('active');
25+
export let hover: CssClasses = getContext('hover');
26+
export let padding: CssClasses = getContext('padding');
27+
28+
// Classes
29+
const cBase: string = 'px-4 py-2 cursor-pointer';
30+
const cLabel: string = 'flex space-x-4';
31+
32+
// Local
33+
let checked: boolean;
34+
let elemInput: HTMLElement;
35+
36+
// Svelte Checkbox Bugfix
37+
// GitHub: https://github.com/sveltejs/svelte/issues/2308
38+
// REPL: https://svelte.dev/repl/de117399559f4e7e9e14e2fc9ab243cc?version=3.12.1
39+
$: if (multiple) updateCheckbox(group);
40+
$: if (multiple) updateGroup(checked);
41+
function updateCheckbox(group: any) {
42+
checked = group.indexOf(value) >= 0;
43+
}
44+
function updateGroup(checked: boolean) {
45+
const index = group.indexOf(value);
46+
if (checked) {
47+
if (index < 0) {
48+
group.push(value);
49+
group = group;
50+
}
51+
} else {
52+
if (index >= 0) {
53+
group.splice(index, 1);
54+
group = group;
55+
}
56+
}
57+
}
58+
59+
// A11y Key Down Handler
60+
function onKeyDown(event: KeyboardEvent): void {
61+
if (['Enter', 'Space'].includes(event.code)) {
62+
event.preventDefault();
63+
elemInput.click();
64+
}
65+
}
66+
67+
// Reactive
68+
$: selected = multiple ? group.includes(value) : group === value;
69+
$: classesActive = selected ? active : hover;
70+
$: classesBase = `${cBase} ${rounded} ${padding} ${classesActive} ${$$props.class ?? ''}`;
71+
$: classesLabel = `${cLabel}`;
72+
</script>
73+
74+
<!-- WARNING: avoid click handlers on <label>; will fire twice -->
75+
<!-- svelte-ignore a11y-no-noninteractive-element-to-interactive-role -->
76+
<label
77+
class="bcu-list-item {classesBase}"
78+
role="option"
79+
aria-selected={selected}
80+
tabindex="0"
81+
data-testid="bcu-list-item"
82+
on:keydown={onKeyDown}
83+
on:keydown
84+
on:keyup
85+
on:keypress
86+
>
87+
<!-- NOTE: Don't use `hidden` as it prevents `required` from operating -->
88+
<div class="h-0 w-0 overflow-hidden">
89+
{#if multiple}
90+
<input
91+
bind:this={elemInput}
92+
type="checkbox"
93+
{name}
94+
{value}
95+
bind:checked
96+
tabindex="-1"
97+
on:click
98+
on:change
99+
/>
100+
{:else}
101+
<input
102+
bind:this={elemInput}
103+
type="radio"
104+
bind:group
105+
{name}
106+
{value}
107+
tabindex="-1"
108+
on:click
109+
on:change
110+
/>
111+
{/if}
112+
</div>
113+
<!-- <slot /> -->
114+
<div class="bcu-list-label {classesLabel}">
115+
<!-- Slot: Lead -->
116+
{#if $$slots['bcu-list-lead']}<div class="bcu-list-lead">
117+
<slot name="bcu-list-lead" />
118+
</div>{/if}
119+
<!-- Slot: Default -->
120+
<div class="bcu-list-label-content flex-1"><slot /></div>
121+
<!-- Slot: Trail -->
122+
{#if $$slots['bcu-list-trail']}<div class="bcu-list-trail">
123+
<slot name="bcu-list-trail" />
124+
</div>{/if}
125+
</div>
126+
</label>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { render } from '@testing-library/svelte';
2+
import { describe, it, expect } from 'vitest';
3+
4+
import ListBoxItem from '$lib/components/DataDisplay/List/ListItem.svelte';
5+
6+
describe('ListBoxItem.svelte', () => {
7+
it('Renders with minimal props', async () => {
8+
const { getByTestId } = render(ListBoxItem);
9+
const element: HTMLElement = getByTestId('bcu-list-item');
10+
expect(element).toBeTruthy();
11+
expect(element.tagName).eq('LABEL');
12+
});
13+
14+
// TODO: we need to define the `value` prop here, not sure the syntax
15+
it('Renders <nav> selection list item, single value', async () => {
16+
const { getByTestId } = render(ListBoxItem, {
17+
props: {
18+
group: 'testGroup',
19+
name: 'testName',
20+
value: 'testValue'
21+
}
22+
});
23+
const element: HTMLElement = getByTestId('bcu-list-item');
24+
expect(element).toBeTruthy();
25+
});
26+
});

src/lib/components/DataDisplay/Paginator/Paginator.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
/** Set the text for the amount selection input. */
3131
export let amountText = 'Items';
3232
/** Provide abtitrary classes to the next/previous buttons. */
33-
export let buttonClasses: CssClasses = 'btn-icon variant-filled';
33+
export let buttonClasses: CssClasses = 'bcu-button-icon variant-filled';
3434
/** Set the text label for the Previous button. */
3535
export let buttonTextPrevious: CssClasses = '&larr;';
3636
/** Set the text label for the Next button. */

src/lib/styles/elements.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
@import 'elements/cards.css';
1111
@import 'elements/chips.css';
1212
@import 'elements/forms.css';
13-
@import 'elements/lists.css';
1413
@import 'elements/placeholders.css';
1514
@import 'elements/tables.css';
1615

src/lib/styles/typography.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
a:not(.unstyled):not(.permalink):is(:not(.prose *)):not(.bcu-button):not(.bcu-button-icon):not(
3737
.bcu-app-bar a
38-
):not(.logo-item):not(a.bcu-card):not(.bcu-list-nav a) {
38+
):not(.logo-item):not(a.bcu-card) {
3939
@apply text-primary-700 dark:text-primary-500 hover:brightness-110 underline;
4040
}
4141

src/routes/(docs)/DocsAppBar/DocsAppBar.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
<h6>Mode</h6>
114114
<LightSwitch />
115115
</section>
116-
<nav class="bcu-list-nav p-4 -m-4 max-h-64 lg:max-h-[500px] overflow-y-auto">
116+
<nav class="bcu-docs-list-nav p-4 -m-4 max-h-64 lg:max-h-[500px] overflow-y-auto">
117117
<form action="/?/setTheme" method="POST" use:enhance={setTheme}>
118118
<ul>
119119
{#each themes as { icon, name, type }}

src/routes/(docs)/DocsSideNav/DocsSideNav.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
</div>
3030
{#if navLink?.items && navLink?.items?.length > 0}
3131
<!-- Navigation List -->
32-
<nav class="bcu-list-nav">
32+
<nav class="bcu-docs-list-nav">
3333
<ul>
3434
{#each navLink.items as item}
3535
<li on:click={onListItemClick} on:keypress>

0 commit comments

Comments
 (0)