@treeseed/core 0.8.8 → 0.8.9

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 (73) hide show
  1. package/dist/components/content/ContentStatusLegend.astro +4 -4
  2. package/dist/components/docs/BookFontControls.astro +9 -9
  3. package/dist/components/docs/DesktopSidebarToggle.astro +8 -8
  4. package/dist/components/docs/Footer.astro +6 -6
  5. package/dist/components/docs/PageTitle.astro +1 -1
  6. package/dist/components/docs/ThemeSelect.astro +3 -1
  7. package/dist/components/forms/ContactForm.astro +21 -21
  8. package/dist/components/forms/FooterSubscribeForm.astro +9 -9
  9. package/dist/components/site/BookList.astro +7 -7
  10. package/dist/components/site/CTASection.astro +4 -4
  11. package/dist/components/site/ChronicleList.astro +6 -6
  12. package/dist/components/site/Hero.astro +3 -3
  13. package/dist/components/site/PathCard.astro +5 -5
  14. package/dist/components/site/ProfileList.astro +5 -5
  15. package/dist/components/site/RouteNotFound.astro +5 -5
  16. package/dist/components/site/SectionIntro.astro +3 -3
  17. package/dist/components/site/StageBanner.astro +2 -2
  18. package/dist/components/site/TrustCallout.astro +3 -3
  19. package/dist/components/ui/data/ActionList.astro +51 -0
  20. package/dist/components/ui/data/Badge.astro +19 -0
  21. package/dist/components/ui/data/DataTable.astro +51 -0
  22. package/dist/components/ui/data/KeyValueList.astro +28 -0
  23. package/dist/components/ui/data/MetricCard.astro +25 -0
  24. package/dist/components/ui/data/MetricGrid.astro +27 -0
  25. package/dist/components/ui/data/StatusPill.astro +20 -0
  26. package/dist/components/ui/forms/Button.astro +52 -0
  27. package/dist/components/ui/forms/Field.astro +39 -0
  28. package/dist/components/ui/forms/FormActions.astro +12 -0
  29. package/dist/components/ui/forms/PasswordMeter.astro +80 -0
  30. package/dist/components/ui/forms/RadioGroup.astro +55 -0
  31. package/dist/components/ui/forms/Select.astro +44 -0
  32. package/dist/components/ui/forms/TextInput.astro +58 -0
  33. package/dist/components/ui/forms/Textarea.astro +45 -0
  34. package/dist/components/ui/layout/PageHeader.astro +45 -0
  35. package/dist/components/ui/shell/AppShell.astro +110 -0
  36. package/dist/components/ui/shell/BottomNav.astro +35 -0
  37. package/dist/components/ui/shell/ProjectHeader.astro +66 -0
  38. package/dist/components/ui/shell/PublicShell.astro +108 -0
  39. package/dist/components/ui/shell/RailNav.astro +35 -0
  40. package/dist/components/ui/shell/TopBar.astro +52 -0
  41. package/dist/components/ui/surface/Card.astro +46 -0
  42. package/dist/components/ui/surface/EmptyState.astro +45 -0
  43. package/dist/components/ui/surface/Panel.astro +54 -0
  44. package/dist/components/ui/theme/ThemeMenu.astro +32 -0
  45. package/dist/components/ui/theme/ThemePreviewSwatch.astro +18 -0
  46. package/dist/components/ui/theme/ThemeScript.astro +105 -0
  47. package/dist/components/ui/theme/ThemeSelector.astro +202 -0
  48. package/dist/components/ui/types.js +0 -0
  49. package/dist/dev.js +14 -2
  50. package/dist/layouts/AuthoredEntryLayout.astro +27 -27
  51. package/dist/layouts/BookLayout.astro +10 -10
  52. package/dist/layouts/ContentLayout.astro +4 -4
  53. package/dist/layouts/MainLayout.astro +27 -25
  54. package/dist/layouts/NoteLayout.astro +6 -6
  55. package/dist/layouts/ProfileLayout.astro +17 -17
  56. package/dist/pages/404.astro +6 -6
  57. package/dist/pages/contact.astro +4 -4
  58. package/dist/pages/docs-runtime/[...slug].astro +12 -12
  59. package/dist/pages/docs-runtime/index.astro +13 -13
  60. package/dist/pages/index.astro +28 -28
  61. package/dist/pages/ui/index.astro +216 -0
  62. package/dist/site.js +3 -2
  63. package/dist/styles/app-shell.css +398 -0
  64. package/dist/styles/forms.css +258 -0
  65. package/dist/styles/global.css +119 -119
  66. package/dist/styles/prose.css +11 -11
  67. package/dist/styles/theme.css +177 -0
  68. package/dist/styles/tokens.css +62 -22
  69. package/dist/styles/ui.css +551 -0
  70. package/dist/utils/content-status.js +5 -5
  71. package/dist/utils/site-config.js +2 -2
  72. package/dist/utils/theme.js +352 -40
  73. package/package.json +35 -2
@@ -0,0 +1,19 @@
1
+ ---
2
+ import type { Tone } from '../types.js';
3
+
4
+ interface Props {
5
+ tone?: Tone;
6
+ size?: 'sm' | 'md';
7
+ class?: string;
8
+ }
9
+
10
+ const {
11
+ tone = 'default',
12
+ size = 'md',
13
+ class: className,
14
+ } = Astro.props as Props;
15
+ ---
16
+
17
+ <span class:list={['ts-badge', className]} data-tone={tone} data-size={size}>
18
+ <slot />
19
+ </span>
@@ -0,0 +1,51 @@
1
+ ---
2
+ interface DataTableColumn {
3
+ key: string;
4
+ label: string;
5
+ }
6
+
7
+ interface Props {
8
+ columns: DataTableColumn[];
9
+ rows: Array<Record<string, unknown>>;
10
+ caption?: string;
11
+ emptyLabel?: string;
12
+ class?: string;
13
+ }
14
+
15
+ const {
16
+ columns,
17
+ rows,
18
+ caption,
19
+ emptyLabel = 'No rows to show.',
20
+ class: className,
21
+ } = Astro.props as Props;
22
+
23
+ function cellValue(row: Record<string, unknown>, key: string) {
24
+ const value = row[key];
25
+ return value === null || value === undefined ? '' : String(value);
26
+ }
27
+ ---
28
+
29
+ <div class:list={['ts-data-table-wrap', className]}>
30
+ <table class="ts-data-table">
31
+ {caption ? <caption>{caption}</caption> : null}
32
+ <thead>
33
+ <tr>
34
+ {columns.map((column) => <th scope="col">{column.label}</th>)}
35
+ </tr>
36
+ </thead>
37
+ <tbody>
38
+ {rows.length > 0 ? rows.map((row) => (
39
+ <tr>
40
+ {columns.map((column) => (
41
+ <td data-label={column.label}>{cellValue(row, column.key)}</td>
42
+ ))}
43
+ </tr>
44
+ )) : (
45
+ <tr>
46
+ <td colspan={columns.length}>{emptyLabel}</td>
47
+ </tr>
48
+ )}
49
+ </tbody>
50
+ </table>
51
+ </div>
@@ -0,0 +1,28 @@
1
+ ---
2
+ import type { Tone } from '../types.js';
3
+
4
+ interface KeyValueItem {
5
+ key: string;
6
+ value: string | number;
7
+ tone?: Tone;
8
+ }
9
+
10
+ interface Props {
11
+ items: KeyValueItem[];
12
+ class?: string;
13
+ }
14
+
15
+ const {
16
+ items,
17
+ class: className,
18
+ } = Astro.props as Props;
19
+ ---
20
+
21
+ <dl class:list={['ts-key-value-list', className]}>
22
+ {items.map((item) => (
23
+ <div class="ts-key-value-list__item" data-tone={item.tone ?? 'default'}>
24
+ <dt>{item.key}</dt>
25
+ <dd>{item.value}</dd>
26
+ </div>
27
+ ))}
28
+ </dl>
@@ -0,0 +1,25 @@
1
+ ---
2
+ import type { Tone } from '../types.js';
3
+
4
+ interface Props {
5
+ label: string;
6
+ value: string | number;
7
+ description?: string;
8
+ tone?: Tone;
9
+ class?: string;
10
+ }
11
+
12
+ const {
13
+ label,
14
+ value,
15
+ description,
16
+ tone = 'default',
17
+ class: className,
18
+ } = Astro.props as Props;
19
+ ---
20
+
21
+ <article class:list={['ts-metric-card', className]} data-tone={tone}>
22
+ <p class="ts-metric-card__label">{label}</p>
23
+ <p class="ts-metric-card__value">{value}</p>
24
+ {description ? <p class="ts-metric-card__description">{description}</p> : null}
25
+ </article>
@@ -0,0 +1,27 @@
1
+ ---
2
+ import MetricCard from './MetricCard.astro';
3
+ import type { Tone } from '../types.js';
4
+
5
+ interface MetricItem {
6
+ label: string;
7
+ value: string | number;
8
+ description?: string;
9
+ tone?: Tone;
10
+ }
11
+
12
+ interface Props {
13
+ metrics?: MetricItem[];
14
+ min?: string;
15
+ class?: string;
16
+ }
17
+
18
+ const {
19
+ metrics = [],
20
+ min = '12rem',
21
+ class: className,
22
+ } = Astro.props as Props;
23
+ ---
24
+
25
+ <div class:list={['ts-metric-grid', className]} style={`--ts-metric-grid-min: ${min}`}>
26
+ {metrics.length > 0 ? metrics.map((metric) => <MetricCard {...metric} />) : <slot />}
27
+ </div>
@@ -0,0 +1,20 @@
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>
@@ -0,0 +1,52 @@
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
+ class?: string;
12
+ }
13
+
14
+ const {
15
+ href,
16
+ type = 'button',
17
+ variant = 'primary',
18
+ size = 'md',
19
+ disabled = false,
20
+ ariaLabel,
21
+ class: className,
22
+ } = Astro.props as Props;
23
+
24
+ const classes = ['ts-button', className].filter(Boolean).join(' ');
25
+ ---
26
+
27
+ {
28
+ href ? (
29
+ <a
30
+ href={disabled ? undefined : href}
31
+ class={classes}
32
+ data-variant={variant}
33
+ data-size={size}
34
+ aria-label={ariaLabel}
35
+ aria-disabled={disabled ? 'true' : undefined}
36
+ tabindex={disabled ? -1 : undefined}
37
+ >
38
+ <slot />
39
+ </a>
40
+ ) : (
41
+ <button
42
+ type={type}
43
+ class={classes}
44
+ data-variant={variant}
45
+ data-size={size}
46
+ disabled={disabled}
47
+ aria-label={ariaLabel}
48
+ >
49
+ <slot />
50
+ </button>
51
+ )
52
+ }
@@ -0,0 +1,39 @@
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>
@@ -0,0 +1,12 @@
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>
@@ -0,0 +1,80 @@
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>
@@ -0,0 +1,55 @@
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>
@@ -0,0 +1,44 @@
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
+ }
18
+
19
+ const {
20
+ name,
21
+ id = name,
22
+ value,
23
+ options,
24
+ required = false,
25
+ disabled = false,
26
+ ariaDescribedby,
27
+ class: className,
28
+ } = Astro.props as Props;
29
+ ---
30
+
31
+ <select
32
+ class:list={['ts-control', 'ts-control--select', className]}
33
+ id={id}
34
+ name={name}
35
+ required={required}
36
+ disabled={disabled}
37
+ aria-describedby={ariaDescribedby}
38
+ >
39
+ {options.map((option) => (
40
+ <option value={option.value} selected={option.value === value} disabled={option.disabled}>
41
+ {option.label}
42
+ </option>
43
+ ))}
44
+ </select>
@@ -0,0 +1,58 @@
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
+ />
@@ -0,0 +1,45 @@
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>
@@ -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>