@at-flux/astroflare 1.0.2 → 1.0.4

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.
Files changed (41) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +98 -14
  3. package/dist/chunk-pbuEa-1d.js +13 -0
  4. package/dist/core.cjs +162 -138
  5. package/dist/core.d.cts +59 -2
  6. package/dist/core.d.ts +59 -2
  7. package/dist/core.js +147 -16
  8. package/dist/forms/index.cjs +7 -130
  9. package/dist/forms/index.d.cts +2 -52
  10. package/dist/forms/index.d.ts +2 -52
  11. package/dist/forms/index.js +99 -14
  12. package/dist/{chunk-DRYHJSYC.js → forms-8uIdWmMD.cjs} +81 -53
  13. package/dist/index-Bk-K9aDQ.d.ts +44 -0
  14. package/dist/index-CcH_yXr-.d.cts +44 -0
  15. package/dist/index.cjs +20 -141
  16. package/dist/index.d.cts +3 -2
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.js +3 -17
  19. package/package.json +14 -8
  20. package/src/components/CollectionQuery.astro +107 -0
  21. package/src/components/ContactModalCta.astro +2 -0
  22. package/src/components/FilterPills.astro +164 -0
  23. package/src/components/IconButton.astro +5 -0
  24. package/src/components/InstagramProfileLink.astro +3 -0
  25. package/src/components/ListSummary.astro +63 -0
  26. package/src/components/MediaProtect.astro +41 -0
  27. package/src/components/Modal.astro +3 -0
  28. package/src/components/ModalTrigger.astro +2 -0
  29. package/src/components/Pager.astro +92 -0
  30. package/src/components/Section.astro +3 -0
  31. package/src/components/TagSummary.astro +71 -0
  32. package/src/components/ThemeToggle.astro +1 -0
  33. package/src/components/Tooltip.astro +70 -0
  34. package/src/components/collection-query-props.ts +45 -0
  35. package/src/runtime/collection-query.ts +156 -0
  36. package/src/runtime/media-protect.ts +70 -0
  37. package/src/styles/accessibility.css +1 -1
  38. package/src/styles/no-save.css +2 -2
  39. package/src/styles/prose.css +23 -11
  40. package/src/styles/scrollbar.css +10 -3
  41. package/dist/chunk-ALMPH3A2.js +0 -0
@@ -0,0 +1,164 @@
1
+ ---
2
+ import { getTagPalette } from "../tag-colors";
3
+
4
+ interface Props {
5
+ /** Tag/filter values rendered as clickable pills. */
6
+ items: Array<
7
+ | string
8
+ | {
9
+ value: string;
10
+ label?: string;
11
+ href?: string;
12
+ active?: boolean;
13
+ colorKey?: string;
14
+ }
15
+ >;
16
+ /** Include an "all" filter option first. */
17
+ includeAll?: boolean;
18
+ /** Label shown for the catch-all filter. */
19
+ allLabel?: string;
20
+ /** Optional URL for the catch-all filter pill (renders as link when set). */
21
+ allHref?: string;
22
+ /** Initial active filter value. */
23
+ active?: string;
24
+ /** Optional casing transform for labels. */
25
+ itemCase?: "none" | "upper";
26
+ /** Explicit color map by lowercase tag key. */
27
+ colorOverrides?: Record<string, string>;
28
+ /** Classes applied to the pill row wrapper. */
29
+ class?: string;
30
+ }
31
+
32
+ const {
33
+ items,
34
+ includeAll = true,
35
+ allLabel = "All",
36
+ allHref,
37
+ active = "all",
38
+ itemCase = "upper",
39
+ colorOverrides,
40
+ class: className = "",
41
+ } = Astro.props;
42
+
43
+ const formatLabel = (value: string): string =>
44
+ itemCase === "upper" ? value.toUpperCase() : value;
45
+
46
+ const normalizedItems = items.map((item) =>
47
+ typeof item === "string"
48
+ ? { value: item, label: item }
49
+ : {
50
+ value: item.value,
51
+ label: item.label ?? item.value,
52
+ href: item.href,
53
+ active: item.active,
54
+ colorKey: item.colorKey,
55
+ },
56
+ );
57
+ ---
58
+
59
+ <div class:list={["af-filter-pills flex flex-wrap items-center gap-2", className]}>
60
+ {includeAll && (
61
+ allHref ? (
62
+ <a
63
+ href={allHref}
64
+ class:list={["af-filter-pill", "af-filter-pill-all", active === "all" && "is-active"]}
65
+ >
66
+ {formatLabel(allLabel)}
67
+ </a>
68
+ ) : (
69
+ <button
70
+ type="button"
71
+ data-filter="all"
72
+ class:list={["af-filter-pill", "af-filter-pill-all", active === "all" && "is-active"]}
73
+ >
74
+ {formatLabel(allLabel)}
75
+ </button>
76
+ )
77
+ )}
78
+ {normalizedItems.map((item) => {
79
+ const palette = getTagPalette(item.colorKey ?? item.value, { overrides: colorOverrides });
80
+ const isActive = item.active ?? active === item.value;
81
+ const label = formatLabel(item.label);
82
+ return (
83
+ item.href ? (
84
+ <a
85
+ href={item.href}
86
+ class:list={["af-filter-pill", isActive && "is-active"]}
87
+ style={{
88
+ "--af-pill-bg": palette.bg,
89
+ "--af-pill-border": palette.border,
90
+ "--af-pill-text": palette.text,
91
+ }}
92
+ >
93
+ {label}
94
+ </a>
95
+ ) : (
96
+ <button
97
+ type="button"
98
+ data-filter={item.value}
99
+ class:list={["af-filter-pill", isActive && "is-active"]}
100
+ style={{
101
+ "--af-pill-bg": palette.bg,
102
+ "--af-pill-border": palette.border,
103
+ "--af-pill-text": palette.text,
104
+ }}
105
+ >
106
+ {label}
107
+ </button>
108
+ )
109
+ );
110
+ })}
111
+ </div>
112
+
113
+ <style>
114
+ .af-filter-pill {
115
+ border-radius: 9999px;
116
+ border: 1px solid var(--af-pill-border, var(--color-light-border));
117
+ background: var(--af-pill-bg, color-mix(in oklab, var(--color-light-surface) 80%, transparent));
118
+ color: var(--af-pill-text, color-mix(in oklab, var(--color-light-text-subtle) 78%, transparent));
119
+ padding: 0.35rem 0.9rem;
120
+ font-size: 0.72rem;
121
+ font-weight: 500;
122
+ letter-spacing: 0.08em;
123
+ text-transform: uppercase;
124
+ cursor: pointer;
125
+ transition: transform 130ms ease, filter 130ms ease;
126
+ text-decoration: none;
127
+ display: inline-flex;
128
+ align-items: center;
129
+ }
130
+
131
+ .af-filter-pill:hover {
132
+ filter: brightness(1.04);
133
+ }
134
+
135
+ .af-filter-pill.is-active {
136
+ border-color: color-mix(in oklab, var(--af-pill-text, var(--color-secondary)) 80%, transparent);
137
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--af-pill-text, var(--color-secondary)) 36%, transparent);
138
+ }
139
+ .af-filter-pill.af-filter-pill-all {
140
+ --af-pill-bg: transparent;
141
+ --af-pill-border: color-mix(in oklab, var(--color-light-text-subtle) 45%, transparent);
142
+ --af-pill-text: color-mix(in oklab, var(--color-light-text-body) 92%, transparent);
143
+ }
144
+ .af-filter-pill.af-filter-pill-all.is-active {
145
+ --af-pill-bg: color-mix(in oklab, var(--color-light-text-body) 7%, transparent);
146
+ --af-pill-border: color-mix(in oklab, var(--color-light-text-body) 72%, transparent);
147
+ --af-pill-text: var(--color-light-text-body);
148
+ }
149
+ :global([data-theme="dark"]) .af-filter-pill {
150
+ color: var(--af-pill-text, color-mix(in oklab, var(--color-dark-text-subtle) 85%, transparent));
151
+ border-color: var(--af-pill-border, var(--color-dark-border));
152
+ background: var(--af-pill-bg, color-mix(in oklab, var(--color-dark-surface) 70%, transparent));
153
+ }
154
+ :global([data-theme="dark"]) .af-filter-pill.af-filter-pill-all {
155
+ --af-pill-bg: transparent;
156
+ --af-pill-border: color-mix(in oklab, var(--color-dark-text-subtle) 48%, transparent);
157
+ --af-pill-text: color-mix(in oklab, var(--color-dark-text-body) 90%, transparent);
158
+ }
159
+ :global([data-theme="dark"]) .af-filter-pill.af-filter-pill-all.is-active {
160
+ --af-pill-bg: color-mix(in oklab, var(--color-dark-text-body) 10%, transparent);
161
+ --af-pill-border: color-mix(in oklab, var(--color-dark-text-body) 70%, transparent);
162
+ --af-pill-text: var(--color-dark-text-body);
163
+ }
164
+ </style>
@@ -5,10 +5,15 @@
5
5
  * Consumers must supply their own icon via slot.
6
6
  */
7
7
  interface Props {
8
+ /** Accessible label announced by screen readers. */
8
9
  label: string;
10
+ /** Optional URL; when set, renders an `<a>` instead of `<button>`. */
9
11
  href?: string;
12
+ /** Extra classes merged with base button styles. */
10
13
  class?: string;
14
+ /** Optional element id for targeting and tests. */
11
15
  id?: string;
16
+ /** Additional passthrough attributes for anchor/button element. */
12
17
  [key: string]: unknown;
13
18
  }
14
19
 
@@ -2,8 +2,11 @@
2
2
  interface Props {
3
3
  /** Instagram username without @ */
4
4
  handle: string;
5
+ /** Optional explicit profile URL (defaults to `https://www.instagram.com/{handle}`). */
5
6
  href?: string;
7
+ /** Classes merged onto the anchor element. */
6
8
  class?: string;
9
+ /** Optional accessible label override for the link. */
7
10
  'aria-label'?: string;
8
11
  }
9
12
 
@@ -0,0 +1,63 @@
1
+ ---
2
+ import Tooltip from "./Tooltip.astro";
3
+
4
+ interface Props {
5
+ /** Ordered list values to render. */
6
+ items: string[];
7
+ /** Maximum number of visible items before overflow summary is shown. */
8
+ visibleCount?: number;
9
+ /** String used to join visible/overflow values. */
10
+ separator?: string;
11
+ /** Classes applied to the outer summary wrapper. */
12
+ class?: string;
13
+ /** Fallback label when `items` is empty. */
14
+ emptyLabel?: string;
15
+ /** Classes applied to the overflow `+N` chip. */
16
+ overflowClass?: string;
17
+ /** Tooltip placement for overflow details. */
18
+ tooltipPosition?: "top" | "bottom";
19
+ /** Optional value casing transform before render. */
20
+ itemCase?: "none" | "upper";
21
+ }
22
+
23
+ const {
24
+ items,
25
+ visibleCount = 3,
26
+ separator = ", ",
27
+ class: className = "",
28
+ emptyLabel = "",
29
+ overflowClass = "",
30
+ tooltipPosition = "top",
31
+ itemCase = "none",
32
+ } = Astro.props;
33
+
34
+ const formatItem = (item: string): string =>
35
+ itemCase === "upper" ? item.toUpperCase() : item;
36
+
37
+ const visibleItems = items.slice(0, visibleCount).map(formatItem);
38
+ const overflowItems = items.slice(visibleCount).map(formatItem);
39
+ const overflowLabel = `+ ${overflowItems.length}`;
40
+ const tooltipText = overflowItems.join(separator);
41
+ ---
42
+
43
+ <span class:list={["inline-flex flex-wrap items-center gap-1", className]}>
44
+ {items.length === 0 ? (
45
+ emptyLabel && <span>{emptyLabel}</span>
46
+ ) : (
47
+ <>
48
+ <span>{visibleItems.join(separator)}</span>
49
+ {overflowItems.length > 0 && (
50
+ <Tooltip text={tooltipText} position={tooltipPosition}>
51
+ <span
52
+ class:list={[
53
+ "inline-flex cursor-help items-center rounded-full border px-2 py-0.5 text-xs",
54
+ overflowClass,
55
+ ]}
56
+ >
57
+ {overflowLabel}
58
+ </span>
59
+ </Tooltip>
60
+ )}
61
+ </>
62
+ )}
63
+ </span>
@@ -0,0 +1,41 @@
1
+ ---
2
+ /**
3
+ * Wrap media/content in no-save utility classes and register delegated protection handlers.
4
+ *
5
+ * - `drag`: block dragstart inside this protected region (defaults to true)
6
+ * - `contextMenu`: block right-click context menu inside this protected region (defaults to true)
7
+ */
8
+ interface Props {
9
+ /** Classes applied to the inner `.no-save` wrapper. */
10
+ class?: string;
11
+ /** Classes applied to the outer `.no-save-container` wrapper. */
12
+ containerClass?: string;
13
+ /** Enable dragstart prevention in this protected region. Defaults to true. */
14
+ drag?: boolean;
15
+ /** Enable contextmenu prevention in this protected region. Defaults to true. */
16
+ contextMenu?: boolean;
17
+ }
18
+
19
+ const {
20
+ class: className = "",
21
+ containerClass = "",
22
+ drag = true,
23
+ contextMenu = true,
24
+ } = Astro.props;
25
+ ---
26
+
27
+ <div
28
+ class:list={["no-save-container", containerClass]}
29
+ data-media-protect-root
30
+ data-media-protect-drag={drag ? "true" : "false"}
31
+ data-media-protect-context-menu={contextMenu ? "true" : "false"}
32
+ >
33
+ <div class:list={["no-save", className]}>
34
+ <slot />
35
+ </div>
36
+ </div>
37
+ <script>
38
+ import { initMediaProtectRoots } from "../runtime/media-protect";
39
+
40
+ initMediaProtectRoots();
41
+ </script>
@@ -4,12 +4,15 @@
4
4
  * `class` applies to the modal panel (card). Dialog element keeps backdrop/transparent shell.
5
5
  */
6
6
  interface Props {
7
+ /** Dialog id used by triggers (`ModalTrigger`, direct DOM calls). */
7
8
  id: string;
8
9
  /** Panel (card) classes — surface, border, radius, etc. */
9
10
  class?: string;
11
+ /** Classes applied to the `<dialog>` shell/backdrop container. */
10
12
  backdropClass?: string;
11
13
  /** Extra panel classes merged after `class`. */
12
14
  panelClass?: string;
15
+ /** Classes applied to the built-in close button. */
13
16
  closeButtonClass?: string;
14
17
  /** Scrollable inner wrapper around the default slot. */
15
18
  contentClass?: string;
@@ -3,7 +3,9 @@
3
3
  * Headless modal trigger. Clicking opens a <dialog> by ID.
4
4
  */
5
5
  interface Props {
6
+ /** Target dialog id to open via `showModal()`. */
6
7
  modalId: string;
8
+ /** Classes applied to the host trigger element. */
7
9
  class?: string;
8
10
  }
9
11
 
@@ -0,0 +1,92 @@
1
+ ---
2
+ interface Props {
3
+ /** Optional initial page count for static rendering. */
4
+ pageCount?: number;
5
+ /** Active page index (1-based) for static rendering. */
6
+ activePage?: number;
7
+ /** Classes applied to the pagination wrapper. */
8
+ class?: string;
9
+ /** Optional explicit pager items for link-based server rendering. */
10
+ items?: Array<
11
+ | { page: number; label?: string; href?: string; active?: boolean }
12
+ | { ellipsis: true; label?: string }
13
+ >;
14
+ }
15
+
16
+ const { pageCount = 0, activePage = 1, class: className = "", items = [] } = Astro.props;
17
+ const fallbackPages = Array.from({ length: Math.max(0, pageCount) }, (_, index) => index + 1);
18
+ ---
19
+
20
+ <div class:list={["af-pager flex items-center justify-center gap-2", className]} data-pagination>
21
+ {items.length > 0
22
+ ? items.map((item) =>
23
+ "ellipsis" in item ? (
24
+ <span class="af-pager-ellipsis" aria-hidden="true">{item.label ?? "…"}</span>
25
+ ) : item.href ? (
26
+ <a
27
+ href={item.href}
28
+ data-page={String(item.page)}
29
+ class:list={["af-pager-pill", item.active && "is-active"]}
30
+ >
31
+ {item.label ?? item.page}
32
+ </a>
33
+ ) : (
34
+ <button
35
+ type="button"
36
+ data-page={String(item.page)}
37
+ class:list={["af-pager-pill", item.active && "is-active"]}
38
+ >
39
+ {item.label ?? item.page}
40
+ </button>
41
+ ),
42
+ )
43
+ : fallbackPages.map((page) => (
44
+ <button
45
+ type="button"
46
+ data-page={String(page)}
47
+ class:list={["af-pager-pill", page === activePage && "is-active"]}
48
+ >
49
+ {page}
50
+ </button>
51
+ ))}
52
+ </div>
53
+
54
+ <style is:global>
55
+ .af-pager .af-pager-pill {
56
+ border-radius: 9999px;
57
+ border: 1px solid var(--color-light-border);
58
+ padding: 0.35rem 0.9rem;
59
+ font-size: 0.72rem;
60
+ font-weight: 500;
61
+ letter-spacing: 0.08em;
62
+ text-transform: uppercase;
63
+ color: color-mix(in oklab, var(--color-light-text-subtle) 78%, transparent);
64
+ background: color-mix(in oklab, var(--color-light-surface) 80%, transparent);
65
+ cursor: pointer;
66
+ text-decoration: none;
67
+ display: inline-flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ }
71
+ [data-theme="dark"] .af-pager .af-pager-pill {
72
+ border-color: var(--color-dark-border);
73
+ color: color-mix(in oklab, var(--color-dark-text-subtle) 78%, transparent);
74
+ background: color-mix(in oklab, var(--color-dark-surface) 70%, transparent);
75
+ }
76
+ .af-pager .af-pager-pill.is-active {
77
+ border-color: var(--color-secondary);
78
+ color: var(--color-secondary);
79
+ background: color-mix(in oklab, var(--color-secondary) 12%, transparent);
80
+ }
81
+ .af-pager .af-pager-ellipsis {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ min-width: 1.7rem;
86
+ font-size: 0.8rem;
87
+ color: color-mix(in oklab, var(--color-light-text-subtle) 75%, transparent);
88
+ }
89
+ [data-theme="dark"] .af-pager .af-pager-ellipsis {
90
+ color: color-mix(in oklab, var(--color-dark-text-subtle) 75%, transparent);
91
+ }
92
+ </style>
@@ -1,7 +1,10 @@
1
1
  ---
2
2
  interface Props {
3
+ /** Optional section id attribute. */
3
4
  id?: string;
5
+ /** Additional classes merged onto the section container. */
4
6
  class?: string;
7
+ /** Use narrower inner max width (`max-w-4xl`) instead of default wide layout. */
5
8
  narrow?: boolean;
6
9
  /** Inner max-width wrapper only — no section padding (e.g. modal body). */
7
10
  contentOnly?: boolean;
@@ -0,0 +1,71 @@
1
+ ---
2
+ import Tooltip from "./Tooltip.astro";
3
+ import { getTagPalette } from "../tag-colors";
4
+
5
+ interface Props {
6
+ /** Ordered tag list to render as pills. */
7
+ items: string[];
8
+ /** Maximum number of visible tags before overflow summary is shown. */
9
+ visibleCount?: number;
10
+ /** Classes applied to the outer tag list wrapper. */
11
+ class?: string;
12
+ /** Classes applied to each visible tag pill. */
13
+ itemClass?: string;
14
+ /** Classes applied to the overflow `+N` chip. */
15
+ overflowClass?: string;
16
+ /** Optional value casing transform before render. */
17
+ itemCase?: "none" | "upper";
18
+ /** Explicit color map by lowercase tag key, overriding default hues. */
19
+ colorOverrides?: Record<string, string>;
20
+ }
21
+
22
+ const {
23
+ items,
24
+ visibleCount = 3,
25
+ class: className = "",
26
+ itemClass = "",
27
+ overflowClass = "",
28
+ itemCase = "upper",
29
+ colorOverrides,
30
+ } = Astro.props;
31
+
32
+ const formatItem = (item: string): string =>
33
+ itemCase === "upper" ? item.toUpperCase() : item;
34
+
35
+ const visibleItems = items.slice(0, visibleCount);
36
+ const overflowItems = items.slice(visibleCount);
37
+ const overflowTooltip = overflowItems.map(formatItem).join(", ");
38
+ ---
39
+
40
+ <div class:list={["flex flex-wrap gap-1.5", className]}>
41
+ {visibleItems.map((item) => {
42
+ const palette = getTagPalette(item, { overrides: colorOverrides });
43
+ return (
44
+ <span
45
+ style={{
46
+ backgroundColor: palette.bg,
47
+ borderColor: palette.border,
48
+ color: palette.text,
49
+ }}
50
+ class:list={[
51
+ "inline-block rounded-full border px-2.5 py-0.5 text-[0.68rem] font-medium uppercase tracking-[0.06em]",
52
+ itemClass,
53
+ ]}
54
+ >
55
+ {formatItem(item)}
56
+ </span>
57
+ );
58
+ })}
59
+ {overflowItems.length > 0 && (
60
+ <Tooltip text={overflowTooltip}>
61
+ <span
62
+ class:list={[
63
+ "inline-flex cursor-help items-center rounded-full border border-black/20 px-2.5 py-0.5 text-xs font-medium text-black/70 dark:border-white/20 dark:text-white/70",
64
+ overflowClass,
65
+ ]}
66
+ >
67
+ + {overflowItems.length}
68
+ </span>
69
+ </Tooltip>
70
+ )}
71
+ </div>
@@ -6,6 +6,7 @@
6
6
  * Sun/moon icons animate in/out with opacity and rotation transitions.
7
7
  */
8
8
  interface Props {
9
+ /** Classes applied to the custom-element host wrapper. */
9
10
  class?: string;
10
11
  }
11
12
 
@@ -0,0 +1,70 @@
1
+ ---
2
+ interface Props {
3
+ /** Plain text shown in the tooltip panel. */
4
+ text: string;
5
+ /** Tooltip placement relative to trigger content. */
6
+ position?: "top" | "bottom";
7
+ /** Classes applied to the trigger wrapper element. */
8
+ class?: string;
9
+ /** Classes applied to the tooltip panel element. */
10
+ panelClass?: string;
11
+ }
12
+
13
+ const { text, position = "top", class: className = "", panelClass = "" } = Astro.props;
14
+ ---
15
+
16
+ <span class:list={["af-tooltip-root", className]} data-af-tooltip-root>
17
+ <slot />
18
+ <span
19
+ role="tooltip"
20
+ class="af-tooltip-panel"
21
+ class:list={[
22
+ panelClass,
23
+ ]}
24
+ data-position={position}
25
+ >
26
+ {text}
27
+ </span>
28
+ </span>
29
+
30
+ <style is:global>
31
+ [data-af-tooltip-root] {
32
+ position: relative;
33
+ display: inline-flex;
34
+ width: max-content;
35
+ justify-self: start;
36
+ }
37
+
38
+ [data-af-tooltip-root] .af-tooltip-panel {
39
+ pointer-events: none;
40
+ position: absolute;
41
+ left: 50%;
42
+ z-index: 30;
43
+ max-width: 20rem;
44
+ transform: translateX(-50%);
45
+ border-radius: 0.4rem;
46
+ border: 1px solid rgba(0, 0, 0, 0.2);
47
+ background: rgba(10, 10, 12, 0.92);
48
+ color: #fff;
49
+ font-size: 0.74rem;
50
+ line-height: 1.35;
51
+ padding: 0.35rem 0.55rem;
52
+ opacity: 0;
53
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22);
54
+ transition: opacity 150ms ease;
55
+ white-space: normal;
56
+ }
57
+
58
+ [data-af-tooltip-root] .af-tooltip-panel[data-position="top"] {
59
+ bottom: calc(100% + 0.45rem);
60
+ }
61
+
62
+ [data-af-tooltip-root] .af-tooltip-panel[data-position="bottom"] {
63
+ top: calc(100% + 0.45rem);
64
+ }
65
+
66
+ [data-af-tooltip-root]:hover .af-tooltip-panel,
67
+ [data-af-tooltip-root]:focus-within .af-tooltip-panel {
68
+ opacity: 1;
69
+ }
70
+ </style>
@@ -0,0 +1,45 @@
1
+ import type { CollectionQueryState } from "../collection-query";
2
+
3
+ export interface CollectionQueryFilterOption {
4
+ key: string;
5
+ value: string;
6
+ label?: string;
7
+ }
8
+
9
+ interface CollectionQueryBaseProps {
10
+ /** Available filters for filter pill rendering. */
11
+ filters?: CollectionQueryFilterOption[];
12
+ /** Max visible pager buttons before ellipsis. */
13
+ maxPageButtons?: number;
14
+ /** Optional wrapper class for filter pills row. */
15
+ filtersClass?: string;
16
+ /** Optional wrapper class for pager row. */
17
+ pagerClass?: string;
18
+ /** Visible cards per page in client mode. */
19
+ perPage?: number;
20
+ /** Classes applied to the host wrapper element in client mode. */
21
+ class?: string;
22
+ }
23
+
24
+ export interface CollectionQueryServerProps extends CollectionQueryBaseProps {
25
+ /**
26
+ * Enables URL-driven server query mode.
27
+ * Mount with `server:defer` at the callsite when true.
28
+ */
29
+ useServer: true;
30
+ /** Route pathname used to build querystring links in server mode. */
31
+ pathname: string;
32
+ /** Parsed query state for the current request in server mode. */
33
+ query: CollectionQueryState;
34
+ /** Total pages for current filtered collection in server mode. */
35
+ totalPages: number;
36
+ /** Currently selected page (1-based) in server mode. */
37
+ currentPage: number;
38
+ }
39
+
40
+ export interface CollectionQueryClientProps extends CollectionQueryBaseProps {
41
+ /** Client mode (default): filters and pagination are handled in-browser. */
42
+ useServer?: false;
43
+ }
44
+
45
+ export type CollectionQueryProps = CollectionQueryServerProps | CollectionQueryClientProps;