@classic-homes/theme-svelte 1.39.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/components/FilterToolbar.svelte +7 -1
- package/dist/lib/components/FilterToolbar.svelte.d.ts +4 -1
- package/dist/lib/components/Label.svelte +1 -1
- package/dist/lib/components/Toolbar.svelte +8 -3
- package/dist/lib/components/Toolbar.svelte.d.ts +4 -1
- package/dist/lib/components/ToolbarCount.svelte +20 -0
- package/dist/lib/components/ToolbarCount.svelte.d.ts +4 -0
- package/dist/lib/components/ToolbarSearch.svelte +39 -0
- package/dist/lib/components/ToolbarSearch.svelte.d.ts +18 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/utils/result-count.d.ts +34 -0
- package/dist/lib/utils/result-count.js +15 -0
- package/package.json +1 -1
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import FilterPanel from './FilterPanel.svelte';
|
|
7
7
|
import Icon from './Icon.svelte';
|
|
8
8
|
import Toolbar from './Toolbar.svelte';
|
|
9
|
+
import ToolbarCount from './ToolbarCount.svelte';
|
|
10
|
+
import type { ToolbarCountProps } from '../utils/result-count.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* FilterToolbar - a Toolbar that folds its filter controls behind a
|
|
@@ -33,7 +35,9 @@
|
|
|
33
35
|
filterCount: number;
|
|
34
36
|
/** Filters currently narrowing results (button badge) */
|
|
35
37
|
activeCount?: number;
|
|
36
|
-
/** Result count
|
|
38
|
+
/** Result count, after the filters or the button (see `ToolbarCount`) */
|
|
39
|
+
count?: ToolbarCountProps;
|
|
40
|
+
/** Other summary text after the count (inherits the strip color; `text-toolbar-muted` to dim) */
|
|
37
41
|
summary?: Snippet;
|
|
38
42
|
/** Right-aligned cluster (view toggles etc.) */
|
|
39
43
|
end?: Snippet;
|
|
@@ -54,6 +58,7 @@
|
|
|
54
58
|
filters,
|
|
55
59
|
filterCount,
|
|
56
60
|
activeCount = 0,
|
|
61
|
+
count,
|
|
57
62
|
summary,
|
|
58
63
|
end: endSnippet,
|
|
59
64
|
onClear,
|
|
@@ -99,6 +104,7 @@
|
|
|
99
104
|
{:else}
|
|
100
105
|
{@render filters({ inline: true })}
|
|
101
106
|
{/if}
|
|
107
|
+
{#if count}<ToolbarCount {...count} />{/if}
|
|
102
108
|
{@render summary?.()}
|
|
103
109
|
</Toolbar>
|
|
104
110
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ToolbarCountProps } from '../utils/result-count.js';
|
|
2
3
|
/**
|
|
3
4
|
* FilterToolbar - a Toolbar that folds its filter controls behind a
|
|
4
5
|
* "Filters" button once there are too many to sit inline.
|
|
@@ -27,7 +28,9 @@ interface Props {
|
|
|
27
28
|
filterCount: number;
|
|
28
29
|
/** Filters currently narrowing results (button badge) */
|
|
29
30
|
activeCount?: number;
|
|
30
|
-
/** Result count
|
|
31
|
+
/** Result count, after the filters or the button (see `ToolbarCount`) */
|
|
32
|
+
count?: ToolbarCountProps;
|
|
33
|
+
/** Other summary text after the count (inherits the strip color; `text-toolbar-muted` to dim) */
|
|
31
34
|
summary?: Snippet;
|
|
32
35
|
/** Right-aligned cluster (view toggles etc.) */
|
|
33
36
|
end?: Snippet;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<LabelPrimitive.Root
|
|
24
24
|
for={htmlFor}
|
|
25
25
|
class={cn(
|
|
26
|
-
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-foreground',
|
|
26
|
+
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-[hsl(var(--label-foreground,var(--foreground)))]',
|
|
27
27
|
disabled && 'opacity-50 cursor-not-allowed',
|
|
28
28
|
className
|
|
29
29
|
)}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import { cn } from '../utils.js';
|
|
4
|
+
import type { ToolbarCountProps } from '../utils/result-count.js';
|
|
5
|
+
import ToolbarCount from './ToolbarCount.svelte';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Toolbar - dark strip for list controls (search, filters, result count,
|
|
@@ -17,25 +19,28 @@
|
|
|
17
19
|
label: string;
|
|
18
20
|
/** Leading controls (search, filters, result count) */
|
|
19
21
|
children?: Snippet;
|
|
20
|
-
/**
|
|
22
|
+
/** Result count after the leading controls, e.g. `{ total, noun: 'home', page }` */
|
|
23
|
+
count?: ToolbarCountProps;
|
|
24
|
+
/** Right-aligned cluster: scope pickers (All/Mine), SegmentedControl, ViewToggle */
|
|
21
25
|
end?: Snippet;
|
|
22
26
|
class?: string;
|
|
23
27
|
[key: string]: unknown;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
let { label, children, end, class: className, ...restProps }: Props = $props();
|
|
30
|
+
let { label, children, count, end, class: className, ...restProps }: Props = $props();
|
|
27
31
|
</script>
|
|
28
32
|
|
|
29
33
|
<div
|
|
30
34
|
role="group"
|
|
31
35
|
aria-label={label}
|
|
32
36
|
class={cn(
|
|
33
|
-
'flex flex-wrap items-center gap-2 rounded-lg bg-toolbar px-3 py-2 text-toolbar-foreground [--secondary-border:var(--toolbar-border)]',
|
|
37
|
+
'flex flex-wrap items-center gap-2 rounded-lg bg-toolbar px-3 py-2 text-toolbar-foreground [--label-foreground:var(--toolbar-foreground)] [--secondary-border:var(--toolbar-border)]',
|
|
34
38
|
className
|
|
35
39
|
)}
|
|
36
40
|
{...restProps}
|
|
37
41
|
>
|
|
38
42
|
{@render children?.()}
|
|
43
|
+
{#if count}<ToolbarCount {...count} />{/if}
|
|
39
44
|
{#if end}
|
|
40
45
|
<div class="ml-auto flex flex-wrap items-center gap-2">{@render end()}</div>
|
|
41
46
|
{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { ToolbarCountProps } from '../utils/result-count.js';
|
|
2
3
|
/**
|
|
3
4
|
* Toolbar - dark strip for list controls (search, filters, result count,
|
|
4
5
|
* grouping and view toggles) under a page header.
|
|
@@ -14,7 +15,9 @@ interface Props {
|
|
|
14
15
|
label: string;
|
|
15
16
|
/** Leading controls (search, filters, result count) */
|
|
16
17
|
children?: Snippet;
|
|
17
|
-
/**
|
|
18
|
+
/** Result count after the leading controls, e.g. `{ total, noun: 'home', page }` */
|
|
19
|
+
count?: ToolbarCountProps;
|
|
20
|
+
/** Right-aligned cluster: scope pickers (All/Mine), SegmentedControl, ViewToggle */
|
|
18
21
|
end?: Snippet;
|
|
19
22
|
class?: string;
|
|
20
23
|
[key: string]: unknown;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { resultCountText, type ToolbarCountProps } from '../utils/result-count.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ToolbarCount - the result count on a Toolbar / FilterToolbar strip, in the
|
|
6
|
+
* one wording `resultCountText` defines ("Showing 1–50 of 7,289 homes",
|
|
7
|
+
* "12 of 40 homes", "40 homes", "No homes found"). Inherits the strip's
|
|
8
|
+
* light text and announces changes politely. Renders nothing while `total`
|
|
9
|
+
* is undefined (first load). Usually passed as the strip's `count` prop.
|
|
10
|
+
*/
|
|
11
|
+
let { note, ...input }: ToolbarCountProps = $props();
|
|
12
|
+
|
|
13
|
+
const text = $derived(resultCountText(input));
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
{#if text}
|
|
17
|
+
<span class="whitespace-nowrap text-sm tabular-nums" role="status">
|
|
18
|
+
{text}{note && input.total ? ` · ${note}` : ''}
|
|
19
|
+
</span>
|
|
20
|
+
{/if}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import Input from './Input.svelte';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ToolbarSearch - the search field for a Toolbar / FilterToolbar strip:
|
|
7
|
+
* full width on phones, a fixed `sm:w-64` beside the filters, and an
|
|
8
|
+
* accessible name (strips have no visible labels). End `placeholder` with
|
|
9
|
+
* "…", e.g. "Search homes…".
|
|
10
|
+
*/
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Accessible name, e.g. "Search homes" */
|
|
13
|
+
label: string;
|
|
14
|
+
value?: string;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
id?: string;
|
|
17
|
+
class?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
label,
|
|
23
|
+
value = $bindable(''),
|
|
24
|
+
placeholder,
|
|
25
|
+
id,
|
|
26
|
+
class: className,
|
|
27
|
+
...restProps
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<Input
|
|
32
|
+
{id}
|
|
33
|
+
type="search"
|
|
34
|
+
aria-label={label}
|
|
35
|
+
placeholder={placeholder ?? `${label}…`}
|
|
36
|
+
class={cn('w-full sm:w-64', className)}
|
|
37
|
+
bind:value
|
|
38
|
+
{...restProps}
|
|
39
|
+
/>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolbarSearch - the search field for a Toolbar / FilterToolbar strip:
|
|
3
|
+
* full width on phones, a fixed `sm:w-64` beside the filters, and an
|
|
4
|
+
* accessible name (strips have no visible labels). End `placeholder` with
|
|
5
|
+
* "…", e.g. "Search homes…".
|
|
6
|
+
*/
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Accessible name, e.g. "Search homes" */
|
|
9
|
+
label: string;
|
|
10
|
+
value?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
declare const ToolbarSearch: import("svelte").Component<Props, {}, "value">;
|
|
17
|
+
type ToolbarSearch = ReturnType<typeof ToolbarSearch>;
|
|
18
|
+
export default ToolbarSearch;
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -71,6 +71,9 @@ export { default as Tabs } from './components/Tabs.svelte';
|
|
|
71
71
|
export { default as TabPanel } from './components/TabPanel.svelte';
|
|
72
72
|
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
73
73
|
export { default as FilterToolbar } from './components/FilterToolbar.svelte';
|
|
74
|
+
export { default as ToolbarCount } from './components/ToolbarCount.svelte';
|
|
75
|
+
export { default as ToolbarSearch } from './components/ToolbarSearch.svelte';
|
|
76
|
+
export { resultCountText, type ResultCountInput, type ToolbarCountProps, } from './utils/result-count.js';
|
|
74
77
|
export { default as FilterPanel } from './components/FilterPanel.svelte';
|
|
75
78
|
export { default as ActiveFilterChips, type ActiveFilterChip, } from './components/ActiveFilterChips.svelte';
|
|
76
79
|
export { default as SegmentedControl, type SegmentedControlOption, } from './components/SegmentedControl.svelte';
|
package/dist/lib/index.js
CHANGED
|
@@ -85,6 +85,9 @@ export { default as TabPanel } from './components/TabPanel.svelte';
|
|
|
85
85
|
// Toolbar controls
|
|
86
86
|
export { default as Toolbar } from './components/Toolbar.svelte';
|
|
87
87
|
export { default as FilterToolbar } from './components/FilterToolbar.svelte';
|
|
88
|
+
export { default as ToolbarCount } from './components/ToolbarCount.svelte';
|
|
89
|
+
export { default as ToolbarSearch } from './components/ToolbarSearch.svelte';
|
|
90
|
+
export { resultCountText, } from './utils/result-count.js';
|
|
88
91
|
export { default as FilterPanel } from './components/FilterPanel.svelte';
|
|
89
92
|
export { default as ActiveFilterChips, } from './components/ActiveFilterChips.svelte';
|
|
90
93
|
export { default as SegmentedControl, } from './components/SegmentedControl.svelte';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one result-count wording for list toolbars (`ToolbarCount`, and the
|
|
3
|
+
* `count` prop on `Toolbar` / `FilterToolbar`):
|
|
4
|
+
*
|
|
5
|
+
* - server-paged, more than one page: "Showing 1–50 of 7,289 homes"
|
|
6
|
+
* - locally filtered (`of` = unfiltered size): "12 of 40 homes"
|
|
7
|
+
* - otherwise: "40 homes" / "1 home"
|
|
8
|
+
* - nothing matches: `empty` (default "No homes found")
|
|
9
|
+
*
|
|
10
|
+
* Returns '' while the first load is pending (`total` undefined), so the strip
|
|
11
|
+
* never flashes "No homes found" before the data lands.
|
|
12
|
+
*/
|
|
13
|
+
export interface ResultCountInput {
|
|
14
|
+
/** Rows matching the current search/filters (server total or filtered length); undefined while loading. */
|
|
15
|
+
total: number | undefined | null;
|
|
16
|
+
/** Singular noun, e.g. "home". */
|
|
17
|
+
noun: string;
|
|
18
|
+
/** Plural noun when it isn't `${noun}s`, e.g. "entries". */
|
|
19
|
+
plural?: string;
|
|
20
|
+
/** Server paging: renders the "Showing a–b of N" range when total spans pages. */
|
|
21
|
+
page?: {
|
|
22
|
+
page: number;
|
|
23
|
+
limit: number;
|
|
24
|
+
};
|
|
25
|
+
/** Unfiltered size for locally filtered lists: "12 of 40 homes". */
|
|
26
|
+
of?: number;
|
|
27
|
+
/** Zero-match text (default "No <plural> found"). */
|
|
28
|
+
empty?: string;
|
|
29
|
+
}
|
|
30
|
+
/** `ResultCountInput` plus a trailing remark after " · " (falsy hides it). */
|
|
31
|
+
export interface ToolbarCountProps extends ResultCountInput {
|
|
32
|
+
note?: string | false | null;
|
|
33
|
+
}
|
|
34
|
+
export declare function resultCountText({ total, noun, plural, page, of, empty, }: ResultCountInput): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const fmt = (n) => n.toLocaleString('en-US');
|
|
2
|
+
export function resultCountText({ total, noun, plural = `${noun}s`, page, of, empty, }) {
|
|
3
|
+
if (total == null)
|
|
4
|
+
return '';
|
|
5
|
+
if (total === 0)
|
|
6
|
+
return empty ?? `No ${plural} found`;
|
|
7
|
+
if (page && total > page.limit) {
|
|
8
|
+
const start = (page.page - 1) * page.limit + 1;
|
|
9
|
+
const end = Math.min(page.page * page.limit, total);
|
|
10
|
+
return `Showing ${fmt(start)}–${fmt(end)} of ${fmt(total)} ${plural}`;
|
|
11
|
+
}
|
|
12
|
+
if (of !== undefined && of !== total)
|
|
13
|
+
return `${fmt(total)} of ${fmt(of)} ${plural}`;
|
|
14
|
+
return `${fmt(total)} ${total === 1 ? noun : plural}`;
|
|
15
|
+
}
|