@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.
Files changed (90) hide show
  1. package/dist/components/SiteTitle.astro +2 -2
  2. package/dist/components/content/ContentStatusLegend.astro +4 -4
  3. package/dist/components/docs/BookFontControls.astro +9 -9
  4. package/dist/components/docs/DesktopSidebarToggle.astro +8 -8
  5. package/dist/components/docs/Footer.astro +7 -91
  6. package/dist/components/docs/Header.astro +9 -2
  7. package/dist/components/docs/PageTitle.astro +1 -1
  8. package/dist/components/docs/ThemeSelect.astro +3 -1
  9. package/dist/components/forms/ContactForm.astro +21 -21
  10. package/dist/components/forms/FooterSubscribeForm.astro +9 -9
  11. package/dist/components/site/BookList.astro +7 -7
  12. package/dist/components/site/CTASection.astro +4 -4
  13. package/dist/components/site/ChronicleList.astro +6 -6
  14. package/dist/components/site/Hero.astro +3 -3
  15. package/dist/components/site/PathCard.astro +5 -5
  16. package/dist/components/site/ProfileList.astro +5 -5
  17. package/dist/components/site/RouteNotFound.astro +6 -6
  18. package/dist/components/site/SectionIntro.astro +3 -3
  19. package/dist/components/site/StageBanner.astro +2 -2
  20. package/dist/components/site/TrustCallout.astro +3 -3
  21. package/dist/components/ui/data/ActionList.astro +51 -0
  22. package/dist/components/ui/data/Badge.astro +19 -0
  23. package/dist/components/ui/data/DataTable.astro +51 -0
  24. package/dist/components/ui/data/KeyValueList.astro +28 -0
  25. package/dist/components/ui/data/MetricCard.astro +25 -0
  26. package/dist/components/ui/data/MetricGrid.astro +27 -0
  27. package/dist/components/ui/data/StatusPill.astro +20 -0
  28. package/dist/components/ui/forms/Button.astro +52 -0
  29. package/dist/components/ui/forms/Field.astro +39 -0
  30. package/dist/components/ui/forms/FormActions.astro +12 -0
  31. package/dist/components/ui/forms/PasswordMeter.astro +80 -0
  32. package/dist/components/ui/forms/RadioGroup.astro +55 -0
  33. package/dist/components/ui/forms/Select.astro +44 -0
  34. package/dist/components/ui/forms/TextInput.astro +58 -0
  35. package/dist/components/ui/forms/Textarea.astro +45 -0
  36. package/dist/components/ui/layout/PageHeader.astro +45 -0
  37. package/dist/components/ui/shell/AppShell.astro +112 -0
  38. package/dist/components/ui/shell/BottomNav.astro +35 -0
  39. package/dist/components/ui/shell/ProjectHeader.astro +66 -0
  40. package/dist/components/ui/shell/PublicFooter.astro +39 -0
  41. package/dist/components/ui/shell/PublicShell.astro +179 -0
  42. package/dist/components/ui/shell/RailNav.astro +35 -0
  43. package/dist/components/ui/shell/TopBar.astro +52 -0
  44. package/dist/components/ui/surface/Card.astro +46 -0
  45. package/dist/components/ui/surface/EmptyState.astro +45 -0
  46. package/dist/components/ui/surface/Panel.astro +54 -0
  47. package/dist/components/ui/theme/ThemeMenu.astro +32 -0
  48. package/dist/components/ui/theme/ThemePreviewSwatch.astro +18 -0
  49. package/dist/components/ui/theme/ThemeScript.astro +111 -0
  50. package/dist/components/ui/theme/ThemeSelector.astro +202 -0
  51. package/dist/components/ui/types.js +0 -0
  52. package/dist/dev-watch.d.ts +6 -2
  53. package/dist/dev-watch.js +12 -3
  54. package/dist/dev.d.ts +10 -2
  55. package/dist/dev.js +352 -68
  56. package/dist/layouts/AuthoredEntryLayout.astro +27 -27
  57. package/dist/layouts/BookLayout.astro +10 -10
  58. package/dist/layouts/ContentLayout.astro +4 -4
  59. package/dist/layouts/MainLayout.astro +66 -193
  60. package/dist/layouts/NoteLayout.astro +6 -6
  61. package/dist/layouts/ProfileLayout.astro +17 -17
  62. package/dist/middleware/starlightRouteData.js +20 -14
  63. package/dist/pages/404.astro +8 -8
  64. package/dist/pages/[slug].astro +1 -1
  65. package/dist/pages/books/[slug].astro +5 -5
  66. package/dist/pages/contact.astro +4 -4
  67. package/dist/pages/docs-runtime/[...slug].astro +12 -12
  68. package/dist/pages/docs-runtime/index.astro +13 -13
  69. package/dist/pages/index.astro +28 -28
  70. package/dist/pages/ui/index.astro +216 -0
  71. package/dist/scripts/dev-platform.js +7 -1
  72. package/dist/site.js +53 -5
  73. package/dist/styles/app-shell.css +597 -0
  74. package/dist/styles/forms.css +258 -0
  75. package/dist/styles/global.css +125 -120
  76. package/dist/styles/prose.css +11 -11
  77. package/dist/styles/theme.css +177 -0
  78. package/dist/styles/tokens.css +62 -22
  79. package/dist/styles/ui.css +551 -0
  80. package/dist/utils/color-schemes/cedar.js +53 -0
  81. package/dist/utils/color-schemes/fern.js +53 -0
  82. package/dist/utils/color-schemes/index.js +13 -0
  83. package/dist/utils/color-schemes/lichen.js +53 -0
  84. package/dist/utils/color-schemes/shared.js +33 -0
  85. package/dist/utils/color-schemes/tidepool.js +53 -0
  86. package/dist/utils/content-status.js +5 -5
  87. package/dist/utils/site-config.js +2 -2
  88. package/dist/utils/starlight-nav.js +13 -7
  89. package/dist/utils/theme.js +133 -41
  90. 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>