@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.
@@ -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 / summary text, after the filters or the button */
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 / summary text, after the filters or the button */
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
- /** Right-aligned cluster (e.g. SegmentedControl + ViewToggle) */
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
- /** Right-aligned cluster (e.g. SegmentedControl + ViewToggle) */
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,4 @@
1
+ import { type ToolbarCountProps } from '../utils/result-count.js';
2
+ declare const ToolbarCount: import("svelte").Component<ToolbarCountProps, {}, "">;
3
+ type ToolbarCount = ReturnType<typeof ToolbarCount>;
4
+ export default ToolbarCount;
@@ -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;
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/theme-svelte",
3
- "version": "1.39.0",
3
+ "version": "1.40.0",
4
4
  "description": "Svelte components for the Classic theme system",
5
5
  "type": "module",
6
6
  "svelte": "./dist/lib/index.js",