@sarj/docs-ui 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,17 @@ Shared Astro and Starlight reference UI for Sarj standards sites
8
8
  npm install --save-dev @sarj/docs-ui
9
9
  ```
10
10
 
11
- Rules and configuration are documented in the generated rule directory.
11
+ Import `@sarj/docs-ui/starlight.css` once, then compose the typed Astro components. The live component and theme contract is published at [docs-ui.sarj.ai](https://docs-ui.sarj.ai/).
12
12
 
13
- [Documentation](https://code-standards.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
13
+ Public exports:
14
+
15
+ - `@sarj/docs-ui/Breadcrumbs.astro`
16
+ - `@sarj/docs-ui/PageAnchor.astro`
17
+ - `@sarj/docs-ui/ReferencePage.astro`
18
+ - `@sarj/docs-ui/RulePager.astro`
19
+ - `@sarj/docs-ui/catalog`
20
+ - `@sarj/docs-ui/contracts`
21
+ - `@sarj/docs-ui/starlight.css`
22
+ - `@sarj/docs-ui/styles.css`
23
+
24
+ [Documentation](https://docs-ui.sarj.ai/) · [Source](https://github.com/sarj-ai/code-standards)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/docs-ui",
3
- "version": "0.1.1",
3
+ "version": "0.3.1",
4
4
  "packageManager": "npm@12.0.2",
5
5
  "description": "Shared Astro and Starlight reference UI for Sarj standards sites",
6
6
  "type": "module",
@@ -9,8 +9,12 @@
9
9
  },
10
10
  "exports": {
11
11
  "./styles.css": "./src/styles/theme.css",
12
+ "./starlight.css": "./src/styles/starlight.css",
13
+ "./catalog": "./src/catalog.ts",
14
+ "./contracts": "./src/contracts.ts",
12
15
  "./PageAnchor.astro": "./src/components/PageAnchor.astro",
13
16
  "./ReferencePage.astro": "./src/components/ReferencePage.astro",
17
+ "./RulePager.astro": "./src/components/RulePager.astro",
14
18
  "./Breadcrumbs.astro": "./src/components/Breadcrumbs.astro"
15
19
  },
16
20
  "files": [
@@ -19,7 +23,8 @@
19
23
  "LICENSE"
20
24
  ],
21
25
  "sideEffects": [
22
- "./src/styles/theme.css"
26
+ "./src/styles/theme.css",
27
+ "./src/styles/starlight.css"
23
28
  ],
24
29
  "publishConfig": {
25
30
  "access": "public"
@@ -29,7 +34,7 @@
29
34
  "url": "git+https://github.com/sarj-ai/code-standards.git",
30
35
  "directory": "packages/docs-ui"
31
36
  },
32
- "homepage": "https://code-standards.sarj.ai/",
37
+ "homepage": "https://docs-ui.sarj.ai/",
33
38
  "bugs": {
34
39
  "url": "https://github.com/sarj-ai/code-standards/issues"
35
40
  },
package/src/catalog.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { BreadcrumbsProps, ReferencePageProps, RulePagerProps } from './contracts';
2
+
3
+ export interface ComponentDefinition<Props extends object = object> {
4
+ exportPath: string;
5
+ purpose: string;
6
+ properties: Readonly<Record<keyof Props, string>>;
7
+ }
8
+
9
+ export interface ThemeTokenDefinition {
10
+ cssName: `--sarj-${string}`;
11
+ purpose: string;
12
+ light: `#${string}`;
13
+ dark: `#${string}`;
14
+ }
15
+
16
+ export const componentCatalog = Object.freeze({
17
+ Breadcrumbs: {
18
+ exportPath: '@sarj/docs-ui/Breadcrumbs.astro',
19
+ purpose: 'Show a compact, accessible path to the current reference page.',
20
+ properties: {
21
+ ancestors: 'Ordered links from the reference root to the current page.',
22
+ current: 'Current page label, announced with aria-current="page".',
23
+ },
24
+ } satisfies ComponentDefinition<BreadcrumbsProps>,
25
+ PageAnchor: {
26
+ exportPath: '@sarj/docs-ui/PageAnchor.astro',
27
+ purpose: 'Provide the focusable top anchor used by Starlight reference pages.',
28
+ properties: {},
29
+ } satisfies ComponentDefinition,
30
+ ReferencePage: {
31
+ exportPath: '@sarj/docs-ui/ReferencePage.astro',
32
+ purpose: 'Render a Starlight reference shell with explicit robots behavior.',
33
+ properties: {
34
+ title: 'Document title and primary accessible page identity.',
35
+ description: 'Concise page description used by document metadata.',
36
+ sidebar: 'Starlight sidebar definition for this reference surface.',
37
+ indexable: 'Whether robots may index the public page; defaults to true.',
38
+ hasSidebar: 'Whether the Starlight sidebar is rendered; defaults to true.',
39
+ template: 'Starlight document or splash template; defaults to doc.',
40
+ },
41
+ } satisfies ComponentDefinition<ReferencePageProps>,
42
+ RulePager: {
43
+ exportPath: '@sarj/docs-ui/RulePager.astro',
44
+ purpose: 'Navigate between adjacent rules with accessible links and guarded arrow-key shortcuts.',
45
+ properties: {
46
+ previous: 'Optional previous rule link and label.',
47
+ next: 'Optional next rule link and label.',
48
+ },
49
+ } satisfies ComponentDefinition<RulePagerProps>,
50
+ });
51
+
52
+ export const themeTokenCatalog = Object.freeze([
53
+ { cssName: '--sarj-color-report', purpose: 'Diagnostic and rejection emphasis.', light: '#c34453', dark: '#ff8794' },
54
+ { cssName: '--sarj-color-pass', purpose: 'Accepted example and success emphasis.', light: '#16806d', dark: '#62d5bd' },
55
+ { cssName: '--sarj-color-warning', purpose: 'Warning-level diagnostic emphasis with AA text contrast.', light: '#986a00', dark: '#a97708' },
56
+ { cssName: '--sarj-color-rule', purpose: 'Structural borders and separators.', light: '#e4e6eb', dark: '#272a32' },
57
+ { cssName: '--sarj-color-paper', purpose: 'Reference surface background.', light: '#ffffff', dark: '#0b0c10' },
58
+ { cssName: '--sarj-color-ink', purpose: 'Primary reference text.', light: '#12141a', dark: '#f4f5f8' },
59
+ ] satisfies readonly ThemeTokenDefinition[]);
@@ -1,18 +1,10 @@
1
1
  ---
2
- interface Ancestor {
3
- label: string;
4
- href: string;
5
- }
6
-
7
- interface Props {
8
- ancestors: readonly Ancestor[];
9
- current: string;
10
- }
2
+ import type { BreadcrumbsProps as Props } from '../contracts';
11
3
 
12
4
  const { ancestors, current } = Astro.props;
13
5
  ---
14
6
 
15
- <nav class="breadcrumbs" aria-label="Breadcrumb">
7
+ <nav class="breadcrumbs sarj-breadcrumbs" aria-label="Breadcrumb">
16
8
  <ol>
17
9
  {ancestors.map((ancestor) => <li><a href={ancestor.href}>{ancestor.label}</a></li>)}
18
10
  <li><span aria-current="page">{current}</span></li>
@@ -1,29 +1,16 @@
1
1
  ---
2
2
  import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
3
- import type { StarlightUserConfig } from '@astrojs/starlight/types';
4
-
5
- type Sidebar = NonNullable<StarlightUserConfig['sidebar']>;
6
- type Discovery = 'searchable' | 'navigation-only' | 'unlisted';
7
-
8
- interface Props {
9
- title: string;
10
- description: string;
11
- sidebar: Sidebar;
12
- discovery?: Discovery;
13
- hasSidebar?: boolean;
14
- template?: 'doc' | 'splash';
15
- }
3
+ import type { ReferencePageProps as Props } from '../contracts';
16
4
 
17
5
  const {
18
6
  title,
19
7
  description,
20
8
  sidebar,
21
- discovery = 'searchable',
9
+ indexable: indexableProp,
22
10
  hasSidebar = true,
23
11
  template = 'doc',
24
12
  } = Astro.props;
25
- const pagefind = discovery === 'searchable';
26
- const noindex = discovery === 'unlisted';
13
+ const indexable = indexableProp ?? true;
27
14
  ---
28
15
 
29
16
  <StarlightPage
@@ -31,15 +18,15 @@ const noindex = discovery === 'unlisted';
31
18
  title,
32
19
  description,
33
20
  template,
34
- pagefind,
21
+ pagefind: false,
35
22
  prev: false,
36
23
  next: false,
37
- head: noindex ? [{ tag: 'meta', attrs: { name: 'robots', content: 'noindex, nofollow' } }] : [],
24
+ head: indexable ? [] : [{ tag: 'meta', attrs: { name: 'robots', content: 'noindex, nofollow' } }],
38
25
  }}
39
26
  sidebar={sidebar}
40
27
  hasSidebar={hasSidebar}
41
28
  >
42
- <div data-pagefind-body={pagefind ? true : undefined} data-pagefind-ignore={pagefind ? undefined : true}>
29
+ <div>
43
30
  <slot />
44
31
  </div>
45
32
  </StarlightPage>
@@ -0,0 +1,32 @@
1
+ ---
2
+ import type { RulePagerProps as Props } from '../contracts';
3
+
4
+ const { previous, next } = Astro.props;
5
+ ---
6
+
7
+ <nav class="sarj-rule-pager" aria-label="Rule navigation">
8
+ {previous && (
9
+ <a href={previous.href} rel="prev" data-rule-previous aria-keyshortcuts="ArrowLeft" aria-label={`Previous rule: ${previous.label}`} title={previous.label}>
10
+ <kbd aria-hidden="true">←</kbd><span>Previous</span>
11
+ </a>
12
+ )}
13
+ {next && (
14
+ <a href={next.href} rel="next" data-rule-next aria-keyshortcuts="ArrowRight" aria-label={`Next rule: ${next.label}`} title={next.label}>
15
+ <span>Next</span><kbd aria-hidden="true">→</kbd>
16
+ </a>
17
+ )}
18
+ </nav>
19
+
20
+ <script>
21
+ document.addEventListener('keydown', (event) => {
22
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
23
+ if (event.target instanceof Element && event.target.closest('a, button, input, select, textarea, summary, [contenteditable]')) return;
24
+ const selector = event.key === 'ArrowLeft' ? '[data-rule-previous]' : event.key === 'ArrowRight' ? '[data-rule-next]' : null;
25
+ if (!selector) return;
26
+ const link = document.querySelector(selector);
27
+ if (link instanceof HTMLAnchorElement) {
28
+ event.preventDefault();
29
+ location.assign(link.href);
30
+ }
31
+ });
32
+ </script>
@@ -0,0 +1,37 @@
1
+ import type { StarlightUserConfig } from '@astrojs/starlight/types';
2
+
3
+ /** One navigable ancestor rendered before the current page. */
4
+ export interface BreadcrumbAncestor {
5
+ label: string;
6
+ href: string;
7
+ }
8
+
9
+ /** Public properties accepted by {@link Breadcrumbs}. */
10
+ export interface BreadcrumbsProps {
11
+ ancestors: readonly BreadcrumbAncestor[];
12
+ current: string;
13
+ }
14
+
15
+ export interface RulePagerLink {
16
+ href: string;
17
+ label: string;
18
+ }
19
+
20
+ /** Public properties accepted by {@link RulePager}. */
21
+ export interface RulePagerProps {
22
+ previous?: RulePagerLink | null;
23
+ next?: RulePagerLink | null;
24
+ }
25
+
26
+ export type ReferenceSidebar = NonNullable<StarlightUserConfig['sidebar']>;
27
+
28
+ /** Public properties accepted by {@link ReferencePage}. */
29
+ export interface ReferencePageProps {
30
+ title: string;
31
+ description: string;
32
+ sidebar: ReferenceSidebar;
33
+ /** Allow search engines to index the page. Defaults to true. */
34
+ indexable?: boolean;
35
+ hasSidebar?: boolean;
36
+ template?: 'doc' | 'splash';
37
+ }
@@ -0,0 +1,73 @@
1
+ @import './theme.css';
2
+
3
+ .main-pane,
4
+ .main-frame,
5
+ .sl-container {
6
+ min-width: 0;
7
+ }
8
+
9
+ .main-pane {
10
+ width: 100%;
11
+ }
12
+
13
+ .content-panel:has(> .reference-page-anchor:only-child) {
14
+ display: none;
15
+ }
16
+
17
+ header.header {
18
+ border-bottom: 1px solid var(--sarj-color-rule);
19
+ box-shadow: none;
20
+ }
21
+
22
+ .sidebar-pane a,
23
+ .social-icons a,
24
+ starlight-menu-button button {
25
+ min-width: 44px;
26
+ min-height: 44px;
27
+ }
28
+
29
+ .sidebar-content a[aria-current='page'] {
30
+ color: var(--sl-color-accent-high);
31
+ }
32
+
33
+ .sarj-rule-pager {
34
+ display: flex;
35
+ flex: 0 0 auto;
36
+ gap: 0.35rem;
37
+ margin: 0;
38
+ }
39
+
40
+ .sarj-rule-pager a {
41
+ display: inline-flex;
42
+ min-height: 44px;
43
+ align-items: center;
44
+ gap: 0.3rem;
45
+ padding: 0.35rem 0.55rem;
46
+ border: 1px solid var(--sarj-color-rule);
47
+ border-radius: 0.4rem;
48
+ color: var(--sl-color-white);
49
+ font-size: 0.75rem;
50
+ text-decoration: none;
51
+ }
52
+
53
+ .sarj-rule-pager a:hover,
54
+ .sarj-rule-pager a:focus-visible {
55
+ border-color: var(--sl-color-accent);
56
+ }
57
+
58
+ .sarj-rule-pager kbd {
59
+ padding: 0.05rem 0.28rem;
60
+ border: 1px solid var(--sarj-color-rule);
61
+ border-radius: 0.2rem;
62
+ background: var(--sl-color-gray-6);
63
+ font-family: var(--sl-font-mono);
64
+ }
65
+
66
+ @media (max-width: 22rem) {
67
+ .sarj-rule-pager a { width: 44px; justify-content: center; padding-inline: 0; }
68
+ .sarj-rule-pager a span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
69
+ }
70
+
71
+ @media (prefers-reduced-motion: reduce) {
72
+ :root { scroll-behavior: auto; }
73
+ }
@@ -15,11 +15,18 @@
15
15
  --sl-color-gray-5: #dfe2e8;
16
16
  --sl-color-gray-6: #f1f2f5;
17
17
  --sl-color-black: #ffffff;
18
- --report: #c34453;
19
- --pass: #16806d;
20
- --rule: #e4e6eb;
21
- --paper: #ffffff;
22
- --ink: #12141a;
18
+ --sarj-color-report: #c34453;
19
+ --sarj-color-pass: #16806d;
20
+ --sarj-color-warning: #986a00;
21
+ --sarj-color-rule: #e4e6eb;
22
+ --sarj-color-paper: #ffffff;
23
+ --sarj-color-ink: #12141a;
24
+ /* Compatibility aliases. Prefer the namespaced public tokens above. */
25
+ --report: var(--sarj-color-report);
26
+ --pass: var(--sarj-color-pass);
27
+ --rule: var(--sarj-color-rule);
28
+ --paper: var(--sarj-color-paper);
29
+ --ink: var(--sarj-color-ink);
23
30
  }
24
31
 
25
32
  :root[data-theme='dark'] {
@@ -34,9 +41,46 @@
34
41
  --sl-color-gray-5: #272b34;
35
42
  --sl-color-gray-6: #15171d;
36
43
  --sl-color-black: #0b0c10;
37
- --report: #ff8794;
38
- --pass: #62d5bd;
39
- --rule: #272a32;
40
- --paper: #0b0c10;
41
- --ink: #f4f5f8;
44
+ --sarj-color-report: #ff8794;
45
+ --sarj-color-pass: #62d5bd;
46
+ --sarj-color-warning: #a97708;
47
+ --sarj-color-rule: #272a32;
48
+ --sarj-color-paper: #0b0c10;
49
+ --sarj-color-ink: #f4f5f8;
50
+ }
51
+
52
+ .breadcrumbs ol {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ gap: 0.35rem;
56
+ margin: 0;
57
+ padding: 0;
58
+ list-style: none;
59
+ }
60
+
61
+ .breadcrumbs li {
62
+ display: inline-flex;
63
+ align-items: center;
64
+ gap: 0.35rem;
65
+ min-width: 0;
66
+ }
67
+
68
+ .breadcrumbs li + li::before {
69
+ color: var(--sl-color-gray-3);
70
+ content: '/';
71
+ }
72
+
73
+ .breadcrumbs a,
74
+ .breadcrumbs [aria-current='page'] {
75
+ overflow-wrap: anywhere;
76
+ }
77
+
78
+ .breadcrumbs a {
79
+ border-radius: 0.2rem;
80
+ color: var(--sl-color-accent-high);
81
+ }
82
+
83
+ .breadcrumbs a:focus-visible {
84
+ outline: 2px solid var(--sl-color-accent);
85
+ outline-offset: 3px;
42
86
  }