@treeseed/core 0.10.22 → 0.11.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.
Files changed (108) hide show
  1. package/README.md +69 -125
  2. package/dist/dev-watch.js +2 -1
  3. package/dist/dev.d.ts +1 -1
  4. package/dist/dev.js +51 -35
  5. package/dist/pages/404.astro +1 -1
  6. package/dist/pages/[slug].astro +4 -4
  7. package/dist/pages/agents/[slug].astro +3 -3
  8. package/dist/pages/agents/index.astro +3 -3
  9. package/dist/pages/books/[slug].astro +3 -3
  10. package/dist/pages/books/index.astro +3 -3
  11. package/dist/pages/contact.astro +2 -2
  12. package/dist/pages/decisions/[slug].astro +3 -3
  13. package/dist/pages/decisions/index.astro +3 -3
  14. package/dist/pages/docs-runtime/[...slug].astro +3 -3
  15. package/dist/pages/docs-runtime/index.astro +3 -3
  16. package/dist/pages/index.astro +11 -11
  17. package/dist/pages/notes/[slug].astro +3 -3
  18. package/dist/pages/notes/index.astro +3 -3
  19. package/dist/pages/objectives/[slug].astro +3 -3
  20. package/dist/pages/objectives/index.astro +3 -3
  21. package/dist/pages/people/[slug].astro +3 -3
  22. package/dist/pages/people/index.astro +3 -3
  23. package/dist/pages/proposals/[slug].astro +3 -3
  24. package/dist/pages/proposals/index.astro +3 -3
  25. package/dist/pages/questions/[slug].astro +3 -3
  26. package/dist/pages/questions/index.astro +3 -3
  27. package/dist/pages/ui/index.astro +23 -23
  28. package/dist/platform-resources.js +5 -1
  29. package/dist/scripts/build-dist.js +2 -0
  30. package/dist/scripts/release-verify.js +24 -2
  31. package/dist/scripts/workspace-bootstrap.js +3 -0
  32. package/dist/site.js +49 -11
  33. package/dist/styles/global.css +5 -5
  34. package/package.json +3 -45
  35. package/templates/github/deploy-web.workflow.yml +36 -2
  36. package/dist/components/DevWatchReload.astro +0 -45
  37. package/dist/components/SiteTitle.astro +0 -51
  38. package/dist/components/content/ContentStatusLegend.astro +0 -18
  39. package/dist/components/content/StatusBadge.astro +0 -11
  40. package/dist/components/docs/BookFontControls.astro +0 -180
  41. package/dist/components/docs/DesktopSidebarToggle.astro +0 -88
  42. package/dist/components/docs/DownloadBook.astro +0 -34
  43. package/dist/components/docs/Footer.astro +0 -112
  44. package/dist/components/docs/Header.astro +0 -157
  45. package/dist/components/docs/PageFrame.astro +0 -260
  46. package/dist/components/docs/PageSidebar.astro +0 -63
  47. package/dist/components/docs/PageTitle.astro +0 -39
  48. package/dist/components/docs/Sidebar.astro +0 -41
  49. package/dist/components/docs/ThemeSelect.astro +0 -5
  50. package/dist/components/forms/ContactForm.astro +0 -233
  51. package/dist/components/forms/FooterSubscribeForm.astro +0 -188
  52. package/dist/components/site/BookList.astro +0 -27
  53. package/dist/components/site/CTASection.astro +0 -24
  54. package/dist/components/site/ChronicleList.astro +0 -33
  55. package/dist/components/site/Hero.astro +0 -18
  56. package/dist/components/site/NotesList.astro +0 -29
  57. package/dist/components/site/PathCard.astro +0 -16
  58. package/dist/components/site/ProfileList.astro +0 -30
  59. package/dist/components/site/PublishedContentBody.astro +0 -5
  60. package/dist/components/site/RouteNotFound.astro +0 -25
  61. package/dist/components/site/SectionIntro.astro +0 -9
  62. package/dist/components/site/StageBanner.astro +0 -8
  63. package/dist/components/site/TrustCallout.astro +0 -9
  64. package/dist/components/starlight.js +0 -6
  65. package/dist/components/ui/data/ActionList.astro +0 -51
  66. package/dist/components/ui/data/Badge.astro +0 -19
  67. package/dist/components/ui/data/DataTable.astro +0 -51
  68. package/dist/components/ui/data/KeyValueList.astro +0 -28
  69. package/dist/components/ui/data/MetricCard.astro +0 -25
  70. package/dist/components/ui/data/MetricGrid.astro +0 -27
  71. package/dist/components/ui/data/StatusPill.astro +0 -20
  72. package/dist/components/ui/forms/Button.astro +0 -59
  73. package/dist/components/ui/forms/Field.astro +0 -39
  74. package/dist/components/ui/forms/FormActions.astro +0 -12
  75. package/dist/components/ui/forms/PasswordMeter.astro +0 -80
  76. package/dist/components/ui/forms/RadioGroup.astro +0 -55
  77. package/dist/components/ui/forms/Select.astro +0 -47
  78. package/dist/components/ui/forms/TextInput.astro +0 -58
  79. package/dist/components/ui/forms/Textarea.astro +0 -45
  80. package/dist/components/ui/layout/PageHeader.astro +0 -45
  81. package/dist/components/ui/shell/AppShell.astro +0 -130
  82. package/dist/components/ui/shell/BottomNav.astro +0 -42
  83. package/dist/components/ui/shell/ProjectHeader.astro +0 -66
  84. package/dist/components/ui/shell/PublicFooter.astro +0 -39
  85. package/dist/components/ui/shell/PublicShell.astro +0 -184
  86. package/dist/components/ui/shell/RailNav.astro +0 -42
  87. package/dist/components/ui/shell/ShellIconLink.astro +0 -30
  88. package/dist/components/ui/shell/TopBar.astro +0 -52
  89. package/dist/components/ui/surface/Card.astro +0 -46
  90. package/dist/components/ui/surface/EmptyState.astro +0 -45
  91. package/dist/components/ui/surface/Panel.astro +0 -54
  92. package/dist/components/ui/theme/ThemeMenu.astro +0 -58
  93. package/dist/components/ui/theme/ThemePreviewSwatch.astro +0 -18
  94. package/dist/components/ui/theme/ThemeScript.astro +0 -112
  95. package/dist/components/ui/theme/ThemeSelector.astro +0 -202
  96. package/dist/components/ui/types.js +0 -0
  97. package/dist/layouts/AuthoredEntryLayout.astro +0 -195
  98. package/dist/layouts/BookLayout.astro +0 -35
  99. package/dist/layouts/BridgeLayout.astro +0 -11
  100. package/dist/layouts/ContentLayout.astro +0 -24
  101. package/dist/layouts/MainLayout.astro +0 -76
  102. package/dist/layouts/NoteLayout.astro +0 -26
  103. package/dist/layouts/ProfileLayout.astro +0 -85
  104. package/dist/styles/app-shell.css +0 -626
  105. package/dist/styles/forms.css +0 -274
  106. package/dist/styles/theme.css +0 -198
  107. package/dist/styles/tokens.css +0 -65
  108. package/dist/styles/ui.css +0 -551
@@ -1,20 +0,0 @@
1
- ---
2
- import type { Tone } from '../types.js';
3
-
4
- interface Props {
5
- tone?: Tone;
6
- label?: string;
7
- class?: string;
8
- }
9
-
10
- const {
11
- tone = 'default',
12
- label,
13
- class: className,
14
- } = Astro.props as Props;
15
- ---
16
-
17
- <span class:list={['ts-status-pill', className]} data-tone={tone}>
18
- <span class="ts-status-pill__dot" aria-hidden="true"></span>
19
- <span><slot>{label}</slot></span>
20
- </span>
@@ -1,59 +0,0 @@
1
- ---
2
- import type { ButtonSize, ButtonVariant } from '../types.js';
3
-
4
- interface Props {
5
- href?: string;
6
- type?: 'button' | 'submit' | 'reset';
7
- variant?: ButtonVariant;
8
- size?: ButtonSize;
9
- disabled?: boolean;
10
- ariaLabel?: string;
11
- reload?: boolean;
12
- class?: string;
13
- [key: string]: unknown;
14
- }
15
-
16
- const {
17
- href,
18
- type = 'button',
19
- variant = 'primary',
20
- size = 'md',
21
- disabled = false,
22
- ariaLabel,
23
- reload = false,
24
- class: className,
25
- ...buttonAttributes
26
- } = Astro.props as Props;
27
-
28
- const classes = ['ts-button', className].filter(Boolean).join(' ');
29
- ---
30
-
31
- {
32
- href ? (
33
- <a
34
- href={disabled ? undefined : href}
35
- class={classes}
36
- data-variant={variant}
37
- data-size={size}
38
- aria-label={ariaLabel}
39
- aria-disabled={disabled ? 'true' : undefined}
40
- tabindex={disabled ? -1 : undefined}
41
- data-astro-reload={reload ? true : undefined}
42
- {...buttonAttributes}
43
- >
44
- <slot />
45
- </a>
46
- ) : (
47
- <button
48
- {...buttonAttributes}
49
- type={type}
50
- class={classes}
51
- data-variant={variant}
52
- data-size={size}
53
- disabled={disabled}
54
- aria-label={ariaLabel}
55
- >
56
- <slot />
57
- </button>
58
- )
59
- }
@@ -1,39 +0,0 @@
1
- ---
2
- interface Props {
3
- label: string;
4
- id?: string;
5
- name?: string;
6
- help?: string;
7
- error?: string;
8
- required?: boolean;
9
- full?: boolean;
10
- class?: string;
11
- }
12
-
13
- const {
14
- label,
15
- id,
16
- name,
17
- help,
18
- error,
19
- required = false,
20
- full = false,
21
- class: className,
22
- } = Astro.props as Props;
23
-
24
- const fieldId = id ?? name;
25
- const helpId = fieldId && help ? `${fieldId}-help` : undefined;
26
- const errorId = fieldId && error ? `${fieldId}-error` : undefined;
27
- ---
28
-
29
- <div class:list={['ts-field', full && 'ts-field--full', className]}>
30
- <label class="ts-field__label" for={fieldId}>
31
- <span>{label}</span>
32
- {required ? <span class="ts-field__required" aria-hidden="true">*</span> : null}
33
- </label>
34
- <div class="ts-field__control">
35
- <slot helpId={helpId} errorId={errorId} describedBy={[helpId, errorId].filter(Boolean).join(' ') || undefined} />
36
- </div>
37
- {help ? <p class="ts-field__help" id={helpId}>{help}</p> : null}
38
- {error ? <p class="ts-field__error" id={errorId}>{error}</p> : null}
39
- </div>
@@ -1,12 +0,0 @@
1
- ---
2
- interface Props {
3
- align?: 'start' | 'end' | 'between';
4
- class?: string;
5
- }
6
-
7
- const { align = 'end', class: className } = Astro.props as Props;
8
- ---
9
-
10
- <div class:list={['ts-form-actions', `ts-form-actions--${align}`, className]}>
11
- <slot />
12
- </div>
@@ -1,80 +0,0 @@
1
- ---
2
- interface Props {
3
- inputId?: string;
4
- label?: string;
5
- minLength?: number;
6
- class?: string;
7
- }
8
-
9
- const {
10
- inputId,
11
- label = 'Password strength',
12
- minLength = 12,
13
- class: className,
14
- } = Astro.props as Props;
15
- ---
16
-
17
- <div
18
- class:list={['ts-password-meter', className]}
19
- data-ts-password-meter
20
- data-input-id={inputId}
21
- data-min-length={minLength}
22
- >
23
- <div class="ts-password-meter__header">
24
- <span>{label}</span>
25
- <span class="ts-password-meter__status" data-ts-password-meter-status>Not started</span>
26
- </div>
27
- <div class="ts-password-meter__track" aria-hidden="true">
28
- <span class="ts-password-meter__bar" data-ts-password-meter-bar></span>
29
- </div>
30
- <ul class="ts-password-meter__rules">
31
- <li data-ts-password-rule="length">Use at least {minLength} characters</li>
32
- <li data-ts-password-rule="case">Mix upper and lower case letters</li>
33
- <li data-ts-password-rule="number">Include a number</li>
34
- <li data-ts-password-rule="symbol">Include a symbol</li>
35
- </ul>
36
- </div>
37
-
38
- <script is:inline>
39
- (() => {
40
- function scorePassword(value, minLength) {
41
- const checks = {
42
- length: value.length >= minLength,
43
- case: /[a-z]/.test(value) && /[A-Z]/.test(value),
44
- number: /\d/.test(value),
45
- symbol: /[^A-Za-z0-9]/.test(value),
46
- };
47
- return { checks, score: Object.values(checks).filter(Boolean).length };
48
- }
49
-
50
- function bindMeter(meter) {
51
- if (!(meter instanceof HTMLElement)) return;
52
- const minLength = Number(meter.dataset.minLength || 12);
53
- const inputId = meter.dataset.inputId;
54
- const input = inputId
55
- ? document.getElementById(inputId)
56
- : meter.closest('form')?.querySelector('[data-ts-password-input], input[type="password"]');
57
- if (!(input instanceof HTMLInputElement)) return;
58
-
59
- const status = meter.querySelector('[data-ts-password-meter-status]');
60
- const bar = meter.querySelector('[data-ts-password-meter-bar]');
61
- const labels = ['Not started', 'Weak', 'Fair', 'Good', 'Strong'];
62
-
63
- function update() {
64
- const { checks, score } = scorePassword(input.value, minLength);
65
- meter.dataset.strength = String(score);
66
- if (status) status.textContent = input.value ? labels[score] : labels[0];
67
- if (bar instanceof HTMLElement) bar.style.setProperty('--ts-password-strength', String(score));
68
- for (const [rule, passed] of Object.entries(checks)) {
69
- const item = meter.querySelector(`[data-ts-password-rule="${rule}"]`);
70
- if (item instanceof HTMLElement) item.dataset.passed = passed ? 'true' : 'false';
71
- }
72
- }
73
-
74
- input.addEventListener('input', update);
75
- update();
76
- }
77
-
78
- document.querySelectorAll('[data-ts-password-meter]').forEach(bindMeter);
79
- })();
80
- </script>
@@ -1,55 +0,0 @@
1
- ---
2
- interface RadioOption {
3
- label: string;
4
- value: string;
5
- help?: string;
6
- disabled?: boolean;
7
- }
8
-
9
- interface Props {
10
- name: string;
11
- legend: string;
12
- value?: string;
13
- options: RadioOption[];
14
- required?: boolean;
15
- class?: string;
16
- }
17
-
18
- const {
19
- name,
20
- legend,
21
- value,
22
- options,
23
- required = false,
24
- class: className,
25
- } = Astro.props as Props;
26
- ---
27
-
28
- <fieldset class:list={['ts-radio-group', className]}>
29
- <legend class="ts-radio-group__legend">
30
- {legend}
31
- {required ? <span class="ts-field__required" aria-hidden="true">*</span> : null}
32
- </legend>
33
- <div class="ts-radio-group__options">
34
- {options.map((option) => {
35
- const optionId = `${name}-${option.value}`;
36
- return (
37
- <label class="ts-radio-option" for={optionId} data-disabled={option.disabled ? 'true' : undefined}>
38
- <input
39
- id={optionId}
40
- type="radio"
41
- name={name}
42
- value={option.value}
43
- checked={option.value === value}
44
- required={required}
45
- disabled={option.disabled}
46
- />
47
- <span class="ts-radio-option__body">
48
- <span class="ts-radio-option__label">{option.label}</span>
49
- {option.help ? <span class="ts-radio-option__help">{option.help}</span> : null}
50
- </span>
51
- </label>
52
- );
53
- })}
54
- </div>
55
- </fieldset>
@@ -1,47 +0,0 @@
1
- ---
2
- interface SelectOption {
3
- label: string;
4
- value: string;
5
- disabled?: boolean;
6
- }
7
-
8
- interface Props {
9
- name: string;
10
- id?: string;
11
- value?: string;
12
- options: SelectOption[];
13
- required?: boolean;
14
- disabled?: boolean;
15
- ariaDescribedby?: string;
16
- class?: string;
17
- [key: string]: unknown;
18
- }
19
-
20
- const {
21
- name,
22
- id = name,
23
- value,
24
- options,
25
- required = false,
26
- disabled = false,
27
- ariaDescribedby,
28
- class: className,
29
- ...selectAttributes
30
- } = Astro.props as Props;
31
- ---
32
-
33
- <select
34
- {...selectAttributes}
35
- class:list={['ts-control', 'ts-control--select', className]}
36
- id={id}
37
- name={name}
38
- required={required}
39
- disabled={disabled}
40
- aria-describedby={ariaDescribedby}
41
- >
42
- {options.map((option) => (
43
- <option value={option.value} selected={option.value === value} disabled={option.disabled}>
44
- {option.label}
45
- </option>
46
- ))}
47
- </select>
@@ -1,58 +0,0 @@
1
- ---
2
- interface Props {
3
- name: string;
4
- id?: string;
5
- type?: 'text' | 'email' | 'url' | 'password' | 'search' | 'number' | 'tel';
6
- value?: string | number;
7
- placeholder?: string;
8
- autocomplete?: string;
9
- required?: boolean;
10
- disabled?: boolean;
11
- readonly?: boolean;
12
- minlength?: number;
13
- maxlength?: number;
14
- pattern?: string;
15
- inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
16
- ariaDescribedby?: string;
17
- class?: string;
18
- [key: string]: unknown;
19
- }
20
-
21
- const {
22
- name,
23
- id = name,
24
- type = 'text',
25
- value,
26
- placeholder,
27
- autocomplete,
28
- required = false,
29
- disabled = false,
30
- readonly = false,
31
- minlength,
32
- maxlength,
33
- pattern,
34
- inputmode,
35
- ariaDescribedby,
36
- class: className,
37
- ...inputAttributes
38
- } = Astro.props as Props;
39
- ---
40
-
41
- <input
42
- {...inputAttributes}
43
- class:list={['ts-control', className]}
44
- id={id}
45
- name={name}
46
- type={type}
47
- value={value}
48
- placeholder={placeholder}
49
- autocomplete={autocomplete}
50
- required={required}
51
- disabled={disabled}
52
- readonly={readonly}
53
- minlength={minlength}
54
- maxlength={maxlength}
55
- pattern={pattern}
56
- inputmode={inputmode}
57
- aria-describedby={ariaDescribedby}
58
- />
@@ -1,45 +0,0 @@
1
- ---
2
- interface Props {
3
- name: string;
4
- id?: string;
5
- value?: string;
6
- placeholder?: string;
7
- rows?: number;
8
- required?: boolean;
9
- disabled?: boolean;
10
- readonly?: boolean;
11
- minlength?: number;
12
- maxlength?: number;
13
- ariaDescribedby?: string;
14
- class?: string;
15
- }
16
-
17
- const {
18
- name,
19
- id = name,
20
- value,
21
- placeholder,
22
- rows = 4,
23
- required = false,
24
- disabled = false,
25
- readonly = false,
26
- minlength,
27
- maxlength,
28
- ariaDescribedby,
29
- class: className,
30
- } = Astro.props as Props;
31
- ---
32
-
33
- <textarea
34
- class:list={['ts-control', 'ts-control--textarea', className]}
35
- id={id}
36
- name={name}
37
- placeholder={placeholder}
38
- rows={rows}
39
- required={required}
40
- disabled={disabled}
41
- readonly={readonly}
42
- minlength={minlength}
43
- maxlength={maxlength}
44
- aria-describedby={ariaDescribedby}
45
- >{value}</textarea>
@@ -1,45 +0,0 @@
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>
@@ -1,130 +0,0 @@
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 { ClientRouter } from 'astro:transitions';
8
- import ThemeScript from '../theme/ThemeScript.astro';
9
- import ThemeMenu from '../theme/ThemeMenu.astro';
10
- import RailNav from './RailNav.astro';
11
- import BottomNav from './BottomNav.astro';
12
- import TopBar from './TopBar.astro';
13
- import ShellIconLink from './ShellIconLink.astro';
14
- import Button from '../forms/Button.astro';
15
- import DevWatchReload from '../../DevWatchReload.astro';
16
- import type { ButtonAction } from '../types.js';
17
- import type { ThemePreference } from '../../../utils/theme.js';
18
-
19
- interface Brand {
20
- name: string;
21
- tag?: string;
22
- href: string;
23
- logoSrc?: string;
24
- logoAlt?: string;
25
- mark?: string;
26
- }
27
-
28
- interface NavItem {
29
- label: string;
30
- href: string;
31
- ariaLabel?: string;
32
- }
33
-
34
- interface Props {
35
- title: string;
36
- description: string;
37
- currentPath: string;
38
- appearance: ThemePreference;
39
- brand: Brand;
40
- navItems: NavItem[];
41
- quickActions?: ButtonAction[];
42
- bottomNavItems?: NavItem[];
43
- }
44
-
45
- const {
46
- title,
47
- description,
48
- currentPath,
49
- appearance,
50
- brand,
51
- navItems,
52
- quickActions = [],
53
- bottomNavItems = navItems.slice(0, 5),
54
- } = Astro.props as Props;
55
- ---
56
-
57
- <!doctype html>
58
- <html lang="en">
59
- <head>
60
- <meta charset="utf-8" />
61
- <meta name="viewport" content="width=device-width, initial-scale=1" />
62
- <title>{title}</title>
63
- <meta name="description" content={description} />
64
- <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
65
- <link rel="shortcut icon" href="/favicon.svg" type="image/svg+xml" />
66
- <ThemeScript defaultScheme={appearance.scheme} defaultMode={appearance.mode} preferDefaultPreference />
67
- <ClientRouter />
68
- </head>
69
- <body>
70
- <a class="ts-skip-link" href="#main-content">Skip to content</a>
71
- <div class="ts-app-shell">
72
- <aside class="ts-app-shell__rail">
73
- <div class="ts-app-shell__rail-scroll">
74
- <TopBar brand={brand} />
75
- <div class="ts-app-shell__rail-context">
76
- <slot name="railContext" />
77
- </div>
78
- <RailNav items={navItems} currentPath={currentPath} />
79
- </div>
80
- {quickActions.length > 0 ? (
81
- <div class="ts-app-shell__quick-actions">
82
- <p class="ts-app-shell__eyebrow">Quick actions</p>
83
- <div class="ts-app-shell__quick-list">
84
- {quickActions.map((action) => (
85
- <Button
86
- href={action.href}
87
- type={action.type}
88
- variant={action.variant ?? 'secondary'}
89
- ariaLabel={action.ariaLabel}
90
- disabled={action.disabled}
91
- reload={action.reload}
92
- size="sm"
93
- >
94
- {action.label}
95
- </Button>
96
- ))}
97
- </div>
98
- </div>
99
- ) : null}
100
- </aside>
101
- <main class="ts-app-shell__main" id="main-content">
102
- <TopBar brand={brand} class="ts-app-shell__mobile-top">
103
- <Fragment slot="actions">
104
- <ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} />
105
- <ShellIconLink href="/" label="Book home" icon="book" />
106
- </Fragment>
107
- </TopBar>
108
- <header class="ts-app-shell__header">
109
- <div class="ts-app-shell__title">
110
- <h1>{title}</h1>
111
- <p>{description}</p>
112
- </div>
113
- <div class="ts-app-shell__header-actions">
114
- <div class="ts-shell-utility-actions">
115
- <ThemeMenu selectedScheme={appearance.scheme} selectedMode={appearance.mode} />
116
- <ShellIconLink href="/" label="Book home" icon="book" />
117
- </div>
118
- <slot name="headerAction" />
119
- </div>
120
- </header>
121
- <slot name="projectContext" />
122
- <slot />
123
- </main>
124
- {bottomNavItems.length > 0 ? <BottomNav items={bottomNavItems} currentPath={currentPath} /> : null}
125
- </div>
126
- <slot name="sensitiveModal" />
127
- <slot name="modal" />
128
- <DevWatchReload />
129
- </body>
130
- </html>
@@ -1,42 +0,0 @@
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 normalizePath(path: string) {
23
- if (path.length <= 1) return path;
24
- return path.replace(/\/+$/u, '');
25
- }
26
-
27
- function isCurrentPath(href: string) {
28
- const current = normalizePath(currentPath);
29
- const target = normalizePath(href);
30
- if (target === '/') return current === '/';
31
- if (target === '/app') return current === '/app';
32
- return current === target || current.startsWith(`${target}/`);
33
- }
34
- ---
35
-
36
- <nav class:list={['ts-bottom-nav', className]} aria-label={label}>
37
- {items.map((item) => (
38
- <a class="ts-bottom-nav__link" href={item.href} aria-label={item.ariaLabel} aria-current={isCurrentPath(item.href) ? 'page' : undefined}>
39
- <span>{item.label}</span>
40
- </a>
41
- ))}
42
- </nav>