@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
@@ -10,15 +10,15 @@ const {
10
10
 
11
11
  <MainLayout title={title} description={description} currentPath={currentPath}>
12
12
  <section class="mx-auto max-w-3xl space-y-6 py-20">
13
- <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--site-accent-strong)]">404</p>
14
- <h1 class="font-serif text-5xl text-[color:var(--site-text)]">{title}</h1>
15
- <p class="text-lg leading-9 text-[color:var(--site-text-muted)]">{description}</p>
13
+ <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--ts-color-accent-strong)]">404</p>
14
+ <h1 class="font-serif text-5xl text-[color:var(--ts-color-text)]">{title}</h1>
15
+ <p class="text-lg leading-9 text-[color:var(--ts-color-text-muted)]">{description}</p>
16
16
  <div class="flex flex-wrap gap-4">
17
- <a href="/" class="border border-[color:var(--site-accent)] bg-[color:var(--site-accent)] px-5 py-3 text-base font-semibold text-[color:var(--site-text)] transition hover:border-[color:var(--site-blue)] hover:bg-[color:var(--site-blue-soft)]">
17
+ <a href="/" class="border border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
18
18
  Go home
19
19
  </a>
20
- <a href="/knowledge/" class="border border-[color:var(--site-border-strong)] px-5 py-3 text-base font-semibold text-[color:var(--site-text)] transition hover:border-[color:var(--site-blue)] hover:bg-[color:var(--site-blue-soft)]">
21
- Open knowledge
20
+ <a href="/books/" class="border border-[color:var(--ts-color-border-strong)] px-5 py-3 text-base font-semibold text-[color:var(--ts-color-text)] transition hover:border-[color:var(--ts-color-info)] hover:bg-[color:var(--ts-color-info-soft)]">
21
+ Open books
22
22
  </a>
23
23
  </div>
24
24
  </section>
@@ -3,7 +3,7 @@ const { eyebrow, title, description } = Astro.props;
3
3
  ---
4
4
 
5
5
  <header class="max-w-4xl space-y-4">
6
- {eyebrow && <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--site-accent-strong)]">{eyebrow}</p>}
7
- <h2 class="max-w-4xl font-serif text-4xl font-bold tracking-tight text-[color:var(--site-text)] md:text-5xl">{title}</h2>
8
- <p class="max-w-3xl text-lg leading-9 text-[color:var(--site-text-muted)] md:text-xl">{description}</p>
6
+ {eyebrow && <p class="text-sm font-semibold uppercase tracking-[0.16em] text-[color:var(--ts-color-accent-strong)]">{eyebrow}</p>}
7
+ <h2 class="max-w-4xl font-serif text-4xl font-bold tracking-tight text-[color:var(--ts-color-text)] md:text-5xl">{title}</h2>
8
+ <p class="max-w-3xl text-lg leading-9 text-[color:var(--ts-color-text-muted)] md:text-xl">{description}</p>
9
9
  </header>
@@ -2,7 +2,7 @@
2
2
  import { PROJECT_STAGE } from '../../utils/content-status';
3
3
  ---
4
4
 
5
- <div class="inline-flex max-w-full flex-wrap items-center gap-x-3 gap-y-1 border-l-4 border-[color:var(--site-accent)] bg-[color:var(--site-accent-soft)] px-4 py-3 text-[color:var(--site-text)]">
5
+ <div class="inline-flex max-w-full flex-wrap items-center gap-x-3 gap-y-1 border-l-4 border-[color:var(--ts-color-accent)] bg-[color:var(--ts-color-accent-soft)] px-4 py-3 text-[color:var(--ts-color-text)]">
6
6
  <span class="font-bold uppercase tracking-[0.16em] text-xs">{PROJECT_STAGE.label}</span>
7
- <span class="text-[color:var(--site-text-muted)]">{PROJECT_STAGE.description}</span>
7
+ <span class="text-[color:var(--ts-color-text-muted)]">{PROJECT_STAGE.description}</span>
8
8
  </div>
@@ -2,8 +2,8 @@
2
2
  const { title, body } = Astro.props;
3
3
  ---
4
4
 
5
- <aside class="border-l-4 border-[color:var(--site-warm)] bg-[color:rgba(215,176,123,0.12)] px-5 py-5 text-[color:var(--site-text)]">
6
- <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--site-warm-strong)]">Trust note</p>
5
+ <aside class="border-l-4 border-[color:var(--ts-color-warning)] bg-[color:var(--ts-color-warning-soft)] px-5 py-5 text-[color:var(--ts-color-text)]">
6
+ <p class="text-sm font-semibold uppercase tracking-[0.14em] text-[color:var(--ts-color-warning-text)]">Trust note</p>
7
7
  <h3 class="mt-2 text-2xl font-bold">{title}</h3>
8
- <p class="mt-3 text-base leading-8 text-[color:var(--site-text-muted)]">{body}</p>
8
+ <p class="mt-3 text-base leading-8 text-[color:var(--ts-color-text-muted)]">{body}</p>
9
9
  </aside>
@@ -0,0 +1,51 @@
1
+ ---
2
+ import Badge from './Badge.astro';
3
+ import type { Tone } from '../types.js';
4
+
5
+ interface ActionListItem {
6
+ title: string;
7
+ description?: string;
8
+ href?: string;
9
+ meta?: string;
10
+ tone?: Tone;
11
+ actionLabel?: string;
12
+ }
13
+
14
+ interface Props {
15
+ items?: ActionListItem[];
16
+ class?: string;
17
+ }
18
+
19
+ const {
20
+ items = [],
21
+ class: className,
22
+ } = Astro.props as Props;
23
+ ---
24
+
25
+ <div class:list={['ts-action-list', className]}>
26
+ {items.length > 0 ? (
27
+ <ul class="ts-action-list__items">
28
+ {items.map((item) => {
29
+ const Inner = (
30
+ <>
31
+ <span class="ts-action-list__content">
32
+ <span class="ts-action-list__title">{item.title}</span>
33
+ {item.description ? <span class="ts-action-list__description">{item.description}</span> : null}
34
+ </span>
35
+ <span class="ts-action-list__meta">
36
+ {item.meta ? <Badge tone={item.tone ?? 'muted'} size="sm">{item.meta}</Badge> : null}
37
+ {item.actionLabel ? <span class="ts-action-list__action">{item.actionLabel}</span> : null}
38
+ </span>
39
+ </>
40
+ );
41
+ return (
42
+ <li class="ts-action-list__item" data-tone={item.tone ?? 'default'}>
43
+ {item.href ? <a href={item.href} class="ts-action-list__link">{Inner}</a> : <div class="ts-action-list__row">{Inner}</div>}
44
+ </li>
45
+ );
46
+ })}
47
+ </ul>
48
+ ) : (
49
+ <slot />
50
+ )}
51
+ </div>
@@ -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>