@treeseed/core 0.8.8 → 0.8.10
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/components/SiteTitle.astro +2 -2
- package/dist/components/content/ContentStatusLegend.astro +4 -4
- package/dist/components/docs/BookFontControls.astro +9 -9
- package/dist/components/docs/DesktopSidebarToggle.astro +8 -8
- package/dist/components/docs/Footer.astro +7 -91
- package/dist/components/docs/Header.astro +9 -2
- package/dist/components/docs/PageTitle.astro +1 -1
- package/dist/components/docs/ThemeSelect.astro +3 -1
- package/dist/components/forms/ContactForm.astro +21 -21
- package/dist/components/forms/FooterSubscribeForm.astro +9 -9
- package/dist/components/site/BookList.astro +7 -7
- package/dist/components/site/CTASection.astro +4 -4
- package/dist/components/site/ChronicleList.astro +6 -6
- package/dist/components/site/Hero.astro +3 -3
- package/dist/components/site/PathCard.astro +5 -5
- package/dist/components/site/ProfileList.astro +5 -5
- package/dist/components/site/RouteNotFound.astro +6 -6
- package/dist/components/site/SectionIntro.astro +3 -3
- package/dist/components/site/StageBanner.astro +2 -2
- package/dist/components/site/TrustCallout.astro +3 -3
- package/dist/components/ui/data/ActionList.astro +51 -0
- package/dist/components/ui/data/Badge.astro +19 -0
- package/dist/components/ui/data/DataTable.astro +51 -0
- package/dist/components/ui/data/KeyValueList.astro +28 -0
- package/dist/components/ui/data/MetricCard.astro +25 -0
- package/dist/components/ui/data/MetricGrid.astro +27 -0
- package/dist/components/ui/data/StatusPill.astro +20 -0
- package/dist/components/ui/forms/Button.astro +52 -0
- package/dist/components/ui/forms/Field.astro +39 -0
- package/dist/components/ui/forms/FormActions.astro +12 -0
- package/dist/components/ui/forms/PasswordMeter.astro +80 -0
- package/dist/components/ui/forms/RadioGroup.astro +55 -0
- package/dist/components/ui/forms/Select.astro +44 -0
- package/dist/components/ui/forms/TextInput.astro +58 -0
- package/dist/components/ui/forms/Textarea.astro +45 -0
- package/dist/components/ui/layout/PageHeader.astro +45 -0
- package/dist/components/ui/shell/AppShell.astro +112 -0
- package/dist/components/ui/shell/BottomNav.astro +35 -0
- package/dist/components/ui/shell/ProjectHeader.astro +66 -0
- package/dist/components/ui/shell/PublicFooter.astro +39 -0
- package/dist/components/ui/shell/PublicShell.astro +179 -0
- package/dist/components/ui/shell/RailNav.astro +35 -0
- package/dist/components/ui/shell/TopBar.astro +52 -0
- package/dist/components/ui/surface/Card.astro +46 -0
- package/dist/components/ui/surface/EmptyState.astro +45 -0
- package/dist/components/ui/surface/Panel.astro +54 -0
- package/dist/components/ui/theme/ThemeMenu.astro +32 -0
- package/dist/components/ui/theme/ThemePreviewSwatch.astro +18 -0
- package/dist/components/ui/theme/ThemeScript.astro +111 -0
- package/dist/components/ui/theme/ThemeSelector.astro +202 -0
- package/dist/components/ui/types.js +0 -0
- package/dist/dev-watch.d.ts +6 -2
- package/dist/dev-watch.js +12 -3
- package/dist/dev.d.ts +10 -2
- package/dist/dev.js +352 -68
- package/dist/layouts/AuthoredEntryLayout.astro +27 -27
- package/dist/layouts/BookLayout.astro +10 -10
- package/dist/layouts/ContentLayout.astro +4 -4
- package/dist/layouts/MainLayout.astro +66 -193
- package/dist/layouts/NoteLayout.astro +6 -6
- package/dist/layouts/ProfileLayout.astro +17 -17
- package/dist/middleware/starlightRouteData.js +20 -14
- package/dist/pages/404.astro +8 -8
- package/dist/pages/[slug].astro +1 -1
- package/dist/pages/books/[slug].astro +5 -5
- package/dist/pages/contact.astro +4 -4
- package/dist/pages/docs-runtime/[...slug].astro +12 -12
- package/dist/pages/docs-runtime/index.astro +13 -13
- package/dist/pages/index.astro +28 -28
- package/dist/pages/ui/index.astro +216 -0
- package/dist/scripts/dev-platform.js +7 -1
- package/dist/site.js +53 -5
- package/dist/styles/app-shell.css +597 -0
- package/dist/styles/forms.css +258 -0
- package/dist/styles/global.css +125 -120
- package/dist/styles/prose.css +11 -11
- package/dist/styles/theme.css +177 -0
- package/dist/styles/tokens.css +62 -22
- package/dist/styles/ui.css +551 -0
- package/dist/utils/color-schemes/cedar.js +53 -0
- package/dist/utils/color-schemes/fern.js +53 -0
- package/dist/utils/color-schemes/index.js +13 -0
- package/dist/utils/color-schemes/lichen.js +53 -0
- package/dist/utils/color-schemes/shared.js +33 -0
- package/dist/utils/color-schemes/tidepool.js +53 -0
- package/dist/utils/content-status.js +5 -5
- package/dist/utils/site-config.js +2 -2
- package/dist/utils/starlight-nav.js +13 -7
- package/dist/utils/theme.js +133 -41
- package/package.json +36 -2
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from '../forms/Button.astro';
|
|
3
|
+
import type { ButtonAction } from '../types.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
eyebrow?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
actions?: ButtonAction[];
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
eyebrow,
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
actions = [],
|
|
18
|
+
class: className,
|
|
19
|
+
} = Astro.props as Props;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<header class:list={['ts-page-header', className]}>
|
|
23
|
+
<div class="ts-page-header__content">
|
|
24
|
+
{eyebrow ? <p class="ts-page-header__eyebrow">{eyebrow}</p> : null}
|
|
25
|
+
<h1 class="ts-page-header__title">{title}</h1>
|
|
26
|
+
{description ? <p class="ts-page-header__description">{description}</p> : null}
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
29
|
+
{actions.length > 0 || Astro.slots.has('actions') ? (
|
|
30
|
+
<div class="ts-page-header__actions">
|
|
31
|
+
{actions.map((action) => (
|
|
32
|
+
<Button
|
|
33
|
+
href={action.href}
|
|
34
|
+
type={action.type}
|
|
35
|
+
variant={action.variant ?? 'secondary'}
|
|
36
|
+
ariaLabel={action.ariaLabel}
|
|
37
|
+
disabled={action.disabled}
|
|
38
|
+
>
|
|
39
|
+
{action.label}
|
|
40
|
+
</Button>
|
|
41
|
+
))}
|
|
42
|
+
<slot name="actions" />
|
|
43
|
+
</div>
|
|
44
|
+
) : null}
|
|
45
|
+
</header>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../../../styles/tokens.css';
|
|
3
|
+
import '../../../styles/theme.css';
|
|
4
|
+
import '../../../styles/ui.css';
|
|
5
|
+
import '../../../styles/forms.css';
|
|
6
|
+
import '../../../styles/app-shell.css';
|
|
7
|
+
import ThemeScript from '../theme/ThemeScript.astro';
|
|
8
|
+
import RailNav from './RailNav.astro';
|
|
9
|
+
import BottomNav from './BottomNav.astro';
|
|
10
|
+
import TopBar from './TopBar.astro';
|
|
11
|
+
import Button from '../forms/Button.astro';
|
|
12
|
+
import DevWatchReload from '../../DevWatchReload.astro';
|
|
13
|
+
import type { ButtonAction } from '../types.js';
|
|
14
|
+
import type { ThemePreference } from '../../../utils/theme.js';
|
|
15
|
+
|
|
16
|
+
interface Brand {
|
|
17
|
+
name: string;
|
|
18
|
+
tag?: string;
|
|
19
|
+
href: string;
|
|
20
|
+
logoSrc?: string;
|
|
21
|
+
logoAlt?: string;
|
|
22
|
+
mark?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface NavItem {
|
|
26
|
+
label: string;
|
|
27
|
+
href: string;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
title: string;
|
|
33
|
+
description: string;
|
|
34
|
+
currentPath: string;
|
|
35
|
+
appearance: ThemePreference;
|
|
36
|
+
brand: Brand;
|
|
37
|
+
navItems: NavItem[];
|
|
38
|
+
quickActions?: ButtonAction[];
|
|
39
|
+
bottomNavItems?: NavItem[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
title,
|
|
44
|
+
description,
|
|
45
|
+
currentPath,
|
|
46
|
+
appearance,
|
|
47
|
+
brand,
|
|
48
|
+
navItems,
|
|
49
|
+
quickActions = [],
|
|
50
|
+
bottomNavItems = navItems.slice(0, 5),
|
|
51
|
+
} = Astro.props as Props;
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
<!doctype html>
|
|
55
|
+
<html lang="en">
|
|
56
|
+
<head>
|
|
57
|
+
<meta charset="utf-8" />
|
|
58
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
59
|
+
<title>{title}</title>
|
|
60
|
+
<meta name="description" content={description} />
|
|
61
|
+
<ThemeScript defaultScheme={appearance.scheme} defaultMode={appearance.mode} preferDefaultPreference />
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
<a class="ts-skip-link" href="#main-content">Skip to content</a>
|
|
65
|
+
<div class="ts-app-shell">
|
|
66
|
+
<aside class="ts-app-shell__rail">
|
|
67
|
+
<TopBar brand={brand} />
|
|
68
|
+
<div class="ts-app-shell__rail-context">
|
|
69
|
+
<slot name="railContext" />
|
|
70
|
+
</div>
|
|
71
|
+
<RailNav items={navItems} currentPath={currentPath} />
|
|
72
|
+
{quickActions.length > 0 ? (
|
|
73
|
+
<div class="ts-app-shell__quick-actions">
|
|
74
|
+
<p class="ts-app-shell__eyebrow">Quick actions</p>
|
|
75
|
+
<div class="ts-app-shell__quick-list">
|
|
76
|
+
{quickActions.map((action) => (
|
|
77
|
+
<Button
|
|
78
|
+
href={action.href}
|
|
79
|
+
type={action.type}
|
|
80
|
+
variant={action.variant ?? 'secondary'}
|
|
81
|
+
ariaLabel={action.ariaLabel}
|
|
82
|
+
disabled={action.disabled}
|
|
83
|
+
size="sm"
|
|
84
|
+
>
|
|
85
|
+
{action.label}
|
|
86
|
+
</Button>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
) : null}
|
|
91
|
+
</aside>
|
|
92
|
+
<main class="ts-app-shell__main" id="main-content">
|
|
93
|
+
<TopBar brand={brand} class="ts-app-shell__mobile-top">
|
|
94
|
+
</TopBar>
|
|
95
|
+
<header class="ts-app-shell__header">
|
|
96
|
+
<div class="ts-app-shell__title">
|
|
97
|
+
<h1>{title}</h1>
|
|
98
|
+
<p>{description}</p>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="ts-app-shell__header-actions">
|
|
101
|
+
<slot name="headerAction" />
|
|
102
|
+
</div>
|
|
103
|
+
</header>
|
|
104
|
+
<slot name="projectContext" />
|
|
105
|
+
<slot />
|
|
106
|
+
</main>
|
|
107
|
+
{bottomNavItems.length > 0 ? <BottomNav items={bottomNavItems} currentPath={currentPath} /> : null}
|
|
108
|
+
</div>
|
|
109
|
+
<slot name="sensitiveModal" />
|
|
110
|
+
<DevWatchReload />
|
|
111
|
+
</body>
|
|
112
|
+
</html>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface NavItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
ariaLabel?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
items: NavItem[];
|
|
10
|
+
currentPath: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
items,
|
|
17
|
+
currentPath,
|
|
18
|
+
label = 'Primary',
|
|
19
|
+
class: className,
|
|
20
|
+
} = Astro.props as Props;
|
|
21
|
+
|
|
22
|
+
function isCurrentPath(href: string) {
|
|
23
|
+
if (href === '/') return currentPath === '/';
|
|
24
|
+
if (href.endsWith('/')) return currentPath === href || currentPath.startsWith(href);
|
|
25
|
+
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
26
|
+
}
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<nav class:list={['ts-bottom-nav', className]} aria-label={label}>
|
|
30
|
+
{items.map((item) => (
|
|
31
|
+
<a class="ts-bottom-nav__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
|
|
32
|
+
<span>{item.label}</span>
|
|
33
|
+
</a>
|
|
34
|
+
))}
|
|
35
|
+
</nav>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Badge from '../data/Badge.astro';
|
|
3
|
+
import Button from '../forms/Button.astro';
|
|
4
|
+
import type { ButtonAction } from '../types.js';
|
|
5
|
+
|
|
6
|
+
interface TabItem {
|
|
7
|
+
label: string;
|
|
8
|
+
href: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
title: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
badges?: string[];
|
|
15
|
+
actions?: ButtonAction[];
|
|
16
|
+
tabs?: TabItem[];
|
|
17
|
+
currentPath?: string;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
title,
|
|
23
|
+
description,
|
|
24
|
+
badges = [],
|
|
25
|
+
actions = [],
|
|
26
|
+
tabs = [],
|
|
27
|
+
currentPath = '',
|
|
28
|
+
class: className,
|
|
29
|
+
} = Astro.props as Props;
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<section class:list={['ts-project-header', className]}>
|
|
33
|
+
<div class="ts-project-header__main">
|
|
34
|
+
{badges.length > 0 ? (
|
|
35
|
+
<div class="ts-project-header__badges">
|
|
36
|
+
{badges.map((badge) => <Badge>{badge}</Badge>)}
|
|
37
|
+
</div>
|
|
38
|
+
) : null}
|
|
39
|
+
<h2>{title}</h2>
|
|
40
|
+
{description ? <p>{description}</p> : null}
|
|
41
|
+
</div>
|
|
42
|
+
{actions.length > 0 || Astro.slots.has('actions') ? (
|
|
43
|
+
<div class="ts-project-header__actions">
|
|
44
|
+
{actions.map((action) => (
|
|
45
|
+
<Button
|
|
46
|
+
href={action.href}
|
|
47
|
+
type={action.type}
|
|
48
|
+
variant={action.variant ?? 'secondary'}
|
|
49
|
+
ariaLabel={action.ariaLabel}
|
|
50
|
+
disabled={action.disabled}
|
|
51
|
+
size="sm"
|
|
52
|
+
>
|
|
53
|
+
{action.label}
|
|
54
|
+
</Button>
|
|
55
|
+
))}
|
|
56
|
+
<slot name="actions" />
|
|
57
|
+
</div>
|
|
58
|
+
) : null}
|
|
59
|
+
{tabs.length > 0 ? (
|
|
60
|
+
<nav class="ts-shell-tabs" aria-label="Project">
|
|
61
|
+
{tabs.map((tab) => (
|
|
62
|
+
<a class="ts-shell-tab" href={tab.href} aria-current={currentPath === tab.href ? 'page' : undefined}>{tab.label}</a>
|
|
63
|
+
))}
|
|
64
|
+
</nav>
|
|
65
|
+
) : null}
|
|
66
|
+
</section>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
import FooterSubscribeForm from '../../forms/FooterSubscribeForm.astro';
|
|
3
|
+
import { PROJECT_STAGE } from '../../../utils/content-status';
|
|
4
|
+
import { SITE } from '../../../utils/seo';
|
|
5
|
+
import { SITE_FOOTER_MENU } from '../../../utils/site-config';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
currentPath?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { currentPath = Astro.url.pathname } = Astro.props as Props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<footer class="ts-public-footer">
|
|
15
|
+
<div class="ts-public-footer__inner">
|
|
16
|
+
<div class="ts-public-footer__grid">
|
|
17
|
+
<div class="ts-public-footer__column">
|
|
18
|
+
<p class="ts-public-footer__title">{SITE.name}</p>
|
|
19
|
+
<p class="ts-public-footer__copy">{SITE.summary}</p>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="ts-public-footer__column">
|
|
22
|
+
<p class="ts-public-footer__heading">Project stage</p>
|
|
23
|
+
<p class="ts-public-footer__column-title">{PROJECT_STAGE.label}</p>
|
|
24
|
+
<p class="ts-public-footer__copy">{PROJECT_STAGE.description}</p>
|
|
25
|
+
</div>
|
|
26
|
+
{SITE_FOOTER_MENU.map((group) => (
|
|
27
|
+
<div class="ts-public-footer__column">
|
|
28
|
+
<p class="ts-public-footer__heading">{group.label}</p>
|
|
29
|
+
<div class="ts-public-footer__links">
|
|
30
|
+
{group.items.map((item) => (
|
|
31
|
+
<a href={item.href}>{item.label}</a>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
<FooterSubscribeForm currentPath={currentPath} />
|
|
38
|
+
</div>
|
|
39
|
+
</footer>
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../../../styles/tokens.css';
|
|
3
|
+
import '../../../styles/theme.css';
|
|
4
|
+
import '../../../styles/ui.css';
|
|
5
|
+
import '../../../styles/forms.css';
|
|
6
|
+
import '../../../styles/app-shell.css';
|
|
7
|
+
import ThemeScript from '../theme/ThemeScript.astro';
|
|
8
|
+
import ThemeMenu from '../theme/ThemeMenu.astro';
|
|
9
|
+
import TopBar from './TopBar.astro';
|
|
10
|
+
import Button from '../forms/Button.astro';
|
|
11
|
+
import PublicFooter from './PublicFooter.astro';
|
|
12
|
+
import DevWatchReload from '../../DevWatchReload.astro';
|
|
13
|
+
import type { ButtonAction } from '../types.js';
|
|
14
|
+
import type { ThemePreference } from '../../../utils/theme.js';
|
|
15
|
+
|
|
16
|
+
interface Brand {
|
|
17
|
+
name: string;
|
|
18
|
+
tag?: string;
|
|
19
|
+
href: string;
|
|
20
|
+
logoSrc?: string;
|
|
21
|
+
logoAlt?: string;
|
|
22
|
+
mark?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface NavItem {
|
|
26
|
+
label: string;
|
|
27
|
+
href: string;
|
|
28
|
+
ariaLabel?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface NavGroup {
|
|
32
|
+
label: string;
|
|
33
|
+
items: NavItem[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Props {
|
|
37
|
+
title: string;
|
|
38
|
+
description: string;
|
|
39
|
+
currentPath: string;
|
|
40
|
+
appearance: ThemePreference;
|
|
41
|
+
brand: Brand;
|
|
42
|
+
navItems: NavItem[];
|
|
43
|
+
navGroups?: NavGroup[];
|
|
44
|
+
actions?: ButtonAction[];
|
|
45
|
+
showAppearanceControl?: boolean;
|
|
46
|
+
preferServerAppearance?: boolean;
|
|
47
|
+
contentWidth?: 'shell' | 'content';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
title,
|
|
52
|
+
description,
|
|
53
|
+
currentPath,
|
|
54
|
+
appearance,
|
|
55
|
+
brand,
|
|
56
|
+
navItems,
|
|
57
|
+
navGroups = [],
|
|
58
|
+
actions = [],
|
|
59
|
+
showAppearanceControl = true,
|
|
60
|
+
preferServerAppearance = false,
|
|
61
|
+
contentWidth = 'shell',
|
|
62
|
+
} = Astro.props as Props;
|
|
63
|
+
|
|
64
|
+
function isCurrentPath(href: string) {
|
|
65
|
+
if (href === '/') return currentPath === '/';
|
|
66
|
+
return currentPath === href || currentPath.startsWith(href);
|
|
67
|
+
}
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
<!doctype html>
|
|
71
|
+
<html lang="en">
|
|
72
|
+
<head>
|
|
73
|
+
<meta charset="utf-8" />
|
|
74
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
75
|
+
<title>{title}</title>
|
|
76
|
+
<meta name="description" content={description} />
|
|
77
|
+
<ThemeScript
|
|
78
|
+
defaultScheme={appearance.scheme}
|
|
79
|
+
defaultMode={appearance.mode}
|
|
80
|
+
preferDefaultPreference={preferServerAppearance}
|
|
81
|
+
/>
|
|
82
|
+
<slot name="head" />
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<a class="ts-skip-link" href="#main-content">Skip to content</a>
|
|
86
|
+
<div class:list={['ts-public-shell', contentWidth === 'content' && 'ts-public-shell--content']}>
|
|
87
|
+
<header class="ts-public-shell__header">
|
|
88
|
+
<TopBar brand={brand}>
|
|
89
|
+
<Fragment slot="actions">
|
|
90
|
+
<nav class="ts-public-shell__nav" aria-label="Primary" data-ts-public-nav>
|
|
91
|
+
{navGroups.length > 0 ? (
|
|
92
|
+
navGroups.map((group) => (
|
|
93
|
+
<details class="ts-public-shell__nav-group" data-ts-public-nav-group>
|
|
94
|
+
<summary
|
|
95
|
+
class:list={[
|
|
96
|
+
'ts-public-shell__link ts-public-shell__summary',
|
|
97
|
+
group.items.some((item) => isCurrentPath(item.href)) && 'ts-public-shell__summary--active',
|
|
98
|
+
]}
|
|
99
|
+
>
|
|
100
|
+
{group.label}
|
|
101
|
+
<span aria-hidden="true">▾</span>
|
|
102
|
+
</summary>
|
|
103
|
+
<div class="ts-public-shell__menu">
|
|
104
|
+
{group.items.map((item) => (
|
|
105
|
+
<a
|
|
106
|
+
class="ts-public-shell__menu-link"
|
|
107
|
+
href={item.href}
|
|
108
|
+
aria-label={item.ariaLabel}
|
|
109
|
+
aria-current={isCurrentPath(item.href) ? 'page' : undefined}
|
|
110
|
+
>
|
|
111
|
+
{item.label}
|
|
112
|
+
</a>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</details>
|
|
116
|
+
))
|
|
117
|
+
) : (
|
|
118
|
+
navItems.map((item) => (
|
|
119
|
+
<a class="ts-public-shell__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
|
|
120
|
+
{item.label}
|
|
121
|
+
</a>
|
|
122
|
+
))
|
|
123
|
+
)}
|
|
124
|
+
</nav>
|
|
125
|
+
{showAppearanceControl ? <ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} /> : null}
|
|
126
|
+
{actions.map((action) => (
|
|
127
|
+
<Button
|
|
128
|
+
href={action.href}
|
|
129
|
+
type={action.type}
|
|
130
|
+
variant={action.variant ?? 'secondary'}
|
|
131
|
+
ariaLabel={action.ariaLabel}
|
|
132
|
+
disabled={action.disabled}
|
|
133
|
+
size="sm"
|
|
134
|
+
>
|
|
135
|
+
{action.label}
|
|
136
|
+
</Button>
|
|
137
|
+
))}
|
|
138
|
+
<slot name="actions" />
|
|
139
|
+
</Fragment>
|
|
140
|
+
</TopBar>
|
|
141
|
+
</header>
|
|
142
|
+
<main class="ts-public-shell__main" id="main-content">
|
|
143
|
+
<slot />
|
|
144
|
+
</main>
|
|
145
|
+
{Astro.slots.has('footer') ? <slot name="footer" /> : <PublicFooter currentPath={currentPath} />}
|
|
146
|
+
<slot name="afterFooter" />
|
|
147
|
+
</div>
|
|
148
|
+
<DevWatchReload />
|
|
149
|
+
<script>
|
|
150
|
+
const publicNav = document.querySelector('[data-ts-public-nav]');
|
|
151
|
+
const navGroups = document.querySelectorAll<HTMLDetailsElement>('[data-ts-public-nav-group]');
|
|
152
|
+
|
|
153
|
+
navGroups.forEach((group) => {
|
|
154
|
+
group.addEventListener('toggle', () => {
|
|
155
|
+
if (!group.open) return;
|
|
156
|
+
navGroups.forEach((otherGroup) => {
|
|
157
|
+
if (otherGroup !== group) otherGroup.open = false;
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
document.addEventListener('click', (event) => {
|
|
163
|
+
if (!(publicNav instanceof HTMLElement)) return;
|
|
164
|
+
if (event.target instanceof Node && publicNav.contains(event.target)) return;
|
|
165
|
+
navGroups.forEach((group) => {
|
|
166
|
+
group.open = false;
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
publicNav?.addEventListener('click', (event) => {
|
|
171
|
+
const target = event.target;
|
|
172
|
+
if (!(target instanceof HTMLElement) || !target.closest('a')) return;
|
|
173
|
+
navGroups.forEach((group) => {
|
|
174
|
+
group.open = false;
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
</script>
|
|
178
|
+
</body>
|
|
179
|
+
</html>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface NavItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
ariaLabel?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
items: NavItem[];
|
|
10
|
+
currentPath: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
items,
|
|
17
|
+
currentPath,
|
|
18
|
+
label = 'Application',
|
|
19
|
+
class: className,
|
|
20
|
+
} = Astro.props as Props;
|
|
21
|
+
|
|
22
|
+
function isCurrentPath(href: string) {
|
|
23
|
+
if (href === '/') return currentPath === '/';
|
|
24
|
+
if (href.endsWith('/')) return currentPath === href || currentPath.startsWith(href);
|
|
25
|
+
return currentPath === href || currentPath.startsWith(`${href}/`);
|
|
26
|
+
}
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<nav class:list={['ts-rail-nav', className]} aria-label={label}>
|
|
30
|
+
{items.map((item) => (
|
|
31
|
+
<a class="ts-rail-nav__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
|
|
32
|
+
<span>{item.label}</span>
|
|
33
|
+
</a>
|
|
34
|
+
))}
|
|
35
|
+
</nav>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from '../forms/Button.astro';
|
|
3
|
+
import type { ButtonAction } from '../types.js';
|
|
4
|
+
|
|
5
|
+
interface Brand {
|
|
6
|
+
name: string;
|
|
7
|
+
tag?: string;
|
|
8
|
+
href: string;
|
|
9
|
+
logoSrc?: string;
|
|
10
|
+
logoAlt?: string;
|
|
11
|
+
mark?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
brand: Brand;
|
|
16
|
+
actions?: ButtonAction[];
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
brand,
|
|
22
|
+
actions = [],
|
|
23
|
+
class: className,
|
|
24
|
+
} = Astro.props as Props;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<div class:list={['ts-top-bar', className]}>
|
|
28
|
+
<a class="ts-shell-brand" href={brand.href}>
|
|
29
|
+
<span class="ts-shell-brand__mark">
|
|
30
|
+
{brand.logoSrc ? <img src={brand.logoSrc} alt={brand.logoAlt ?? ''} width="60" height="60" loading="eager" /> : brand.mark}
|
|
31
|
+
</span>
|
|
32
|
+
<span class="ts-shell-brand__text">
|
|
33
|
+
<span class="ts-shell-brand__name">{brand.name}</span>
|
|
34
|
+
{brand.tag ? <span class="ts-shell-brand__tag">{brand.tag}</span> : null}
|
|
35
|
+
</span>
|
|
36
|
+
</a>
|
|
37
|
+
<div class="ts-top-bar__actions">
|
|
38
|
+
<slot name="actions" />
|
|
39
|
+
{actions.map((action) => (
|
|
40
|
+
<Button
|
|
41
|
+
href={action.href}
|
|
42
|
+
type={action.type}
|
|
43
|
+
variant={action.variant ?? 'secondary'}
|
|
44
|
+
ariaLabel={action.ariaLabel}
|
|
45
|
+
disabled={action.disabled}
|
|
46
|
+
size="sm"
|
|
47
|
+
>
|
|
48
|
+
{action.label}
|
|
49
|
+
</Button>
|
|
50
|
+
))}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Tone } from '../types.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
href?: string;
|
|
6
|
+
eyebrow?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
tone?: Tone;
|
|
10
|
+
interactive?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
href,
|
|
16
|
+
eyebrow,
|
|
17
|
+
title,
|
|
18
|
+
description,
|
|
19
|
+
tone = 'default',
|
|
20
|
+
interactive = false,
|
|
21
|
+
class: className,
|
|
22
|
+
} = Astro.props as Props;
|
|
23
|
+
|
|
24
|
+
const classes = ['ts-card', className].filter(Boolean).join(' ');
|
|
25
|
+
const isInteractive = href || interactive;
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
href ? (
|
|
30
|
+
<a href={href} class={classes} data-tone={tone} data-interactive="true">
|
|
31
|
+
{eyebrow ? <p class="ts-card__eyebrow">{eyebrow}</p> : null}
|
|
32
|
+
{title ? <h3 class="ts-card__title">{title}</h3> : null}
|
|
33
|
+
{description ? <p class="ts-card__description">{description}</p> : null}
|
|
34
|
+
<div class="ts-card__body"><slot /></div>
|
|
35
|
+
<slot name="footer" />
|
|
36
|
+
</a>
|
|
37
|
+
) : (
|
|
38
|
+
<article class={classes} data-tone={tone} data-interactive={isInteractive ? 'true' : undefined}>
|
|
39
|
+
{eyebrow ? <p class="ts-card__eyebrow">{eyebrow}</p> : null}
|
|
40
|
+
{title ? <h3 class="ts-card__title">{title}</h3> : null}
|
|
41
|
+
{description ? <p class="ts-card__description">{description}</p> : null}
|
|
42
|
+
<div class="ts-card__body"><slot /></div>
|
|
43
|
+
<slot name="footer" />
|
|
44
|
+
</article>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Button from '../forms/Button.astro';
|
|
3
|
+
import type { ButtonAction } from '../types.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
eyebrow?: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
actions?: ButtonAction[];
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
eyebrow,
|
|
15
|
+
title,
|
|
16
|
+
description,
|
|
17
|
+
actions = [],
|
|
18
|
+
class: className,
|
|
19
|
+
} = Astro.props as Props;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<section class:list={['ts-empty-state', className]}>
|
|
23
|
+
{eyebrow ? <p class="ts-empty-state__eyebrow">{eyebrow}</p> : null}
|
|
24
|
+
<h2 class="ts-empty-state__title">{title}</h2>
|
|
25
|
+
{description ? <p class="ts-empty-state__description">{description}</p> : null}
|
|
26
|
+
<div class="ts-empty-state__body">
|
|
27
|
+
<slot />
|
|
28
|
+
</div>
|
|
29
|
+
{actions.length > 0 || Astro.slots.has('actions') ? (
|
|
30
|
+
<div class="ts-empty-state__actions">
|
|
31
|
+
{actions.map((action) => (
|
|
32
|
+
<Button
|
|
33
|
+
href={action.href}
|
|
34
|
+
type={action.type}
|
|
35
|
+
variant={action.variant ?? 'secondary'}
|
|
36
|
+
ariaLabel={action.ariaLabel}
|
|
37
|
+
disabled={action.disabled}
|
|
38
|
+
>
|
|
39
|
+
{action.label}
|
|
40
|
+
</Button>
|
|
41
|
+
))}
|
|
42
|
+
<slot name="actions" />
|
|
43
|
+
</div>
|
|
44
|
+
) : null}
|
|
45
|
+
</section>
|