@astralarium/repo-ui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +61 -2
- package/dist/error-boundary.d.ts +29 -0
- package/dist/error-boundary.js +30 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/l10n/repo-ui/en.json +14 -0
- package/dist/lazy-google-analytics.d.ts +4 -0
- package/dist/lazy-google-analytics.js +34 -0
- package/dist/page-tree.d.ts +25 -0
- package/dist/page-tree.js +45 -0
- package/dist/responsive-table.d.ts +54 -0
- package/dist/responsive-table.js +180 -0
- package/dist/router/dev-route.d.ts +29 -0
- package/dist/router/dev-route.js +15 -0
- package/dist/router/index.d.ts +5 -0
- package/dist/router/index.js +5 -0
- package/dist/router/tab-bar.d.ts +12 -0
- package/dist/router/tab-bar.js +98 -0
- package/dist/router/tab-bar.messages.d.json.ts +7 -0
- package/dist/router/tab-bar.messages.json +6 -0
- package/dist/router/tabs.d.ts +21 -0
- package/dist/router/tabs.js +37 -0
- package/dist/router/use-search-param.d.ts +5 -0
- package/dist/router/use-search-param.js +46 -0
- package/dist/slider/index.d.ts +8 -0
- package/dist/slider/index.js +7 -0
- package/dist/slider/scrubber-state.d.ts +118 -0
- package/dist/slider/scrubber-state.js +179 -0
- package/dist/slider/scrubber.d.ts +77 -0
- package/dist/slider/scrubber.js +664 -0
- package/dist/slider/slider-parts.d.ts +16 -0
- package/dist/slider/slider-parts.js +11 -0
- package/dist/slider/slider-state.d.ts +75 -0
- package/dist/slider/slider-state.js +183 -0
- package/dist/slider/slider.d.ts +100 -0
- package/dist/slider/slider.js +966 -0
- package/dist/slider/slider.messages.d.json.ts +8 -0
- package/dist/slider/slider.messages.json +10 -0
- package/dist/slider/timeline-scrubber-state.d.ts +13 -0
- package/dist/slider/timeline-scrubber-state.js +21 -0
- package/dist/slider/timeline-scrubber.d.ts +26 -0
- package/dist/slider/timeline-scrubber.js +142 -0
- package/dist/sorted.d.ts +16 -0
- package/dist/sorted.js +40 -0
- package/dist/status.d.ts +9 -0
- package/dist/status.js +55 -0
- package/dist/sticky-height.d.ts +10 -0
- package/dist/sticky-height.js +133 -0
- package/dist/storage.d.ts +21 -0
- package/dist/storage.js +42 -0
- package/dist/testkit/index.d.ts +12 -0
- package/dist/testkit/index.js +42 -0
- package/dist/theme/index.d.ts +3 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/theme-context.d.ts +11 -0
- package/dist/theme/theme-context.js +68 -0
- package/dist/theme/theme.d.ts +28 -0
- package/dist/theme/theme.js +37 -0
- package/dist/theme/themed-component.d.ts +8 -0
- package/dist/theme/themed-component.js +10 -0
- package/dist/timeline.d.ts +34 -0
- package/dist/timeline.js +47 -0
- package/dist/tree/index.d.ts +2 -0
- package/dist/tree/index.js +1 -0
- package/dist/tree/tree.d.ts +27 -0
- package/dist/tree/tree.js +299 -0
- package/dist/tree/use-tree.d.ts +38 -0
- package/dist/tree/use-tree.js +155 -0
- package/dist/use-copy-feedback.d.ts +15 -0
- package/dist/use-copy-feedback.js +72 -0
- package/dist/use-debounce.d.ts +3 -0
- package/dist/use-debounce.js +66 -0
- package/dist/use-external-script.d.ts +10 -0
- package/dist/use-external-script.js +73 -0
- package/dist/use-frame.d.ts +3 -0
- package/dist/use-frame.js +37 -0
- package/dist/use-hover-focus.d.ts +18 -0
- package/dist/use-hover-focus.js +87 -0
- package/dist/use-media-query.d.ts +5 -0
- package/dist/use-media-query.js +42 -0
- package/dist/use-pausable-hover.d.ts +27 -0
- package/dist/use-pausable-hover.js +100 -0
- package/dist/use-pulse-style.d.ts +3 -0
- package/dist/use-pulse-style.js +19 -0
- package/dist/use-sticky-height.d.ts +10 -0
- package/dist/use-sticky-height.js +37 -0
- package/l10n.json +12 -0
- package/package.json +84 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Astralarium
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@astralarium/repo-ui`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Headless React UI following Base UI patterns.
|
|
4
|
+
|
|
5
|
+
App setup:
|
|
6
|
+
|
|
7
|
+
- `themeScript(cookie)` in `<head>` to apply theme pre-paint.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
const [title, setTitle] = useDebounce(draft.title, (title) => save({ title }));
|
|
11
|
+
|
|
12
|
+
// debounced search param (./router)
|
|
13
|
+
const [query, setQuery] = useSearchParam("q");
|
|
14
|
+
|
|
15
|
+
// sticky headers: level n sits at `top: var(--sticky-h<n>)`
|
|
16
|
+
<StickyHeightProvider>{children}</StickyHeightProvider>;
|
|
17
|
+
<h2
|
|
18
|
+
ref={useStickyHeight(1)}
|
|
19
|
+
style={{ position: "sticky", top: "var(--sticky-h1)" }}
|
|
20
|
+
/>;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Parts carry `data-slot` and state attributes; the app supplies styling.
|
|
24
|
+
Responsive tables: below its breakpoint, a column collapses into the lead cell;
|
|
25
|
+
the app's CSS hides the inactive copy.
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
const columns = [
|
|
29
|
+
{ key: "name", label: "Name", render: (row: Row) => row.name },
|
|
30
|
+
{
|
|
31
|
+
key: "rank",
|
|
32
|
+
label: "Rank",
|
|
33
|
+
breakpoint: "md",
|
|
34
|
+
render: (row: Row) => row.rank,
|
|
35
|
+
},
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
<table>
|
|
39
|
+
<ResponsiveTH columns={columns} />
|
|
40
|
+
<tbody>
|
|
41
|
+
{rows.map((row, index) => (
|
|
42
|
+
<ResponsiveTR key={row.id} item={row} index={index} columns={columns} />
|
|
43
|
+
))}
|
|
44
|
+
</tbody>
|
|
45
|
+
</table>;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
[data-slot="responsive-cell"][data-breakpoint="md"],
|
|
50
|
+
[data-slot="responsive-header"][data-breakpoint="md"] {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
@media (width >= 48rem) {
|
|
54
|
+
[data-slot="responsive-cell"][data-breakpoint="md"],
|
|
55
|
+
[data-slot="responsive-header"][data-breakpoint="md"] {
|
|
56
|
+
display: table-cell;
|
|
57
|
+
}
|
|
58
|
+
[data-slot="responsive-detail"][data-breakpoint="md"] {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
2
|
+
/** `ErrorBoundary` props. */
|
|
3
|
+
export interface ErrorBoundaryProps {
|
|
4
|
+
/** A change clears a caught fault and renders `children` afresh. */
|
|
5
|
+
resetKey: string;
|
|
6
|
+
/** Shown in place of `children` after a fault. */
|
|
7
|
+
fallback: ReactNode;
|
|
8
|
+
/** Reports each caught fault. */
|
|
9
|
+
onCatch?: (error: unknown, info: ErrorInfo) => void;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
interface ErrorBoundaryState {
|
|
13
|
+
/** The caught fault, boxed: a thrown value may be falsy. */
|
|
14
|
+
caught: {
|
|
15
|
+
error: unknown;
|
|
16
|
+
} | null;
|
|
17
|
+
/** The `resetKey` the fault belongs to. */
|
|
18
|
+
resetKey: string;
|
|
19
|
+
}
|
|
20
|
+
/** Catches render faults below, showing `fallback` until `resetKey`
|
|
21
|
+
* changes. */
|
|
22
|
+
export declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
23
|
+
state: ErrorBoundaryState;
|
|
24
|
+
static getDerivedStateFromError(error: unknown): Partial<ErrorBoundaryState>;
|
|
25
|
+
static getDerivedStateFromProps(props: ErrorBoundaryProps, state: ErrorBoundaryState): Partial<ErrorBoundaryState> | null;
|
|
26
|
+
componentDidCatch(error: unknown, info: ErrorInfo): void;
|
|
27
|
+
render(): ReactNode;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Component } from "react";
|
|
4
|
+
/** Catches render faults below, showing `fallback` until `resetKey`
|
|
5
|
+
* changes. */
|
|
6
|
+
export class ErrorBoundary extends Component {
|
|
7
|
+
state = {
|
|
8
|
+
caught: null,
|
|
9
|
+
resetKey: this.props.resetKey
|
|
10
|
+
};
|
|
11
|
+
static getDerivedStateFromError(error) {
|
|
12
|
+
return {
|
|
13
|
+
caught: {
|
|
14
|
+
error
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
static getDerivedStateFromProps(props, state) {
|
|
19
|
+
return props.resetKey === state.resetKey ? null : {
|
|
20
|
+
caught: null,
|
|
21
|
+
resetKey: props.resetKey
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
componentDidCatch(error, info) {
|
|
25
|
+
this.props.onCatch?.(error, info);
|
|
26
|
+
}
|
|
27
|
+
render() {
|
|
28
|
+
return this.state.caught ? this.props.fallback : this.props.children;
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Headless React building blocks: theme, tree, page tree, timeline, sorted
|
|
2
|
+
* lookups, status, error boundary, storage, sticky headers, responsive
|
|
3
|
+
* tables, external scripts and small hooks. */
|
|
4
|
+
export * from "./error-boundary.tsx";
|
|
5
|
+
export * from "./lazy-google-analytics.tsx";
|
|
6
|
+
export * from "./page-tree.ts";
|
|
7
|
+
export * from "./responsive-table.tsx";
|
|
8
|
+
export * from "./sorted.ts";
|
|
9
|
+
export * from "./status.tsx";
|
|
10
|
+
export * from "./sticky-height.tsx";
|
|
11
|
+
export * from "./storage.ts";
|
|
12
|
+
export * from "./theme/index.ts";
|
|
13
|
+
export * from "./timeline.ts";
|
|
14
|
+
export * from "./tree/index.ts";
|
|
15
|
+
export * from "./use-copy-feedback.ts";
|
|
16
|
+
export * from "./use-debounce.ts";
|
|
17
|
+
export * from "./use-external-script.ts";
|
|
18
|
+
export * from "./use-frame.ts";
|
|
19
|
+
export * from "./use-hover-focus.ts";
|
|
20
|
+
export * from "./use-media-query.ts";
|
|
21
|
+
export * from "./use-pausable-hover.ts";
|
|
22
|
+
export * from "./use-pulse-style.ts";
|
|
23
|
+
export { useStickyHeight } from "./use-sticky-height.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Headless React building blocks: theme, tree, page tree, timeline, sorted
|
|
2
|
+
* lookups, status, error boundary, storage, sticky headers, responsive
|
|
3
|
+
* tables, external scripts and small hooks. */
|
|
4
|
+
export * from "./error-boundary.js";
|
|
5
|
+
export * from "./lazy-google-analytics.js";
|
|
6
|
+
export * from "./page-tree.js";
|
|
7
|
+
export * from "./responsive-table.js";
|
|
8
|
+
export * from "./sorted.js";
|
|
9
|
+
export * from "./status.js";
|
|
10
|
+
export * from "./sticky-height.js";
|
|
11
|
+
export * from "./storage.js";
|
|
12
|
+
export * from "./theme/index.js";
|
|
13
|
+
export * from "./timeline.js";
|
|
14
|
+
export * from "./tree/index.js";
|
|
15
|
+
export * from "./use-copy-feedback.js";
|
|
16
|
+
export * from "./use-debounce.js";
|
|
17
|
+
export * from "./use-external-script.js";
|
|
18
|
+
export * from "./use-frame.js";
|
|
19
|
+
export * from "./use-hover-focus.js";
|
|
20
|
+
export * from "./use-media-query.js";
|
|
21
|
+
export * from "./use-pausable-hover.js";
|
|
22
|
+
export * from "./use-pulse-style.js";
|
|
23
|
+
export { useStickyHeight } from "./use-sticky-height.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@astralarium/repo-ui/router/tab-bar.messages#barName": {
|
|
3
|
+
"defaultMessage": "Tabs",
|
|
4
|
+
"description": "Accessible name of a tab bar switching between page sections. Shows: screen readers."
|
|
5
|
+
},
|
|
6
|
+
"@astralarium/repo-ui/slider/slider.messages#rangeEnd": {
|
|
7
|
+
"defaultMessage": "{value}, range end",
|
|
8
|
+
"description": "Spoken value of a range slider's last thumb. Shows: screen readers, sliders with several thumbs. Arguments: value (string) the thumb's formatted value."
|
|
9
|
+
},
|
|
10
|
+
"@astralarium/repo-ui/slider/slider.messages#rangeStart": {
|
|
11
|
+
"defaultMessage": "{value}, range start",
|
|
12
|
+
"description": "Spoken value of a range slider's first thumb. Shows: screen readers, sliders with several thumbs. Arguments: value (string) the thumb's formatted value."
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { c as _c } from "react/compiler-runtime";
|
|
4
|
+
import { useExternalScript } from "./use-external-script.js";
|
|
5
|
+
/** Queues gtag's start and `gaId`'s config ahead of its script. */
|
|
6
|
+
function initGtag(gaId) {
|
|
7
|
+
const dataLayer = window.dataLayer ??= [];
|
|
8
|
+
function gtag(..._args) {
|
|
9
|
+
// eslint-disable-next-line prefer-rest-params -- gtag.js reads queued `arguments` objects; arrays go unread
|
|
10
|
+
dataLayer.push(arguments);
|
|
11
|
+
}
|
|
12
|
+
gtag("js", new Date());
|
|
13
|
+
gtag("config", gaId);
|
|
14
|
+
}
|
|
15
|
+
/** Loads Google Analytics property `gaId` once the browser idles. */
|
|
16
|
+
export function LazyGoogleAnalytics(t0) {
|
|
17
|
+
const $ = _c(2);
|
|
18
|
+
const {
|
|
19
|
+
gaId
|
|
20
|
+
} = t0;
|
|
21
|
+
let t1;
|
|
22
|
+
if ($[0] !== gaId) {
|
|
23
|
+
t1 = {
|
|
24
|
+
idle: true,
|
|
25
|
+
init: () => initGtag(gaId)
|
|
26
|
+
};
|
|
27
|
+
$[0] = gaId;
|
|
28
|
+
$[1] = t1;
|
|
29
|
+
} else {
|
|
30
|
+
t1 = $[1];
|
|
31
|
+
}
|
|
32
|
+
useExternalScript(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(gaId)}`, t1);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The router's static paths as a tree, and a breadcrumb trail through it. */
|
|
2
|
+
/** A static path: a page, an ancestor of pages, or both. */
|
|
3
|
+
export interface PageNode {
|
|
4
|
+
/** The path's last segment; empty at the root. */
|
|
5
|
+
segment: string;
|
|
6
|
+
/** The path from the root. */
|
|
7
|
+
path: string;
|
|
8
|
+
/** A page serves `path`. */
|
|
9
|
+
page: boolean;
|
|
10
|
+
/** Static paths one segment deeper, in first-given order. */
|
|
11
|
+
children: PageNode[];
|
|
12
|
+
}
|
|
13
|
+
/** One path segment of the trail. */
|
|
14
|
+
export interface Crumb {
|
|
15
|
+
/** The path through this segment. */
|
|
16
|
+
path: string;
|
|
17
|
+
/** The static paths beside `path`, itself included; only itself for
|
|
18
|
+
* the trail's first crumb; empty off the tree. */
|
|
19
|
+
siblings: PageNode[];
|
|
20
|
+
}
|
|
21
|
+
/** `paths` as a tree rooted at `/`. Paths with a param or splat segment
|
|
22
|
+
* (`$`) are left out. */
|
|
23
|
+
export declare function pageTree(paths: Iterable<string>): PageNode;
|
|
24
|
+
/** `pathname`'s segments, located in `tree`. */
|
|
25
|
+
export declare function crumbsOf(pathname: string, tree: PageNode): Crumb[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** The router's static paths as a tree, and a breadcrumb trail through it. */
|
|
2
|
+
/** `paths` as a tree rooted at `/`. Paths with a param or splat segment
|
|
3
|
+
* (`$`) are left out. */
|
|
4
|
+
export function pageTree(paths) {
|
|
5
|
+
const root = {
|
|
6
|
+
segment: "",
|
|
7
|
+
path: "/",
|
|
8
|
+
page: false,
|
|
9
|
+
children: []
|
|
10
|
+
};
|
|
11
|
+
for (const path of paths) {
|
|
12
|
+
const segments = path.split("/").filter(Boolean);
|
|
13
|
+
if (segments.some(segment => segment.includes("$"))) continue;
|
|
14
|
+
let node = root;
|
|
15
|
+
for (const segment of segments) {
|
|
16
|
+
let child = node.children.find(c => c.segment === segment);
|
|
17
|
+
if (!child) {
|
|
18
|
+
child = {
|
|
19
|
+
segment,
|
|
20
|
+
path: `${node === root ? "" : node.path}/${segment}`,
|
|
21
|
+
page: false,
|
|
22
|
+
children: []
|
|
23
|
+
};
|
|
24
|
+
node.children.push(child);
|
|
25
|
+
}
|
|
26
|
+
node = child;
|
|
27
|
+
}
|
|
28
|
+
node.page = true;
|
|
29
|
+
}
|
|
30
|
+
return root;
|
|
31
|
+
}
|
|
32
|
+
/** `pathname`'s segments, located in `tree`. */
|
|
33
|
+
export function crumbsOf(pathname, tree) {
|
|
34
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
35
|
+
let parent;
|
|
36
|
+
let node = tree;
|
|
37
|
+
return segments.map((segment, i) => {
|
|
38
|
+
parent = node;
|
|
39
|
+
node = parent?.children.find(c => c.segment === segment);
|
|
40
|
+
return {
|
|
41
|
+
path: `/${segments.slice(0, i + 1).join("/")}`,
|
|
42
|
+
siblings: !node ? [] : i > 0 && parent ? parent.children : [node]
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
/** Tailwind's breakpoints, smallest first. */
|
|
3
|
+
declare const TAILWIND_BREAKPOINTS: readonly ["sm", "md", "lg", "xl", "2xl"];
|
|
4
|
+
/** A Tailwind breakpoint. */
|
|
5
|
+
export type TailwindBreakpoint = (typeof TAILWIND_BREAKPOINTS)[number];
|
|
6
|
+
/** A column's header: its label and where it collapses. */
|
|
7
|
+
export interface ResponsiveColumnLabel<B extends string = TailwindBreakpoint> {
|
|
8
|
+
key: string;
|
|
9
|
+
/** Header text; also the detail's `<dt>`, visually hidden. */
|
|
10
|
+
label?: ReactNode;
|
|
11
|
+
/** From this breakpoint up, the column shows as a cell; below it, as a
|
|
12
|
+
* detail in the lead cell. Absent, or on the lead column: always a
|
|
13
|
+
* cell. */
|
|
14
|
+
breakpoint?: B;
|
|
15
|
+
/** Class of the column's `<th>` and `<td>`. */
|
|
16
|
+
className?: string;
|
|
17
|
+
/** Class of the column's `<dd>`. */
|
|
18
|
+
detailClassName?: string;
|
|
19
|
+
colSpan?: number;
|
|
20
|
+
}
|
|
21
|
+
/** A column: its header and how it renders a row's item. */
|
|
22
|
+
export interface ResponsiveColumn<T, B extends string = TailwindBreakpoint> extends ResponsiveColumnLabel<B> {
|
|
23
|
+
/** The cell's content for the row at `index`. */
|
|
24
|
+
render: (item: T, index: number) => ReactNode;
|
|
25
|
+
/** The detail's content; `render`'s by default. */
|
|
26
|
+
renderDetail?: (item: T, index: number) => ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/** `ResponsiveTH` props: a `<thead>`'s, and the columns it heads. */
|
|
29
|
+
export interface ResponsiveTHProps<B extends string = TailwindBreakpoint> extends Omit<ComponentProps<"thead">, "children"> {
|
|
30
|
+
columns: readonly ResponsiveColumnLabel<B>[];
|
|
31
|
+
}
|
|
32
|
+
/** The table head: one `<th>` per column. */
|
|
33
|
+
export declare function ResponsiveTH<B extends string = TailwindBreakpoint>({ columns, ...props }: ResponsiveTHProps<B>): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
/** `ResponsiveTR` props: a `<tr>`'s, the item it renders, and the app's
|
|
35
|
+
* breakpoints, required for breakpoints beyond Tailwind's. */
|
|
36
|
+
export type ResponsiveTRProps<T, B extends string = TailwindBreakpoint> = ResponsiveTRBaseProps<T, B> & ([B] extends [TailwindBreakpoint] ? Partial<ResponsiveTRBreakpoints<B>> : ResponsiveTRBreakpoints<B>);
|
|
37
|
+
/** `ResponsiveTR` props besides `breakpoints`. */
|
|
38
|
+
interface ResponsiveTRBaseProps<T, B extends string = TailwindBreakpoint> extends Omit<ComponentProps<"tr">, "children"> {
|
|
39
|
+
item: T;
|
|
40
|
+
/** The row's index, passed to `render`. */
|
|
41
|
+
index: number;
|
|
42
|
+
columns: readonly ResponsiveColumn<T, B>[];
|
|
43
|
+
/** Renders the lead as the row's header, `<th scope="row">`. */
|
|
44
|
+
rowHeader?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** `ResponsiveTR`'s breakpoint order. */
|
|
47
|
+
interface ResponsiveTRBreakpoints<B extends string> {
|
|
48
|
+
/** The app's breakpoints, smallest first; Tailwind's when omitted. */
|
|
49
|
+
breakpoints: readonly B[];
|
|
50
|
+
}
|
|
51
|
+
/** A body row: its item's cells, the collapsing columns repeated as
|
|
52
|
+
* details in the lead cell. */
|
|
53
|
+
export declare function ResponsiveTR<T, B extends string = TailwindBreakpoint>({ item, index, columns, breakpoints, rowHeader, ...props }: ResponsiveTRProps<T, B>): import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Table rows whose columns collapse below a breakpoint into the lead
|
|
4
|
+
* cell's `<dl>`. Parts carry `data-slot`; `data-breakpoint` names where
|
|
5
|
+
* a part's layout changes, and the app's CSS shows or hides each part
|
|
6
|
+
* there:
|
|
7
|
+
* - `responsive-header` / `responsive-cell`: the column's `<th>` / `<td>`,
|
|
8
|
+
* a cell from its breakpoint up. The lead column is always a cell.
|
|
9
|
+
* - `responsive-lead`: the row's first `<td>` (or `<th scope="row">`),
|
|
10
|
+
* holding the details; the lowest breakpoint, where the first column
|
|
11
|
+
* leaves it.
|
|
12
|
+
* - `responsive-details`: the lead's `<dl>`; the highest breakpoint, where
|
|
13
|
+
* the last column leaves it.
|
|
14
|
+
* - `responsive-detail`: a column's `<dt>` + `<dd>` in the `<dl>`, until
|
|
15
|
+
* its breakpoint.
|
|
16
|
+
*/
|
|
17
|
+
import { visuallyHidden } from "@base-ui/utils/visuallyHidden";
|
|
18
|
+
/** Tailwind's breakpoints, smallest first. */
|
|
19
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
20
|
+
const TAILWIND_BREAKPOINTS = ["sm", "md", "lg", "xl", "2xl"];
|
|
21
|
+
/** The table head: one `<th>` per column. */
|
|
22
|
+
export function ResponsiveTH(t0) {
|
|
23
|
+
const $ = _c(10);
|
|
24
|
+
let columns;
|
|
25
|
+
let props;
|
|
26
|
+
if ($[0] !== t0) {
|
|
27
|
+
({
|
|
28
|
+
columns,
|
|
29
|
+
...props
|
|
30
|
+
} = t0);
|
|
31
|
+
$[0] = t0;
|
|
32
|
+
$[1] = columns;
|
|
33
|
+
$[2] = props;
|
|
34
|
+
} else {
|
|
35
|
+
columns = $[1];
|
|
36
|
+
props = $[2];
|
|
37
|
+
}
|
|
38
|
+
let t1;
|
|
39
|
+
if ($[3] !== columns) {
|
|
40
|
+
t1 = columns.map(_temp);
|
|
41
|
+
$[3] = columns;
|
|
42
|
+
$[4] = t1;
|
|
43
|
+
} else {
|
|
44
|
+
t1 = $[4];
|
|
45
|
+
}
|
|
46
|
+
let t2;
|
|
47
|
+
if ($[5] !== t1) {
|
|
48
|
+
t2 = /*#__PURE__*/_jsx("tr", {
|
|
49
|
+
children: t1
|
|
50
|
+
});
|
|
51
|
+
$[5] = t1;
|
|
52
|
+
$[6] = t2;
|
|
53
|
+
} else {
|
|
54
|
+
t2 = $[6];
|
|
55
|
+
}
|
|
56
|
+
let t3;
|
|
57
|
+
if ($[7] !== props || $[8] !== t2) {
|
|
58
|
+
t3 = /*#__PURE__*/_jsx("thead", {
|
|
59
|
+
"data-slot": "responsive-head",
|
|
60
|
+
...props,
|
|
61
|
+
children: t2
|
|
62
|
+
});
|
|
63
|
+
$[7] = props;
|
|
64
|
+
$[8] = t2;
|
|
65
|
+
$[9] = t3;
|
|
66
|
+
} else {
|
|
67
|
+
t3 = $[9];
|
|
68
|
+
}
|
|
69
|
+
return t3;
|
|
70
|
+
}
|
|
71
|
+
/** The lowest and highest of `breakpoints` the columns name. */
|
|
72
|
+
function _temp(column, index) {
|
|
73
|
+
return /*#__PURE__*/_jsx("th", {
|
|
74
|
+
scope: "col",
|
|
75
|
+
colSpan: column.colSpan,
|
|
76
|
+
className: column.className,
|
|
77
|
+
"data-slot": "responsive-header",
|
|
78
|
+
"data-breakpoint": index === 0 ? undefined : column.breakpoint,
|
|
79
|
+
children: column.label
|
|
80
|
+
}, column.key);
|
|
81
|
+
}
|
|
82
|
+
function breakpointRange(columns, breakpoints) {
|
|
83
|
+
const named = breakpoints.filter(breakpoint => columns.some(column => column.breakpoint === breakpoint));
|
|
84
|
+
return {
|
|
85
|
+
min: named[0],
|
|
86
|
+
max: named.at(-1)
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** A body row: its item's cells, the collapsing columns repeated as
|
|
90
|
+
* details in the lead cell. */
|
|
91
|
+
export function ResponsiveTR(t0) {
|
|
92
|
+
const $ = _c(14);
|
|
93
|
+
let columns;
|
|
94
|
+
let index;
|
|
95
|
+
let item;
|
|
96
|
+
let props;
|
|
97
|
+
let t1;
|
|
98
|
+
let t2;
|
|
99
|
+
if ($[0] !== t0) {
|
|
100
|
+
({
|
|
101
|
+
item,
|
|
102
|
+
index,
|
|
103
|
+
columns,
|
|
104
|
+
breakpoints: t1,
|
|
105
|
+
rowHeader: t2,
|
|
106
|
+
...props
|
|
107
|
+
} = t0);
|
|
108
|
+
$[0] = t0;
|
|
109
|
+
$[1] = columns;
|
|
110
|
+
$[2] = index;
|
|
111
|
+
$[3] = item;
|
|
112
|
+
$[4] = props;
|
|
113
|
+
$[5] = t1;
|
|
114
|
+
$[6] = t2;
|
|
115
|
+
} else {
|
|
116
|
+
columns = $[1];
|
|
117
|
+
index = $[2];
|
|
118
|
+
item = $[3];
|
|
119
|
+
props = $[4];
|
|
120
|
+
t1 = $[5];
|
|
121
|
+
t2 = $[6];
|
|
122
|
+
}
|
|
123
|
+
const breakpoints = t1 === undefined ? TAILWIND_BREAKPOINTS : t1;
|
|
124
|
+
const rowHeader = t2 === undefined ? false : t2;
|
|
125
|
+
let t3;
|
|
126
|
+
if ($[7] !== breakpoints || $[8] !== columns || $[9] !== index || $[10] !== item || $[11] !== props || $[12] !== rowHeader) {
|
|
127
|
+
const [lead, ...rest] = columns;
|
|
128
|
+
const collapsing = rest.filter(_temp2);
|
|
129
|
+
const {
|
|
130
|
+
min,
|
|
131
|
+
max
|
|
132
|
+
} = breakpointRange(collapsing, breakpoints);
|
|
133
|
+
const Lead = rowHeader ? "th" : "td";
|
|
134
|
+
t3 = /*#__PURE__*/_jsxs("tr", {
|
|
135
|
+
"data-slot": "responsive-row",
|
|
136
|
+
...props,
|
|
137
|
+
children: [lead ? /*#__PURE__*/_jsxs(Lead, {
|
|
138
|
+
scope: rowHeader ? "row" : undefined,
|
|
139
|
+
colSpan: lead.colSpan,
|
|
140
|
+
className: lead.className,
|
|
141
|
+
"data-slot": "responsive-lead",
|
|
142
|
+
"data-breakpoint": min,
|
|
143
|
+
children: [lead.render(item, index), collapsing.length > 0 ? /*#__PURE__*/_jsx("dl", {
|
|
144
|
+
"data-slot": "responsive-details",
|
|
145
|
+
"data-breakpoint": max,
|
|
146
|
+
children: collapsing.map(column_0 => /*#__PURE__*/_jsxs("div", {
|
|
147
|
+
"data-slot": "responsive-detail",
|
|
148
|
+
"data-breakpoint": column_0.breakpoint,
|
|
149
|
+
children: [/*#__PURE__*/_jsx("dt", {
|
|
150
|
+
style: visuallyHidden,
|
|
151
|
+
children: column_0.label
|
|
152
|
+
}), /*#__PURE__*/_jsx("dd", {
|
|
153
|
+
className: column_0.detailClassName,
|
|
154
|
+
children: (column_0.renderDetail ?? column_0.render)(item, index)
|
|
155
|
+
})]
|
|
156
|
+
}, column_0.key))
|
|
157
|
+
}) : null]
|
|
158
|
+
}) : null, rest.map(column_1 => /*#__PURE__*/_jsx("td", {
|
|
159
|
+
colSpan: column_1.colSpan,
|
|
160
|
+
className: column_1.className,
|
|
161
|
+
"data-slot": "responsive-cell",
|
|
162
|
+
"data-breakpoint": column_1.breakpoint,
|
|
163
|
+
children: column_1.render(item, index)
|
|
164
|
+
}, column_1.key))]
|
|
165
|
+
});
|
|
166
|
+
$[7] = breakpoints;
|
|
167
|
+
$[8] = columns;
|
|
168
|
+
$[9] = index;
|
|
169
|
+
$[10] = item;
|
|
170
|
+
$[11] = props;
|
|
171
|
+
$[12] = rowHeader;
|
|
172
|
+
$[13] = t3;
|
|
173
|
+
} else {
|
|
174
|
+
t3 = $[13];
|
|
175
|
+
}
|
|
176
|
+
return t3;
|
|
177
|
+
}
|
|
178
|
+
function _temp2(column) {
|
|
179
|
+
return column.breakpoint !== undefined;
|
|
180
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type RouteComponent } from "@tanstack/react-router";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/** A dev page module: its default export renders the page. */
|
|
4
|
+
export interface DevPageModule {
|
|
5
|
+
default: () => ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The page module's loader, or `false` once the build compiled it out.
|
|
9
|
+
* Call sites write `import.meta.env.DEV && (() => import("~dev/…"))`: the
|
|
10
|
+
* literal guard satisfies `dev-only/imports`, and `vite build` folds it
|
|
11
|
+
* to `false`.
|
|
12
|
+
*/
|
|
13
|
+
export type DevPageLoad = false | (() => Promise<DevPageModule>);
|
|
14
|
+
/** How a dev page renders. */
|
|
15
|
+
export interface DevRouteOptions {
|
|
16
|
+
/** Render in the browser only (`ssr: false`): canvas scenes. */
|
|
17
|
+
clientOnly?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** TanStack route options; spread beside `validateSearch` and the like. */
|
|
20
|
+
export interface DevRoute {
|
|
21
|
+
/** Throws `notFound()` where the page was compiled out. */
|
|
22
|
+
beforeLoad: () => void;
|
|
23
|
+
/** The lazily loaded page; renders nothing where compiled out. */
|
|
24
|
+
component: RouteComponent;
|
|
25
|
+
/** `false` with `clientOnly`; absent otherwise. */
|
|
26
|
+
ssr?: false;
|
|
27
|
+
}
|
|
28
|
+
/** Route options for the dev page `load` loads; a 404 where compiled out. */
|
|
29
|
+
export declare function devRoute(load: DevPageLoad, { clientOnly }?: DevRouteOptions): DevRoute;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { lazyRouteComponent, notFound } from "@tanstack/react-router";
|
|
2
|
+
/** Route options for the dev page `load` loads; a 404 where compiled out. */
|
|
3
|
+
export function devRoute(load, {
|
|
4
|
+
clientOnly = false
|
|
5
|
+
} = {}) {
|
|
6
|
+
return {
|
|
7
|
+
beforeLoad: () => {
|
|
8
|
+
if (!load) throw notFound();
|
|
9
|
+
},
|
|
10
|
+
component: load ? lazyRouteComponent(load) : () => null,
|
|
11
|
+
...(clientOnly ? {
|
|
12
|
+
ssr: false
|
|
13
|
+
} : {})
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import type { Tab } from "./tabs.ts";
|
|
3
|
+
/** `TabBar` props: a `<nav>`'s, and its tabs. */
|
|
4
|
+
export interface TabBarProps extends Omit<ComponentProps<"nav">, "children"> {
|
|
5
|
+
tabs: readonly Tab[];
|
|
6
|
+
/** Class of each tab. */
|
|
7
|
+
tabClassName?: string;
|
|
8
|
+
}
|
|
9
|
+
/** A tab bar: router `Link`s current on their exact path, search aside,
|
|
10
|
+
* unless the link sets `activeOptions`; buttons current while `active`.
|
|
11
|
+
* Named "Tabs" unless `aria-label` says otherwise. */
|
|
12
|
+
export declare function TabBar({ tabs, tabClassName, ...props }: TabBarProps): import("react/jsx-runtime").JSX.Element;
|